From 8d345cc1b86c9fb500e5c45ff2d5481551334beb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 14 Oct 2023 11:44:58 +1100 Subject: [PATCH 01/51] Adjustment for missing props --- app/Http/Requests/ClientPortal/RegisterRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/ClientPortal/RegisterRequest.php b/app/Http/Requests/ClientPortal/RegisterRequest.php index c2c5e8ecfcea..860628340369 100644 --- a/app/Http/Requests/ClientPortal/RegisterRequest.php +++ b/app/Http/Requests/ClientPortal/RegisterRequest.php @@ -40,7 +40,7 @@ class RegisterRequest extends FormRequest $rules = []; foreach ($this->company()->client_registration_fields as $field) { - if ($field['visible']) { + if ($field['visible'] ?? true) { $rules[$field['key']] = $field['required'] ? ['bail','required'] : ['sometimes']; } } From 17d11710a7147db67b3623386365398752b4bbcf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 05:49:14 +1100 Subject: [PATCH 02/51] Minor fixes --- .../Invoices/ProcessInvoicesInBulkRequest.php | 11 ++++++ .../Requests/Payment/StorePaymentRequest.php | 2 +- app/Utils/HtmlEngine.php | 10 ++--- app/Utils/Traits/MakesHash.php | 22 ++++++++++- app/Utils/VendorHtmlEngine.php | 39 +++++++++++-------- 5 files changed, 61 insertions(+), 23 deletions(-) diff --git a/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php b/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php index 1ee0490b8fb6..73724da000c3 100644 --- a/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php +++ b/app/Http/Requests/ClientPortal/Invoices/ProcessInvoicesInBulkRequest.php @@ -28,4 +28,15 @@ class ProcessInvoicesInBulkRequest extends FormRequest 'invoices' => ['array'], ]; } + + public function prepareForValidation() + { + $input = $this->all(); + + if(isset($input['invoices'])){ + $input['invoices'] = array_unique($input['invoices']); + } + + $this->replace($input); + } } diff --git a/app/Http/Requests/Payment/StorePaymentRequest.php b/app/Http/Requests/Payment/StorePaymentRequest.php index e51eeb50bc57..b110abcf38ec 100644 --- a/app/Http/Requests/Payment/StorePaymentRequest.php +++ b/app/Http/Requests/Payment/StorePaymentRequest.php @@ -51,7 +51,7 @@ class StorePaymentRequest extends Request $credits_total = 0; if (isset($input['client_id']) && is_string($input['client_id'])) { - $input['client_id'] = $this->decodePrimaryKey($input['client_id']); + $input['client_id'] = $this->decodePrimaryKey($input['client_id'], true); } if (array_key_exists('assigned_user_id', $input) && is_string($input['assigned_user_id'])) { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index f96d37be5df8..538b40b1d311 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -1014,17 +1014,17 @@ html { */ protected function generateEntityImagesMarkup() { - // if (!$this->client->getSetting('embed_documents') && !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { - // return ''; - // } + if (!$this->client->getSetting('embed_documents') || !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { + return ''; + } $dom = new \DOMDocument('1.0', 'UTF-8'); $container = $dom->createElement('div'); $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;'); - foreach ($this->entity->documents as $document) { - if (!$document->isImage()) { + foreach ($this->entity->documents()->where('is_public',true)->get() as $document) { + if (!$document->isImage()) { continue; } diff --git a/app/Utils/Traits/MakesHash.php b/app/Utils/Traits/MakesHash.php index 63c151b93530..dc8d6d158a40 100644 --- a/app/Utils/Traits/MakesHash.php +++ b/app/Utils/Traits/MakesHash.php @@ -62,8 +62,27 @@ trait MakesHash return $hashids->encode($value); } - public function decodePrimaryKey($value) + public function decodePrimaryKey($value, $return_string_failure = false) { + + try { + $hashids = new Hashids(config('ninja.hash_salt'), 10); + + $decoded_array = $hashids->decode($value); + + if(isset($decoded_array[0]) ?? false) { + return $decoded_array[0]; + } elseif($return_string_failure) { + return "Invalid Primary Key"; + } else { + throw new \Exception('Invalid Primary Key'); + } + + } catch (\Exception $e) { + return response()->json(['error'=>'Invalid primary key'], 400); + } + + /* try { $hashids = new Hashids(config('ninja.hash_salt'), 10); @@ -77,6 +96,7 @@ trait MakesHash } catch (\Exception $e) { return response()->json(['error'=>'Invalid primary key'], 400); } + */ } public function transformKeys($keys) diff --git a/app/Utils/VendorHtmlEngine.php b/app/Utils/VendorHtmlEngine.php index eb1f595584e5..7bd05d75924c 100644 --- a/app/Utils/VendorHtmlEngine.php +++ b/app/Utils/VendorHtmlEngine.php @@ -12,18 +12,19 @@ namespace App\Utils; -use App\Models\Country; -use App\Models\CreditInvitation; -use App\Models\InvoiceInvitation; -use App\Models\PurchaseOrderInvitation; -use App\Models\QuoteInvitation; -use App\Models\RecurringInvoiceInvitation; -use App\Utils\Traits\AppSetup; -use App\Utils\Traits\DesignCalculator; -use App\Utils\Traits\MakesDates; use Exception; +use App\Models\Account; +use App\Models\Country; +use App\Utils\Traits\AppSetup; +use App\Models\QuoteInvitation; +use App\Models\CreditInvitation; +use App\Utils\Traits\MakesDates; +use App\Models\InvoiceInvitation; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; +use App\Utils\Traits\DesignCalculator; +use App\Models\PurchaseOrderInvitation; +use App\Models\RecurringInvoiceInvitation; /** * Note the premise used here is that any currencies will be formatted back to the company currency and not @@ -775,31 +776,37 @@ html { */ protected function generateEntityImagesMarkup() { - if ($this->company->getSetting('embed_documents') === false) { + + if (!$this->vendor->getSetting('embed_documents') || !$this->company->account->hasFeature(Account::FEATURE_DOCUMENTS)) { return ''; } $dom = new \DOMDocument('1.0', 'UTF-8'); $container = $dom->createElement('div'); - $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(2, 1fr);'); - - foreach ($this->entity->documents as $document) { + $container->setAttribute('style', 'display:grid; grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); grid-template-rows: repeat(2, 1fr);justify-items: center;'); + + foreach ($this->entity->documents()->where('is_public',true)->get() as $document) { if (!$document->isImage()) { continue; } $image = $dom->createElement('img'); - $image->setAttribute('src', $document->generateUrl()); - $image->setAttribute('style', 'max-height: 100px; margin-top: 20px;'); + $image->setAttribute('src', "data:image/png;base64,".base64_encode($document->getFile())); + $image->setAttribute('style', 'max-width: 50%; margin-top: 20px;'); $container->appendChild($image); } $dom->appendChild($container); - return $dom->saveHTML(); + $html = $dom->saveHTML(); + + $dom = null; + + return $html; + } /** From 14144f72e4da70b05392b8f22ef311ad2e55db61 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 16:57:17 +1100 Subject: [PATCH 03/51] Updated translations --- lang/en/texts.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lang/en/texts.php b/lang/en/texts.php index 49eb412a6202..5fe2741906a1 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -3679,9 +3679,9 @@ $LANG = array( 'send_date' => 'Send Date', 'auto_bill_on' => 'Auto Bill On', 'minimum_under_payment_amount' => 'Minimum Under Payment Amount', - 'allow_over_payment' => 'Allow Over Payment', + 'allow_over_payment' => 'Allow Overpayment', 'allow_over_payment_help' => 'Support paying extra to accept tips', - 'allow_under_payment' => 'Allow Under Payment', + 'allow_under_payment' => 'Allow Underpayment', 'allow_under_payment_help' => 'Support paying at minimum the partial/deposit amount', 'test_mode' => 'Test Mode', 'calculated_rate' => 'Calculated Rate', @@ -3978,8 +3978,8 @@ $LANG = array( 'account_balance' => 'Account Balance', 'thanks' => 'Thanks', 'minimum_required_payment' => 'Minimum required payment is :amount', - 'under_payments_disabled' => 'Company doesn\'t support under payments.', - 'over_payments_disabled' => 'Company doesn\'t support over payments.', + 'under_payments_disabled' => 'Company doesn\'t support underpayments.', + 'over_payments_disabled' => 'Company doesn\'t support overpayments.', 'saved_at' => 'Saved at :time', 'credit_payment' => 'Credit applied to Invoice :invoice_number', 'credit_subject' => 'New credit :number from :account', @@ -4654,8 +4654,8 @@ $LANG = array( 'search_purchase_order' => 'Search Purchase Order', 'search_purchase_orders' => 'Search Purchase Orders', 'login_url' => 'Login URL', - 'enable_applying_payments' => 'Enable Applying Payments', - 'enable_applying_payments_help' => 'Support separately creating and applying payments', + 'enable_applying_payments' => 'Manual Overpayments', + 'enable_applying_payments_help' => 'Support adding an overpayment amount manually on a payment', 'stock_quantity' => 'Stock Quantity', 'notification_threshold' => 'Notification Threshold', 'track_inventory' => 'Track Inventory', From 9913d4c0b260d4d5a33ea92e7502181620ba1ffa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 17:15:38 +1100 Subject: [PATCH 04/51] Minor fixes for tax rules --- app/DataMapper/Tax/AU/Rule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/DataMapper/Tax/AU/Rule.php b/app/DataMapper/Tax/AU/Rule.php index d6536e1a7177..c1d9e7ed218e 100644 --- a/app/DataMapper/Tax/AU/Rule.php +++ b/app/DataMapper/Tax/AU/Rule.php @@ -62,7 +62,7 @@ class Rule extends BaseRule implements RuleInterface public function taxByType($item): self { - if ($this->client->is_tax_exempt) { + if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) { return $this->taxExempt($item); } From 14033459891de54ec545dee807495d3154944904 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 17:16:27 +1100 Subject: [PATCH 05/51] Fixes for null or missing tax_id props --- app/DataMapper/Tax/BaseRule.php | 2 +- app/DataMapper/Tax/DE/Rule.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/DataMapper/Tax/BaseRule.php b/app/DataMapper/Tax/BaseRule.php index 36635429a6d7..ba8169734547 100644 --- a/app/DataMapper/Tax/BaseRule.php +++ b/app/DataMapper/Tax/BaseRule.php @@ -297,7 +297,7 @@ class BaseRule implements RuleInterface public function tax($item = null): self { - if ($this->client->is_tax_exempt) { + if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) { return $this->taxExempt($item); diff --git a/app/DataMapper/Tax/DE/Rule.php b/app/DataMapper/Tax/DE/Rule.php index c82f701496f4..8214188c369f 100644 --- a/app/DataMapper/Tax/DE/Rule.php +++ b/app/DataMapper/Tax/DE/Rule.php @@ -63,7 +63,7 @@ class Rule extends BaseRule implements RuleInterface public function taxByType($item): self { - if ($this->client->is_tax_exempt) { + if ($this->client->is_tax_exempt || !property_exists($item, 'tax_id')) { return $this->taxExempt($item); } From 36f5b2e11ec80562b4d8b68637fbfa7901c0fd98 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 17:21:56 +1100 Subject: [PATCH 06/51] Minor fixes for paytrace --- app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php | 2 +- app/PaymentDrivers/PayTrace/CreditCard.php | 2 +- app/PaymentDrivers/PaytracePaymentDriver.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php index 19ff1974c64d..ceb1417c7530 100644 --- a/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php +++ b/app/Helpers/Bank/Yodlee/Transformer/AccountTransformer.php @@ -140,7 +140,7 @@ class AccountTransformer implements AccountTransformerInterface 'id' => $account->id, 'account_type' => $account->CONTAINER, // 'account_name' => $account->accountName, - 'account_name' => property_exists($account, 'accountName') ? $account->accountName : $account->nickname, + 'account_name' => property_exists($account, 'accountName') ? $account->accountName : ($account->nickname ?? 'Unknown Account'), 'account_status' => $account_status, 'account_number' => property_exists($account, 'accountNumber') ? '**** ' . substr($account?->accountNumber, -7) : '', 'provider_account_id' => $account->providerAccountId, diff --git a/app/PaymentDrivers/PayTrace/CreditCard.php b/app/PaymentDrivers/PayTrace/CreditCard.php index 720d5f0b02be..350fc6948b87 100644 --- a/app/PaymentDrivers/PayTrace/CreditCard.php +++ b/app/PaymentDrivers/PayTrace/CreditCard.php @@ -183,7 +183,7 @@ class CreditCard $response = $this->paytrace->gatewayRequest('/v1/transactions/sale/by_customer', $data); - if ($response->success) { + if ($response->success ?? false) { $this->paytrace->logSuccessfulGatewayResponse(['response' => $response, 'data' => $this->paytrace->payment_hash], SystemLog::TYPE_PAYTRACE); return $this->processSuccessfulPayment($response); diff --git a/app/PaymentDrivers/PaytracePaymentDriver.php b/app/PaymentDrivers/PaytracePaymentDriver.php index 5f33ea843e3a..b55e8857c169 100644 --- a/app/PaymentDrivers/PaytracePaymentDriver.php +++ b/app/PaymentDrivers/PaytracePaymentDriver.php @@ -198,7 +198,7 @@ class PaytracePaymentDriver extends BaseDriver $auth_data = json_decode($response); - if (! property_exists($auth_data, 'access_token')) { + if (!isset($auth_data) || ! property_exists($auth_data, 'access_token')) { throw new SystemError('Error authenticating with PayTrace'); } From 3ac6f4c2a76ca37041528cfc40d70531fcdb9acf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 15 Oct 2023 17:27:00 +1100 Subject: [PATCH 07/51] Catch missing props --- app/PaymentDrivers/Eway/CreditCard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/PaymentDrivers/Eway/CreditCard.php b/app/PaymentDrivers/Eway/CreditCard.php index 11dbafdcfe2b..d4203d0eab9f 100644 --- a/app/PaymentDrivers/Eway/CreditCard.php +++ b/app/PaymentDrivers/Eway/CreditCard.php @@ -251,7 +251,7 @@ class CreditCard $response = $this->eway_driver->init()->eway->createTransaction(\Eway\Rapid\Enum\ApiMethod::DIRECT, $transaction); - if ($response->TransactionStatus) { + if ($response->TransactionStatus ?? false) { $this->logResponse($response, true); $payment = $this->storePayment($response); } else { From bf935896073f1a5b7b7927c030b653a452929dca Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Oct 2023 06:27:37 +1100 Subject: [PATCH 08/51] Only add shipping address to einvoice if country is set. --- app/Services/Invoice/EInvoice/ZugferdEInvoice.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php index 6b7dbe884350..539e78f6e270 100644 --- a/app/Services/Invoice/EInvoice/ZugferdEInvoice.php +++ b/app/Services/Invoice/EInvoice/ZugferdEInvoice.php @@ -75,7 +75,7 @@ class ZugferdEInvoice extends AbstractService } else { $this->xrechnung->setDocumentBuyerReference($client->routing_id); } - if (!empty($client->shipping_address1)){ + if (!empty($client->shipping_address1) && $client->shipping_country->exists()){ $this->xrechnung->setDocumentShipToAddress($client->shipping_address1, $client->shipping_address2, "", $client->shipping_postal_code, $client->shipping_city, $client->shipping_country->iso_3166_2, $client->shipping_state); } From 55f71a27c7df25b10fa8e075d5521acdfd6f90b4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Oct 2023 07:25:47 +1100 Subject: [PATCH 09/51] Fixes for square autobill --- app/PaymentDrivers/Square/SquareWebhook.php | 10 ++++++++-- app/Services/Invoice/AutoBillInvoice.php | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index c83b73fd4765..c638e2ad3bb7 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -134,8 +134,14 @@ class SquareWebhook implements ShouldQueue nlog("Searching by payment hash"); - $payment_hash_id = $apiResponse->getPayment()->getReferenceId() ?? false; - $square_payment = $apiResponse->getPayment()->jsonSerialize(); + $body = json_decode($apiResponse->getBody()); + + $payment_hash_id = $body->payment->reference_id ?? false; + $square_payment = $body->payment ?? false; + + if(!$payment_hash_id) + return; + $payment_hash = PaymentHash::query()->where('hash', $payment_hash_id)->firstOrFail(); $payment_hash->data = array_merge((array) $payment_hash->data, (array)$square_payment); diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 0585f888bd66..19a366fdc507 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -120,7 +120,7 @@ class AutoBillInvoice extends AbstractService /* Build payment hash */ $payment_hash = PaymentHash::create([ - 'hash' => Str::random(64), + 'hash' => Str::random(32), 'data' => [ 'amount_with_fee' => $amount + $fee, 'invoices' => [ From 0694378bb5e83ffacab896751c1c8e53fafad6a8 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 16 Oct 2023 07:28:17 +1100 Subject: [PATCH 10/51] Fixes for square autobill --- app/PaymentDrivers/Square/SquareWebhook.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/PaymentDrivers/Square/SquareWebhook.php b/app/PaymentDrivers/Square/SquareWebhook.php index c638e2ad3bb7..67d656e8c926 100644 --- a/app/PaymentDrivers/Square/SquareWebhook.php +++ b/app/PaymentDrivers/Square/SquareWebhook.php @@ -134,14 +134,8 @@ class SquareWebhook implements ShouldQueue nlog("Searching by payment hash"); - $body = json_decode($apiResponse->getBody()); - - $payment_hash_id = $body->payment->reference_id ?? false; - $square_payment = $body->payment ?? false; - - if(!$payment_hash_id) - return; - + $payment_hash_id = $apiResponse->getResult()->getPayment()->getReferenceId() ?? false; + $square_payment = $apiResponse->getResult()->getPayment()->jsonSerialize(); $payment_hash = PaymentHash::query()->where('hash', $payment_hash_id)->firstOrFail(); $payment_hash->data = array_merge((array) $payment_hash->data, (array)$square_payment); From fb3c0120ec7ff82c65ee6bf2bcd74098e84725a3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 17 Oct 2023 13:39:36 +1100 Subject: [PATCH 11/51] Fixes for Email histoyr --- .../Controllers/EmailHistoryController.php | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php index 4da69ba7b6dc..aea6a5e2008f 100644 --- a/app/Http/Controllers/EmailHistoryController.php +++ b/app/Http/Controllers/EmailHistoryController.php @@ -31,11 +31,11 @@ class EmailHistoryController extends BaseController ->where('category_id', SystemLog::CATEGORY_MAIL) ->orderBy('id', 'DESC') ->cursor() - ->map(function ($system_log) { - if(($system_log->log['history'] && $system_log->log['history']['events'] && count($system_log->log['history']['events']) >=1) ?? false) { - return $system_log->log['history']; - } - }); + ->filter(function ($system_log) { + return ($system_log->log['history'] && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; + })->map(function ($system_log) { + return $system_log->log['history']; + })->values()->all(); return response()->json($data, 200); @@ -51,16 +51,17 @@ class EmailHistoryController extends BaseController /** @var \App\Models\User $user */ $user = auth()->user(); + $data = SystemLog::where('company_id', $user->company()->id) - ->where('category_id', SystemLog::CATEGORY_MAIL) - ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id)) - ->orderBy('id', 'DESC') - ->cursor() - ->map(function ($system_log) { - if(($system_log->log['history'] && $system_log->log['history']['events'] && count($system_log->log['history']['events']) >=1) ?? false) { - return $system_log->log['history']; - } - }); + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->whereJsonContains('log->history->entity_id', $this->encodePrimaryKey($request->entity_id)) + ->orderBy('id', 'DESC') + ->cursor() + ->filter(function ($system_log) { + return ($system_log->log['history'] && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; + })->map(function ($system_log) { + return $system_log->log['history']; + })->values()->all(); return response()->json($data, 200); From e4fc9e2cc897116a699327cb132b5a949b23a03d Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 17 Oct 2023 21:28:18 +1100 Subject: [PATCH 12/51] Refactor for live previews --- app/Http/Controllers/PreviewController.php | 180 ++++++++++++++---- .../ProtectedDownloadController.php | 2 +- .../Preview/PreviewInvoiceRequest.php | 126 +++++++++++- app/Jobs/Util/PreviewPdf.php | 13 +- tests/Feature/LiveDesignTest.php | 26 +++ 5 files changed, 297 insertions(+), 50 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 0f94c8e9ac1a..6777af3f941d 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -11,42 +11,43 @@ namespace App\Http\Controllers; -use App\DataMapper\Analytics\LivePreview; -use App\Factory\CreditFactory; -use App\Factory\InvoiceFactory; -use App\Factory\QuoteFactory; -use App\Factory\RecurringInvoiceFactory; -use App\Http\Requests\Preview\DesignPreviewRequest; -use App\Http\Requests\Preview\PreviewInvoiceRequest; -use App\Jobs\Util\PreviewPdf; -use App\Libraries\MultiDB; +use App\Utils\Ninja; +use App\Models\Quote; use App\Models\Client; -use App\Models\ClientContact; use App\Models\Credit; use App\Models\Invoice; -use App\Models\InvoiceInvitation; -use App\Models\Quote; -use App\Models\RecurringInvoice; -use App\Repositories\CreditRepository; -use App\Repositories\InvoiceRepository; -use App\Repositories\QuoteRepository; -use App\Repositories\RecurringInvoiceRepository; +use App\Utils\HtmlEngine; +use App\Libraries\MultiDB; +use App\Factory\QuoteFactory; +use App\Jobs\Util\PreviewPdf; +use App\Models\ClientContact; use App\Services\Pdf\PdfMock; +use App\Factory\CreditFactory; +use App\Factory\InvoiceFactory; +use App\Utils\Traits\MakesHash; +use App\Models\RecurringInvoice; +use App\Utils\PhantomJS\Phantom; +use App\Models\InvoiceInvitation; use App\Services\PdfMaker\Design; +use App\Utils\HostedPDF\NinjaPdf; +use Illuminate\Support\Facades\DB; +use App\Services\PdfMaker\PdfMaker; +use Illuminate\Support\Facades\App; +use App\Repositories\QuoteRepository; +use Illuminate\Support\Facades\Cache; +use App\Repositories\CreditRepository; +use App\Utils\Traits\MakesInvoiceHtml; +use Turbo124\Beacon\Facades\LightLogs; +use App\Repositories\InvoiceRepository; +use App\Utils\Traits\Pdf\PageNumbering; +use App\Factory\RecurringInvoiceFactory; +use Illuminate\Support\Facades\Response; +use App\DataMapper\Analytics\LivePreview; +use App\Repositories\RecurringInvoiceRepository; +use App\Http\Requests\Preview\DesignPreviewRequest; use App\Services\PdfMaker\Design as PdfDesignModel; use App\Services\PdfMaker\Design as PdfMakerDesign; -use App\Services\PdfMaker\PdfMaker; -use App\Utils\HostedPDF\NinjaPdf; -use App\Utils\HtmlEngine; -use App\Utils\Ninja; -use App\Utils\PhantomJS\Phantom; -use App\Utils\Traits\MakesHash; -use App\Utils\Traits\MakesInvoiceHtml; -use App\Utils\Traits\Pdf\PageNumbering; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Response; -use Turbo124\Beacon\Facades\LightLogs; +use App\Http\Requests\Preview\PreviewInvoiceRequest; class PreviewController extends BaseController { @@ -56,7 +57,108 @@ class PreviewController extends BaseController public function __construct() { - parent::__construct(); + parent::__construct(); + } + + private function purgeCache() + { nlog(auth()->user()->id); + Cache::pull("preview_".auth()->user()->id); + } + + public function newLivePreview(PreviewInvoiceRequest $request) + { + + $time = time(); + nlog($time); + + $invitation = $request->resolveInvitation(); + + App::forgetInstance('translator'); + $t = app('translator'); + App::setLocale($invitation->contact->preferredLocale()); + $t->replace(Ninja::transformTranslations($invitation->{$request->entity}->client->getMergedSettings())); + + $html = new HtmlEngine($invitation); + $variables = $html->generateLabelsAndValues(); + + $entity_obj = $invitation->{$request->entity}; + + // if (! $invitation->{$request->entity}->id ?? true) { + // $invitation->{$request->entity}->service()->fillDefaults(); + // } + + $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); + + /* Catch all in case migration doesn't pass back a valid design */ + if (! $design) { + $design = \App\Models\Design::find(2); + } + + if ($design->is_custom) { + $options = [ + 'custom_partials' => json_decode(json_encode($design->design), true), + ]; + $template = new PdfMakerDesign(PdfDesignModel::CUSTOM, $options); + } else { + $template = new PdfMakerDesign(strtolower($design->name)); + } + + $state = [ + 'template' => $template->elements([ + 'client' => $entity_obj->client, + 'entity' => $entity_obj, + 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + '$product' => $design->design->product, + 'variables' => $variables, + ]), + 'variables' => $variables, + 'options' => [ + 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), + 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + ], + 'process_markdown' => $entity_obj->client->company->markdown_enabled, + ]; + + $maker = new PdfMaker($state); + + $maker + ->design($template) + ->build(); + + if (request()->query('html') == 'true') { + return $maker->getCompiledHTML(); + } + + //if phantom js...... inject here.. + if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + } + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $company = $user->company(); + + if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { + + $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); + + $numbered_pdf = $this->pageNumbering($pdf, $company); + + if ($numbered_pdf) { + $pdf = $numbered_pdf; + } + + return $pdf; + } + + $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + + nlog("merpy derp {$time}"); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); + } /** @@ -173,10 +275,18 @@ class PreviewController extends BaseController public function live(PreviewInvoiceRequest $request) { + + // if(Cache::has("preview_".auth()->user()->id)) + // return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); + + nlog("should not see a lot of these"); + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); } + Cache::put("preview_".auth()->user()->id, 60); + $start = microtime(true); /** @var \App\Models\User $user */ @@ -287,9 +397,13 @@ class PreviewController extends BaseController DB::connection(config('database.default'))->rollBack(); if (request()->query('html') == 'true') { + $this->purgeCache(); return $maker->getCompiledHTML(); } + } catch(\Exception $e) { + + $this->purgeCache(); DB::connection(config('database.default'))->rollBack(); @@ -302,6 +416,7 @@ class PreviewController extends BaseController //if phantom js...... inject here.. if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { + $this->purgeCache(); return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); } @@ -319,7 +434,7 @@ class PreviewController extends BaseController if ($numbered_pdf) { $pdf = $numbered_pdf; } - + $this->purgeCache(); return $pdf; } @@ -335,7 +450,8 @@ class PreviewController extends BaseController $response->header('Content-Type', 'application/pdf'); $response->header('Server-Timing', microtime(true)-$start); - + nlog("returning a response"); + $this->purgeCache(); return $response; } diff --git a/app/Http/Controllers/ProtectedDownloadController.php b/app/Http/Controllers/ProtectedDownloadController.php index b8f598a11021..014527bace14 100644 --- a/app/Http/Controllers/ProtectedDownloadController.php +++ b/app/Http/Controllers/ProtectedDownloadController.php @@ -23,7 +23,7 @@ class ProtectedDownloadController extends BaseController public function index(Request $request) { - + /** @var string $hashed_path */ $hashed_path = Cache::pull($request->hash); if (!$hashed_path) { diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index bfa4db546470..50cc0a7fdf73 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -11,15 +11,29 @@ namespace App\Http\Requests\Preview; +use App\Models\Quote; +use App\Models\Client; +use App\Models\Credit; +use App\Models\Invoice; +use App\Libraries\MultiDB; use App\Http\Requests\Request; -use App\Utils\Traits\CleanLineItems; +use App\Models\CompanyGateway; +use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; +use Illuminate\Validation\Rule; +use App\Models\CreditInvitation; +use App\Models\RecurringInvoice; +use App\Models\InvoiceInvitation; +use App\Utils\Traits\CleanLineItems; +use App\Models\RecurringInvoiceInvitation; class PreviewInvoiceRequest extends Request { use MakesHash; use CleanLineItems; + private string $entity_plural = ''; + /** * Determine if the user is authorized to make this request. * @@ -27,20 +41,32 @@ class PreviewInvoiceRequest extends Request */ public function authorize() : bool { - return auth()->user()->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->hasIntersectPermissionsOrAdmin(['view_invoice', 'view_quote', 'view_recurring_invoice', 'view_credit', 'create_invoice', 'create_quote', 'create_recurring_invoice', 'create_credit','edit_invoice', 'edit_quote', 'edit_recurring_invoice', 'edit_credit']); } public function rules() { - $rules = []; + /** @var \App\Models\User $user */ + $user = auth()->user(); - $rules['number'] = ['nullable']; + return [ + 'number' => 'nullable', + 'entity' => 'bail|sometimes|in:invoice,quote,credit,recurring_invoice', + 'entity_id' => ['bail','sometimes','integer',Rule::exists($this->entity_plural, 'id')->where('is_deleted',0)->where('company_id', $user->company()->id)], + 'client_id' => ['required', Rule::exists(Client::class, 'id')->where('is_deleted', 0)->where('company_id', $user->company()->id)], + ]; - return $rules; } public function prepareForValidation() { + + /** @var \App\Models\User $user */ + $user = auth()->user(); + $input = $this->all(); $input = $this->decodePrimaryKeys($input); @@ -50,6 +76,96 @@ class PreviewInvoiceRequest extends Request $input['balance'] = 0; $input['number'] = isset($input['number']) ? $input['number'] : ctrans('texts.live_preview').' #'.rand(0, 1000); + if($input['entity_id'] ?? false) + $input['entity_id'] = $this->decodePrimaryKey($input['entity_id'], true); + + $this->convertEntityPlural($input['entity'] ?? 'invoice'); + $this->replace($input); } + + public function resolveInvitation() + { + $invitation = false; + + if(! $this->entity_id ?? false) + return $this->stubInvitation(); + + match($this->entity){ + 'invoice' => $invitation = Invoice::withTrashed()->where('invoice_id', $this->entity_id)->first(), + 'quote' => $invitation = Quote::withTrashed()->where('quote_id', $this->entity_id)->first(), + 'credit' => $invitation = Credit::withTrashed()->where('credit_id', $this->entity_id)->first(), + 'recurring_invoice' => $invitation = RecurringInvoice::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), + }; + + if($invitation) + return $invitation; + + $invitation = $this->stubInvitation(); + } + + public function stubInvitation() + { + $client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + $invitation = false; + + match($this->entity) { + 'invoice' => $invitation = InvoiceInvitation::factory()->make(), + 'quote' => $invitation = QuoteInvitation::factory()->make(), + 'credit' => $invitation = CreditInvitation::factory()->make(), + 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::factory()->make(), + default => $invitation = InvoiceInvitation::factory()->make(), + }; + + $entity = $this->stubEntity($client); + + $invitation->make(); + $invitation->setRelation($this->entity, $entity); + $invitation->setRelation('contact', $client->contacts->first()->load('client.company')); + $invitation->setRelation('company', $client->company); + + return $invitation; + } + + private function stubEntity(Client $client) + { + $entity = false; + + match($this->entity){ + 'invoice' => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'quote' => $entity = Quote::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'credit' => $entity = Credit::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + 'recurring_invoice' => $entity = RecurringInvoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + default => $entity = Invoice::factory()->make(['client_id' => $client->id,'user_id' => $client->user_id, 'company_id' => $client->company_id]), + }; + + $entity->setRelation('client', $client); + $entity->setRelation('company', $client->company); + $entity->setRelation('user', $client->user); + $entity->fill($this->all()); + + return $entity; + } + + private function convertEntityPlural(string $entity) :self + { + switch ($entity) { + case 'invoice': + $this->entity_plural = 'invoices'; + return $this; + case 'quote': + $this->entity_plural = 'quotes'; + return $this; + case 'credit': + $this->entity_plural = 'credits'; + return $this; + case 'recurring_invoice': + $this->entity_plural = 'invoices'; + return $this; + default: + $this->entity_plural = 'invoices'; + return $this; + } + } + } diff --git a/app/Jobs/Util/PreviewPdf.php b/app/Jobs/Util/PreviewPdf.php index 214201f1a660..0838379e9428 100644 --- a/app/Jobs/Util/PreviewPdf.php +++ b/app/Jobs/Util/PreviewPdf.php @@ -24,25 +24,14 @@ class PreviewPdf implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels, PdfMaker, PageNumbering; - public $company; - - private $disk; - - public $design_string; - /** * Create a new job instance. * * @param $design_string * @param Company $company */ - public function __construct($design_string, Company $company) + public function __construct(public string $design_string, public Company $company) { - $this->company = $company; - - $this->design_string = $design_string; - - $this->disk = $disk ?? config('filesystems.default'); } public function handle() diff --git a/tests/Feature/LiveDesignTest.php b/tests/Feature/LiveDesignTest.php index 03ce04551e54..7dc750f10902 100644 --- a/tests/Feature/LiveDesignTest.php +++ b/tests/Feature/LiveDesignTest.php @@ -13,7 +13,9 @@ namespace Tests\Feature; use Tests\TestCase; use App\Models\Design; +use App\Utils\HtmlEngine; use Tests\MockAccountData; +use App\Models\InvoiceInvitation; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -41,6 +43,30 @@ class LiveDesignTest extends TestCase } } + public function testSyntheticInvitations() + { + $this->assertGreaterThanOrEqual(1, $this->client->contacts->count()); + + $ii = InvoiceInvitation::factory() + ->for($this->invoice) + ->for($this->client->contacts->first(), 'contact') + ->for($this->company) + ->for($this->user) + ->make(); + + $this->assertInstanceOf(InvoiceInvitation::class, $ii); + + $engine = new HtmlEngine($ii); + + $this->assertNotNull($engine); + + $data = $engine->generateLabelsAndValues(); + + $this->assertIsArray($data); + + nlog($data); + } + public function testDesignRoute200() { $data = [ From c955dfc910141f63c66cd19a82c20631c8634cad Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 17 Oct 2023 21:38:35 +1100 Subject: [PATCH 13/51] Fixes for recurring invoice export --- app/Export/CSV/BaseExport.php | 1 + app/Export/CSV/InvoiceExport.php | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index e0344792895c..dc371aa28013 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -168,6 +168,7 @@ class BaseExport 'tax_rate1' => 'invoice.tax_rate1', 'tax_rate2' => 'invoice.tax_rate2', 'tax_rate3' => 'invoice.tax_rate3', + 'recurring_invoice' => 'invoice.recurring_id', ]; protected array $recurring_invoice_report_keys = [ diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 0a8f3188f2c3..741f5e22a029 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -142,6 +142,11 @@ class InvoiceExport extends BaseExport if (in_array('invoice.status', $this->input['report_keys'])) { $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id); } + + if (in_array('invoice.recurring_id', $this->input['report_keys'])) { + $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? ''; + } + return $entity; } From d06f383a98c16a08056c9f65be852cadc5338be6 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 10:14:14 +1100 Subject: [PATCH 14/51] Minor fixes --- app/Models/Presenters/ClientPresenter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Models/Presenters/ClientPresenter.php b/app/Models/Presenters/ClientPresenter.php index ac4b184d94e1..aed92c0eb8c9 100644 --- a/app/Models/Presenters/ClientPresenter.php +++ b/app/Models/Presenters/ClientPresenter.php @@ -23,7 +23,7 @@ class ClientPresenter extends EntityPresenter */ public function name() { - if ($this->entity->name) { + if (strlen($this->entity->name) > 1) { return $this->entity->name; } From d62bdbedcbd7e5d61f40755d18933a453459ffad Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 10:30:45 +1100 Subject: [PATCH 15/51] Fixes for showing tasks in client portal --- app/Http/Controllers/RecurringInvoiceController.php | 1 + app/Http/Livewire/TasksTable.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/RecurringInvoiceController.php b/app/Http/Controllers/RecurringInvoiceController.php index d09d31e80220..94c2192d178a 100644 --- a/app/Http/Controllers/RecurringInvoiceController.php +++ b/app/Http/Controllers/RecurringInvoiceController.php @@ -157,6 +157,7 @@ class RecurringInvoiceController extends BaseController $user = auth()->user(); $recurring_invoice = RecurringInvoiceFactory::create($user->company()->id, $user->id); + $recurring_invoice->auto_bill = $user->company()->settings->auto_bill; return $this->itemResponse($recurring_invoice); } diff --git a/app/Http/Livewire/TasksTable.php b/app/Http/Livewire/TasksTable.php index 40106c390bd6..f229db889f46 100644 --- a/app/Http/Livewire/TasksTable.php +++ b/app/Http/Livewire/TasksTable.php @@ -39,11 +39,11 @@ class TasksTable extends Component ->where('is_deleted', false) ->where('client_id', auth()->guard('contact')->user()->client_id); - if ($this->company->getSetting('show_all_tasks_client_portal') === 'invoiced') { + if ( auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'invoiced') { $query = $query->whereNotNull('invoice_id'); } - if ($this->company->getSetting('show_all_tasks_client_portal') === 'uninvoiced') { + if ( auth()->guard('contact')->user()->client->getSetting('show_all_tasks_client_portal') === 'uninvoiced') { $query = $query->whereNull('invoice_id'); } From 1163c42fd8a53eae74fd82d74d162a8a53ae73c4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 15:22:38 +1100 Subject: [PATCH 16/51] Fixes for report exports --- app/Export/CSV/BaseExport.php | 10 +++- app/Export/CSV/CreditExport.php | 2 + app/Export/CSV/InvoiceExport.php | 2 + app/Export/CSV/InvoiceItemExport.php | 23 ++++++++ app/Export/CSV/PaymentExport.php | 2 + app/Export/CSV/PurchaseOrderExport.php | 9 ++- app/Export/CSV/PurchaseOrderItemExport.php | 2 + app/Export/CSV/QuoteExport.php | 2 + app/Export/CSV/QuoteItemExport.php | 2 + app/Export/CSV/RecurringInvoiceExport.php | 6 +- app/Export/CSV/TaskExport.php | 2 + .../Export/ReportCsvGenerationTest.php | 58 +++++++++++++++++-- 12 files changed, 110 insertions(+), 10 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index dc371aa28013..0db407e3440f 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -231,7 +231,7 @@ class BaseExport 'po_number' => 'purchase_order.po_number', 'private_notes' => 'purchase_order.private_notes', 'public_notes' => 'purchase_order.public_notes', - 'status' => 'purchase_order.status_id', + 'status' => 'purchase_order.status', 'tax_name1' => 'purchase_order.tax_name1', 'tax_name2' => 'purchase_order.tax_name2', 'tax_name3' => 'purchase_order.tax_name3', @@ -430,6 +430,14 @@ class BaseExport 'project' => 'task.project_id', ]; + protected array $forced_client_fields = [ + "name" => "client.name", + ]; + + protected array $forced_vendor_fields = [ + "name" => "vendor.name", + ]; + protected function filterByClients($query) { if (isset($this->input['client_id']) && $this->input['client_id'] != 'all') { diff --git a/app/Export/CSV/CreditExport.php b/app/Export/CSV/CreditExport.php index 29afbfa16100..c5c05f3c23e2 100644 --- a/app/Export/CSV/CreditExport.php +++ b/app/Export/CSV/CreditExport.php @@ -93,6 +93,8 @@ class CreditExport extends BaseExport $this->input['report_keys'] = array_values($this->credit_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Credit::query() ->withTrashed() ->with('client') diff --git a/app/Export/CSV/InvoiceExport.php b/app/Export/CSV/InvoiceExport.php index 741f5e22a029..4dc4ee3d8b94 100644 --- a/app/Export/CSV/InvoiceExport.php +++ b/app/Export/CSV/InvoiceExport.php @@ -50,6 +50,8 @@ class InvoiceExport extends BaseExport $this->input['report_keys'] = array_values($this->invoice_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Invoice::query() ->withTrashed() ->with('client') diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 74c1a64c25b0..5ac3c9fb2fbf 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -62,6 +62,8 @@ class InvoiceItemExport extends BaseExport $this->input['report_keys'] = array_values($this->mergeItemsKeys('invoice_report_keys')); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Invoice::query() ->withTrashed() ->with('client') @@ -200,6 +202,27 @@ class InvoiceItemExport extends BaseExport $entity['tax_category'] = $invoice->taxTypeString($entity['tax_category']); } + if (in_array('invoice.country_id', $this->input['report_keys'])) { + $entity['invoice.country_id'] = $invoice->client->country ? ctrans("texts.country_{$invoice->client->country->name}") : ''; + } + + if (in_array('invoice.currency_id', $this->input['report_keys'])) { + $entity['invoice.currency_id'] = $invoice->client->currency() ? $invoice->client->currency()->code : $invoice->company->currency()->code; + } + + if (in_array('invoice.client_id', $this->input['report_keys'])) { + $entity['invoice.client_id'] = $invoice->client->present()->name(); + } + + if (in_array('invoice.status', $this->input['report_keys'])) { + $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id); + } + + if (in_array('invoice.recurring_id', $this->input['report_keys'])) { + $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? ''; + } + + return $entity; } diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 01c8641b0725..48f996fae794 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -48,6 +48,8 @@ class PaymentExport extends BaseExport $this->input['report_keys'] = array_values($this->payment_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Payment::query() ->withTrashed() ->where('company_id', $this->company->id) diff --git a/app/Export/CSV/PurchaseOrderExport.php b/app/Export/CSV/PurchaseOrderExport.php index 88145822b10a..45e8a6adb1b7 100644 --- a/app/Export/CSV/PurchaseOrderExport.php +++ b/app/Export/CSV/PurchaseOrderExport.php @@ -54,7 +54,7 @@ class PurchaseOrderExport extends BaseExport 'po_number' => 'purchase_order.po_number', 'private_notes' => 'purchase_order.private_notes', 'public_notes' => 'purchase_order.public_notes', - 'status' => 'purchase_order.status_id', + 'status' => 'purchase_order.status', 'tax_name1' => 'purchase_order.tax_name1', 'tax_name2' => 'purchase_order.tax_name2', 'tax_name3' => 'purchase_order.tax_name3', @@ -95,6 +95,9 @@ class PurchaseOrderExport extends BaseExport if (count($this->input['report_keys']) == 0) { $this->input['report_keys'] = array_values($this->purchase_order_report_keys); } + + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_vendor_fields, $this->input['report_keys'])); + $query = PurchaseOrder::query() ->withTrashed() ->with('vendor') @@ -181,8 +184,8 @@ class PurchaseOrderExport extends BaseExport $entity['vendor'] = $purchase_order->vendor->present()->name(); } - if (in_array('status_id', $this->input['report_keys'])) { - $entity['status'] = $purchase_order->stringStatus($purchase_order->status_id); + if (in_array('purchase_order.status', $this->input['report_keys'])) { + $entity['purchase_order.status'] = $purchase_order->stringStatus($purchase_order->status_id); } return $entity; diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index 9ebf2991c95b..fb09ea6fa8e9 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -55,6 +55,8 @@ class PurchaseOrderItemExport extends BaseExport $this->input['report_keys'] = array_values($this->mergeItemsKeys('purchase_order_report_keys')); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_vendor_fields, $this->input['report_keys'])); + $query = PurchaseOrder::query() ->withTrashed() ->with('vendor')->where('company_id', $this->company->id) diff --git a/app/Export/CSV/QuoteExport.php b/app/Export/CSV/QuoteExport.php index 8083c0a71e7a..eba6adbcef26 100644 --- a/app/Export/CSV/QuoteExport.php +++ b/app/Export/CSV/QuoteExport.php @@ -56,6 +56,8 @@ class QuoteExport extends BaseExport $this->input['report_keys'] = array_values($this->quote_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Quote::query() ->withTrashed() ->with('client') diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index f85a82dd4c12..48bc25b95e6a 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -57,6 +57,8 @@ class QuoteItemExport extends BaseExport $this->input['report_keys'] = array_values($this->mergeItemsKeys('quote_report_keys')); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Quote::query() ->withTrashed() ->with('client')->where('company_id', $this->company->id) diff --git a/app/Export/CSV/RecurringInvoiceExport.php b/app/Export/CSV/RecurringInvoiceExport.php index d30359510c80..3234b0674337 100644 --- a/app/Export/CSV/RecurringInvoiceExport.php +++ b/app/Export/CSV/RecurringInvoiceExport.php @@ -48,6 +48,8 @@ class RecurringInvoiceExport extends BaseExport $this->input['report_keys'] = array_values($this->recurring_invoice_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = RecurringInvoice::query() ->withTrashed() ->with('client') @@ -135,8 +137,8 @@ class RecurringInvoiceExport extends BaseExport $entity['client'] = $invoice->client->present()->name(); } - if (in_array('status_id', $this->input['report_keys'])) { - $entity['status'] = $invoice->stringStatus($invoice->status_id); + if (in_array('recurring_invoice.status', $this->input['report_keys'])) { + $entity['recurring_invoice.status'] = $invoice->stringStatus($invoice->status_id); } if (in_array('project_id', $this->input['report_keys'])) { diff --git a/app/Export/CSV/TaskExport.php b/app/Export/CSV/TaskExport.php index 87834c6aee4f..b2eb6425c527 100644 --- a/app/Export/CSV/TaskExport.php +++ b/app/Export/CSV/TaskExport.php @@ -60,6 +60,8 @@ class TaskExport extends BaseExport $this->input['report_keys'] = array_values($this->task_report_keys); } + $this->input['report_keys'] = array_merge($this->input['report_keys'], array_diff($this->forced_client_fields, $this->input['report_keys'])); + $query = Task::query() ->withTrashed() ->where('company_id', $this->company->id) diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index c7f400db7420..87c967c883bf 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -20,7 +20,6 @@ use App\Models\Account; use App\Models\Company; use App\Models\Expense; use App\Models\Invoice; -use Tests\MockAccountData; use App\Models\CompanyToken; use App\Models\ClientContact; use App\Export\CSV\TaskExport; @@ -30,8 +29,6 @@ use App\Export\CSV\ProductExport; use App\DataMapper\CompanySettings; use App\Export\CSV\PaymentExport; use App\Factory\CompanyUserFactory; -use App\Factory\InvoiceItemFactory; -use App\Services\Report\ARDetailReport; use Illuminate\Routing\Middleware\ThrottleRequests; /** @@ -262,6 +259,21 @@ class ReportCsvGenerationTest extends TestCase } + public function testForcedInsertionOfMandatoryColumns() + { + $forced = ['client.name']; + + $report_keys = ['invoice.number','client.name', 'invoice.amount']; + $array = array_merge($report_keys, array_diff($forced, $report_keys)); + + $this->assertEquals('client.name', $array[1]); + + $report_keys = ['invoice.number','invoice.amount']; + $array = array_merge($report_keys, array_diff($forced, $report_keys)); + + $this->assertEquals('client.name', $array[2]); + + } public function testVendorCsvGeneration() { @@ -322,7 +334,7 @@ class ReportCsvGenerationTest extends TestCase $data = $export->returnJson(); $this->assertNotNull($data); -// nlog($data); + // nlog($data); // $this->assertEquals(0, $this->traverseJson($data, 'columns.0.identifier')); $this->assertEquals('Vendor Name', $this->traverseJson($data, 'columns.9.display_value')); $this->assertEquals('vendor', $this->traverseJson($data, '0.0.entity')); @@ -1021,6 +1033,44 @@ class ReportCsvGenerationTest extends TestCase 'X-API-TOKEN' => $this->token, ])->post('/api/v1/reports/recurring_invoices', $data)->assertStatus(200); + } + + + public function testRecurringInvoiceColumnsCsvGeneration() + { + + \App\Models\RecurringInvoice::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'client_id' => $this->client->id, + 'amount' => 100, + 'balance' => 50, + 'number' => '1234', + 'status_id' => 2, + 'discount' => 10, + 'po_number' => '1234', + 'public_notes' => 'Public', + 'private_notes' => 'Private', + 'terms' => 'Terms', + 'frequency_id' => 1, + ]); + + $data = [ + 'date_range' => 'all', + 'report_keys' => [], + 'send_email' => false, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/reports/recurring_invoices', $data); + + $csv = $response->streamedContent(); + + $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Recurring Invoice Invoice Number')); + $this->assertEquals('Daily', $this->getFirstValueByColumn($csv, 'Recurring Invoice How Often')); + $this->assertEquals('Active', $this->getFirstValueByColumn($csv, 'Recurring Invoice Status')); } From cdcfcf715ce1edad6c7a6caa13539a2529b65942 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 15:38:45 +1100 Subject: [PATCH 17/51] Fixes for report exports --- app/Export/CSV/BaseExport.php | 5 +++-- app/Export/CSV/PaymentExport.php | 4 ++++ app/Services/Email/Email.php | 5 +++++ tests/Feature/InvoiceEmailTest.php | 2 ++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 0db407e3440f..fd1c1b5a3ef4 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -378,6 +378,7 @@ class BaseExport "custom_value4" => "payment.custom_value4", "user" => "payment.user_id", "assigned_user" => "payment.assigned_user_id", + ]; protected array $expense_report_keys = [ @@ -431,11 +432,11 @@ class BaseExport ]; protected array $forced_client_fields = [ - "name" => "client.name", + "client.name", ]; protected array $forced_vendor_fields = [ - "name" => "vendor.name", + "vendor.name", ]; protected function filterByClients($query) diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index 48f996fae794..ef9a4479bca2 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -67,10 +67,14 @@ class PaymentExport extends BaseExport $headerdisplay = $this->buildHeader(); + nlog($headerdisplay); + $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; })->toArray(); + nlog($header); + $report = $query->cursor() ->map(function ($resource) { $row = $this->buildRow($resource); diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index cebb8b7b6f79..6fada767e07b 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -413,6 +413,11 @@ class Email implements ShouldQueue if ($address_object->address == " ") { return true; } + + if ($address_object->address == "") { + return true; + } + } diff --git a/tests/Feature/InvoiceEmailTest.php b/tests/Feature/InvoiceEmailTest.php index a08b306aa44d..89ef93a1d3b8 100644 --- a/tests/Feature/InvoiceEmailTest.php +++ b/tests/Feature/InvoiceEmailTest.php @@ -32,6 +32,8 @@ class InvoiceEmailTest extends TestCase use DatabaseTransactions; use GeneratesCounter; + public $faker; + protected function setUp() :void { parent::setUp(); From e41228a1151998a0f4f8c826603e59674916e31c Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 15:54:55 +1100 Subject: [PATCH 18/51] Fixes for null/not set client contact passwords --- app/Http/Requests/Request.php | 2 +- tests/Feature/ClientTest.php | 28 ++++++++++++++++++++++++++++ tests/Feature/InvoiceEmailTest.php | 10 +++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 2fb9a589e082..8d7eb2acd05b 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -180,7 +180,7 @@ class Request extends FormRequest } //Filter the client contact password - if it is sent with ***** we should ignore it! - if (isset($contact['password'])) { + if (isset($contact['password']) && is_string($contact['password'])) { if (strlen($contact['password']) == 0) { $input['contacts'][$key]['password'] = ''; } else { diff --git a/tests/Feature/ClientTest.php b/tests/Feature/ClientTest.php index 0244a3e8227b..89b49d945c6a 100644 --- a/tests/Feature/ClientTest.php +++ b/tests/Feature/ClientTest.php @@ -65,6 +65,34 @@ class ClientTest extends TestCase $this->makeTestData(); } + public function testStoreClientFixes() + { + $data = [ + "contacts" => [ + [ + "email" => "tenda@gmail.com", + "first_name" => "Tenda", + "is_primary" => True, + "last_name" => "Bavuma", + "password" => null, + "send_email" => True + ], + ], + "country_id" => "356", + "display_name" => "Tenda Bavuma", + "name" => "Tenda Bavuma", + "shipping_country_id" => "356", + ]; + + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/clients', $data); + + $response->assertStatus(200); + } + public function testClientMergeContactDrop() { diff --git a/tests/Feature/InvoiceEmailTest.php b/tests/Feature/InvoiceEmailTest.php index 89ef93a1d3b8..23ba03236086 100644 --- a/tests/Feature/InvoiceEmailTest.php +++ b/tests/Feature/InvoiceEmailTest.php @@ -33,7 +33,7 @@ class InvoiceEmailTest extends TestCase use GeneratesCounter; public $faker; - + protected function setUp() :void { parent::setUp(); @@ -50,6 +50,14 @@ class InvoiceEmailTest extends TestCase } + public function testInvalidEmailParsing() + { + $email = 'illegal@example.com'; + + $this->assertTrue(strpos($email, '@example.com') !== false); + } + + public function testClientEmailHistory() { $system_log = new SystemLog(); From 25ceba275cfc44534c0236cbccd65b29813ec051 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 17:28:10 +1100 Subject: [PATCH 19/51] Add suppressions for user notifications --- app/Export/CSV/PaymentExport.php | 4 +- app/Jobs/Mail/NinjaMailerObject.php | 4 +- app/Listeners/User/UpdateUserLastLogin.php | 3 +- app/Mail/TemplateEmail.php | 11 +++-- app/Mail/VendorTemplateEmail.php | 20 +++++++-- app/Models/User.php | 4 +- app/Services/Email/Email.php | 5 ++- app/Services/Email/EmailDefaults.php | 4 +- app/Transformers/UserTransformer.php | 1 + ...1415_add_user_notification_suppression.php | 43 +++++++++++++++++++ database/seeders/CurrenciesSeeder.php | 1 + lang/en/texts.php | 5 ++- .../ninja2020/invoices/payment.blade.php | 2 +- 13 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 database/migrations/2023_10_18_061415_add_user_notification_suppression.php diff --git a/app/Export/CSV/PaymentExport.php b/app/Export/CSV/PaymentExport.php index ef9a4479bca2..7f77563f9b92 100644 --- a/app/Export/CSV/PaymentExport.php +++ b/app/Export/CSV/PaymentExport.php @@ -67,14 +67,12 @@ class PaymentExport extends BaseExport $headerdisplay = $this->buildHeader(); - nlog($headerdisplay); - $header = collect($this->input['report_keys'])->map(function ($key, $value) use ($headerdisplay) { return ['identifier' => $key, 'display_value' => $headerdisplay[$value]]; })->toArray(); nlog($header); - + $report = $query->cursor() ->map(function ($resource) { $row = $this->buildRow($resource); diff --git a/app/Jobs/Mail/NinjaMailerObject.php b/app/Jobs/Mail/NinjaMailerObject.php index 1d973cb90275..44eea91cc5e1 100644 --- a/app/Jobs/Mail/NinjaMailerObject.php +++ b/app/Jobs/Mail/NinjaMailerObject.php @@ -32,12 +32,12 @@ class NinjaMailerObject /* Variable for cascading notifications */ public $entity_string = false; - /* @var bool | App\Models\InvoiceInvitation | app\Models\QuoteInvitation | app\Models\CreditInvitation | app\Models\RecurringInvoiceInvitation | app\Models\PurchaseOrderInvitation $invitation*/ + /* @var bool | App\Models\InvoiceInvitation | App\Models\QuoteInvitation | App\Models\CreditInvitation | App\Models\RecurringInvoiceInvitation | App\Models\PurchaseOrderInvitation $invitation*/ public $invitation = false; public $template = false; - /* @var bool | App\Models\Invoice | app\Models\Quote | app\Models\Credit | app\Models\RecurringInvoice | app\Models\PurchaseOrder $invitation*/ + /* @var bool | App\Models\Invoice | App\Models\Quote | App\Models\Credit | App\Models\RecurringInvoice | App\Models\PurchaseOrder | App\Models\Payment $entity*/ public $entity = false; public $reminder_template = ''; diff --git a/app/Listeners/User/UpdateUserLastLogin.php b/app/Listeners/User/UpdateUserLastLogin.php index 9fc383db207f..94e20512eefc 100644 --- a/app/Listeners/User/UpdateUserLastLogin.php +++ b/app/Listeners/User/UpdateUserLastLogin.php @@ -55,7 +55,7 @@ class UpdateUserLastLogin implements ShouldQueue $key = "user_logged_in_{$user->id}{$event->company->db}"; - if ($user->ip != $ip && is_null(Cache::get($key))) { + if ($user->ip != $ip && is_null(Cache::get($key)) && $user->user_logged_in_notification) { $nmo = new NinjaMailerObject; $nmo->mailable = new UserLoggedIn($user, $user->account->companies->first(), $ip); $nmo->company = $user->account->companies->first(); @@ -69,6 +69,7 @@ class UpdateUserLastLogin implements ShouldQueue Cache::put($key, true, 60 * 24); $arr = json_encode(['ip' => $ip]); + $arr = ctrans('texts.new_login_detected'). " {$ip}"; SystemLogger::dispatch( $arr, diff --git a/app/Mail/TemplateEmail.php b/app/Mail/TemplateEmail.php index 984029cefe26..63794936e5c8 100644 --- a/app/Mail/TemplateEmail.php +++ b/app/Mail/TemplateEmail.php @@ -108,12 +108,15 @@ class TemplateEmail extends Mailable if (strlen($settings->bcc_email) > 1) { if (Ninja::isHosted()) { - $bccs = explode(',', str_replace(' ', '', $settings->bcc_email)); - $this->bcc(array_slice($bccs, 0, 2)); - //$this->bcc(reset($bccs)); //remove whitespace if any has been inserted. + + if($company->account->isPaid()) { + $bccs = explode(',', str_replace(' ', '', $settings->bcc_email)); + $this->bcc(array_slice($bccs, 0, 5)); + } + } else { $this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email))); - }//remove whitespace if any has been inserted. + } } $this->subject(str_replace("
", "", $this->build_email->getSubject())) diff --git a/app/Mail/VendorTemplateEmail.php b/app/Mail/VendorTemplateEmail.php index 2ac0987b8045..bdfb818b0091 100644 --- a/app/Mail/VendorTemplateEmail.php +++ b/app/Mail/VendorTemplateEmail.php @@ -11,10 +11,11 @@ namespace App\Mail; +use App\Utils\Ninja; use App\Models\VendorContact; -use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; -use App\Utils\VendorHtmlEngine; use Illuminate\Mail\Mailable; +use App\Utils\VendorHtmlEngine; +use App\Services\PdfMaker\Designs\Utilities\DesignHelpers; class VendorTemplateEmail extends Mailable { @@ -102,8 +103,19 @@ class VendorTemplateEmail extends Mailable $this->from(config('mail.from.address'), $email_from_name); if (strlen($settings->bcc_email) > 1) { - $this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email))); - }//remove whitespace if any has been inserted. + + if (Ninja::isHosted()) { + + if($this->company->account->isPaid()) { + $bccs = explode(',', str_replace(' ', '', $settings->bcc_email)); + $this->bcc(array_slice($bccs, 0, 5)); + } + + } else { + $this->bcc(explode(',', str_replace(' ', '', $settings->bcc_email))); + } + + } $this->subject($this->build_email->getSubject()) ->text('email.template.text', [ diff --git a/app/Models/User.php b/app/Models/User.php index 22e5130d0df1..aec9b0e787d5 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -73,7 +73,8 @@ use Illuminate\Foundation\Auth\User as Authenticatable; * @property int|null $deleted_at * @property string|null $oauth_user_refresh_token * @property string|null $last_confirmed_email_address - * @property int $has_password + * @property bool $has_password + * @property bool $user_logged_in_notification * @property Carbon|null $oauth_user_token_expiry * @property string|null $sms_verification_code * @property bool $verified_phone_number @@ -140,6 +141,7 @@ class User extends Authenticatable implements MustVerifyEmail * */ protected $fillable = [ + 'user_logged_in_notification', 'first_name', 'last_name', 'email', diff --git a/app/Services/Email/Email.php b/app/Services/Email/Email.php index 6fada767e07b..c539d075d445 100644 --- a/app/Services/Email/Email.php +++ b/app/Services/Email/Email.php @@ -140,7 +140,7 @@ class Email implements ShouldQueue $this->email_object->client_id ? $this->email_object->settings = $this->email_object->client->getMergedSettings() : $this->email_object->settings = $this->company->settings; - $this->email_object->client_id ? nlog("client settings") : nlog("company settings "); + // $this->email_object->client_id ? nlog("client settings") : nlog("company settings "); $this->email_object->whitelabel = $this->company->account->isPaid() ? true : false; @@ -418,6 +418,9 @@ class Email implements ShouldQueue return true; } + if($address_object->name == " " || $address_object->name == "") { + return true; + } } diff --git a/app/Services/Email/EmailDefaults.php b/app/Services/Email/EmailDefaults.php index 401060b6c7b4..1f78facaa1e5 100644 --- a/app/Services/Email/EmailDefaults.php +++ b/app/Services/Email/EmailDefaults.php @@ -255,8 +255,8 @@ class EmailDefaults if (strlen($this->email->email_object->settings->bcc_email) > 1) { if (Ninja::isHosted() && $this->email->company->account->isPaid()) { - $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 2); - } elseif (Ninja::isSelfHost()) { + $bccs = array_slice(explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email)), 0, 5); + } else { $bccs = (explode(',', str_replace(' ', '', $this->email->email_object->settings->bcc_email))); } } diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index ffe029fbb0fe..0971c7dc2b02 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -64,6 +64,7 @@ class UserTransformer extends EntityTransformer 'oauth_user_token' => empty($user->oauth_user_token) ? '' : '***', 'verified_phone_number' => (bool) $user->verified_phone_number, 'language_id' => (string) $user->language_id ?? '', + 'user_logged_in_notification' => (bool) $user->user_logged_in_notification, ]; } diff --git a/database/migrations/2023_10_18_061415_add_user_notification_suppression.php b/database/migrations/2023_10_18_061415_add_user_notification_suppression.php new file mode 100644 index 000000000000..4c01624a02ac --- /dev/null +++ b/database/migrations/2023_10_18_061415_add_user_notification_suppression.php @@ -0,0 +1,43 @@ +boolean('user_logged_in_notification')->default(true); + }); + + + $cur = Currency::find(120); + + if(!$cur) { + $cur = new \App\Models\Currency(); + $cur->id = 120; + $cur->code = 'TOP'; + $cur->name = "Tongan Pa'anga"; + $cur->symbol = 'T$'; + $cur->thousand_separator = ','; + $cur->decimal_separator = '.'; + $cur->precision = 2; + $cur->save(); + } + + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // + } +}; diff --git a/database/seeders/CurrenciesSeeder.php b/database/seeders/CurrenciesSeeder.php index b2c8b35af751..e6976622ee86 100644 --- a/database/seeders/CurrenciesSeeder.php +++ b/database/seeders/CurrenciesSeeder.php @@ -142,6 +142,7 @@ class CurrenciesSeeder extends Seeder ['id' => 117, 'name' => 'Gold Troy Ounce', 'code' => 'XAU', 'symbol' => 'XAU', 'precision' => '3', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['id' => 118, 'name' => 'Nicaraguan Córdoba', 'code' => 'NIO', 'symbol' => 'C$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ['id' => 119, 'name' => 'Malagasy ariary', 'code' => 'MGA', 'symbol' => 'Ar', 'precision' => '0', 'thousand_separator' => ',', 'decimal_separator' => '.'], + ['id' => 120, 'name' => "Tongan Pa anga", 'code' => 'TOP', 'symbol' => 'T$', 'precision' => '2', 'thousand_separator' => ',', 'decimal_separator' => '.'], ]; foreach ($currencies as $currency) { diff --git a/lang/en/texts.php b/lang/en/texts.php index 5fe2741906a1..c7bdffb4bd50 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2402,7 +2402,10 @@ $LANG = array( 'currency_libyan_dinar' => 'Libyan Dinar', 'currency_silver_troy_ounce' => 'Silver Troy Ounce', 'currency_gold_troy_ounce' => 'Gold Troy Ounce', - + 'currency_nicaraguan_córdoba' => 'Nicaraguan Córdoba', + 'currency_malagasy_ariary' => 'Malagasy ariary', + "currency_tongan_pa_anga" => "Tongan Pa'anga", + 'review_app_help' => 'We hope you\'re enjoying using the app.
If you\'d consider :link we\'d greatly appreciate it!', 'writing_a_review' => 'writing a review', diff --git a/resources/views/portal/ninja2020/invoices/payment.blade.php b/resources/views/portal/ninja2020/invoices/payment.blade.php index 8d5df833d326..a0d786ff4610 100644 --- a/resources/views/portal/ninja2020/invoices/payment.blade.php +++ b/resources/views/portal/ninja2020/invoices/payment.blade.php @@ -64,7 +64,7 @@ {{ ctrans('texts.public_notes') }}
- {{ $invoice->public_notes }} + {!! html_entity_decode($invoice->public_notes) !!}
@else
From d49909e799e03d72a204d5a3359f2688a22bead3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 17:43:26 +1100 Subject: [PATCH 20/51] Add user login notification suppression --- app/Factory/UserFactory.php | 3 ++- lang/en/texts.php | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Factory/UserFactory.php b/app/Factory/UserFactory.php index d31a0019b2c9..9b15037f625c 100644 --- a/app/Factory/UserFactory.php +++ b/app/Factory/UserFactory.php @@ -27,7 +27,8 @@ class UserFactory $user->last_login = now(); $user->failed_logins = 0; $user->signature = ''; - $user->theme_id = 0; + $user->theme_id = 0; + $user->user_logged_in_notification = true; return $user; } diff --git a/lang/en/texts.php b/lang/en/texts.php index c7bdffb4bd50..48cfe30beb3a 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -2405,7 +2405,7 @@ $LANG = array( 'currency_nicaraguan_córdoba' => 'Nicaraguan Córdoba', 'currency_malagasy_ariary' => 'Malagasy ariary', "currency_tongan_pa_anga" => "Tongan Pa'anga", - + 'review_app_help' => 'We hope you\'re enjoying using the app.
If you\'d consider :link we\'d greatly appreciate it!', 'writing_a_review' => 'writing a review', @@ -5183,6 +5183,8 @@ $LANG = array( 'upcoming' => 'Upcoming', 'client_contact' => 'Client Contact', 'uncategorized' => 'Uncategorized', + 'login_notification' => 'Login Notification', + 'login_notification_help' => 'Sends an email notifying that a login has taken place.' ); return $LANG; From bde9f60aeece6f7b0e7527e783735c5f6d216c85 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 18:42:16 +1100 Subject: [PATCH 21/51] Add group settings to client transformer --- app/Jobs/Mail/NinjaMailerObject.php | 2 +- app/Transformers/ClientTransformer.php | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/Jobs/Mail/NinjaMailerObject.php b/app/Jobs/Mail/NinjaMailerObject.php index 44eea91cc5e1..df33c35c2a11 100644 --- a/app/Jobs/Mail/NinjaMailerObject.php +++ b/app/Jobs/Mail/NinjaMailerObject.php @@ -37,7 +37,7 @@ class NinjaMailerObject public $template = false; - /* @var bool | App\Models\Invoice | App\Models\Quote | App\Models\Credit | App\Models\RecurringInvoice | App\Models\PurchaseOrder | App\Models\Payment $entity*/ + /* @var bool | App\Models\Invoice | App\Models\Quote | App\Models\Credit | App\Models\RecurringInvoice | App\Models\PurchaseOrder | App\Models\Payment $entity */ public $entity = false; public $reminder_template = ''; diff --git a/app/Transformers/ClientTransformer.php b/app/Transformers/ClientTransformer.php index 711c9cfec5ba..4cfb7742b3e4 100644 --- a/app/Transformers/ClientTransformer.php +++ b/app/Transformers/ClientTransformer.php @@ -17,6 +17,7 @@ use App\Models\ClientContact; use App\Models\ClientGatewayToken; use App\Models\CompanyLedger; use App\Models\Document; +use App\Models\GroupSetting; use App\Models\SystemLog; use App\Utils\Traits\MakesHash; use League\Fractal\Resource\Collection; @@ -42,6 +43,7 @@ class ClientTransformer extends EntityTransformer 'activities', 'ledger', 'system_logs', + 'group_settings', ]; /** @@ -96,6 +98,16 @@ class ClientTransformer extends EntityTransformer return $this->includeCollection($client->system_logs, $transformer, SystemLog::class); } + public function includeGroupSettings(Client $client) + { + if (!$client->group_settings) + return null; + + $transformer = new GroupSettingTransformer($this->serializer); + + return $this->includeItem($client->group_settings, $transformer, GroupSetting::class); + } + /** * @param Client $client * From 3ae1d3e694da8dc143a28ff25ac893350a0b26ed Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 20:03:26 +1100 Subject: [PATCH 22/51] v5.7.31 --- VERSION.txt | 2 +- app/Helpers/Epc/EpcQrGenerator.php | 7 ++++++- app/Helpers/SwissQr/SwissQrGenerator.php | 11 ++++++++--- config/ninja.php | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 25285daaef52..557bab7d4518 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.30 \ No newline at end of file +5.7.31 \ No newline at end of file diff --git a/app/Helpers/Epc/EpcQrGenerator.php b/app/Helpers/Epc/EpcQrGenerator.php index 62d61a0a15bb..342c5f775f06 100644 --- a/app/Helpers/Epc/EpcQrGenerator.php +++ b/app/Helpers/Epc/EpcQrGenerator.php @@ -50,7 +50,8 @@ class EpcQrGenerator ); $writer = new Writer($renderer); - $this->validateFields(); + if($this->validateFields()) + return ''; $qr = $writer->writeString($this->encodeMessage(), 'utf-8'); @@ -87,12 +88,16 @@ class EpcQrGenerator private function validateFields() { if (Ninja::isSelfHost() && isset($this->company?->custom_fields?->company2)) { + return true; nlog('The BIC field is not present and _may_ be a required fields for EPC QR codes'); } if (Ninja::isSelfHost() && isset($this->company?->custom_fields?->company1)) { + return true; nlog('The IBAN field is required'); } + + return false; } private function formatMoney($value) diff --git a/app/Helpers/SwissQr/SwissQrGenerator.php b/app/Helpers/SwissQr/SwissQrGenerator.php index abacbb57c056..d8c1ad77304c 100644 --- a/app/Helpers/SwissQr/SwissQrGenerator.php +++ b/app/Helpers/SwissQr/SwissQrGenerator.php @@ -174,9 +174,14 @@ class SwissQrGenerator return $html; } catch (\Exception $e) { - foreach ($qrBill->getViolations() as $key => $violation) { - nlog("qr"); - nlog($violation); + + if(is_iterable($qrBill->getViolations())) { + + foreach ($qrBill->getViolations() as $key => $violation) { + nlog("qr"); + nlog($violation); + } + } nlog($e->getMessage()); diff --git a/config/ninja.php b/config/ninja.php index 7ef548ab7efa..df657179e40b 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.30'), - 'app_tag' => env('APP_TAG','5.7.30'), + 'app_version' => env('APP_VERSION','5.7.31'), + 'app_tag' => env('APP_TAG','5.7.31'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From 546e8277329a6537dc7b0bc4795dfaec201099dd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 18 Oct 2023 22:26:13 +1100 Subject: [PATCH 23/51] Checks for history in system logs --- app/Http/Controllers/EmailHistoryController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/EmailHistoryController.php b/app/Http/Controllers/EmailHistoryController.php index aea6a5e2008f..f29f1a79bd9d 100644 --- a/app/Http/Controllers/EmailHistoryController.php +++ b/app/Http/Controllers/EmailHistoryController.php @@ -32,7 +32,7 @@ class EmailHistoryController extends BaseController ->orderBy('id', 'DESC') ->cursor() ->filter(function ($system_log) { - return ($system_log->log['history'] && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; + return (isset($system_log->log['history']) && isset($system_log->log['history']['events']) && count($system_log->log['history']['events']) >=1) !== false; })->map(function ($system_log) { return $system_log->log['history']; })->values()->all(); From e17af36cf4309526acaf188765361792fbdedccd Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 07:34:40 +1100 Subject: [PATCH 24/51] Improvements for live preview --- app/Http/Controllers/PreviewController.php | 8 +++----- app/Http/Requests/Preview/PreviewInvoiceRequest.php | 8 ++++---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 6777af3f941d..2797bae0eea1 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -61,11 +61,11 @@ class PreviewController extends BaseController } private function purgeCache() - { nlog(auth()->user()->id); + { Cache::pull("preview_".auth()->user()->id); } - public function newLivePreview(PreviewInvoiceRequest $request) + public function live(PreviewInvoiceRequest $request) { $time = time(); @@ -153,8 +153,6 @@ class PreviewController extends BaseController $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); - nlog("merpy derp {$time}"); - return response()->streamDownload(function () use ($pdf) { echo $pdf; }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); @@ -273,7 +271,7 @@ class PreviewController extends BaseController return $response; } - public function live(PreviewInvoiceRequest $request) + public function livex(PreviewInvoiceRequest $request) { // if(Cache::has("preview_".auth()->user()->id)) diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index 50cc0a7fdf73..2188efa37c98 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -92,10 +92,10 @@ class PreviewInvoiceRequest extends Request return $this->stubInvitation(); match($this->entity){ - 'invoice' => $invitation = Invoice::withTrashed()->where('invoice_id', $this->entity_id)->first(), - 'quote' => $invitation = Quote::withTrashed()->where('quote_id', $this->entity_id)->first(), - 'credit' => $invitation = Credit::withTrashed()->where('credit_id', $this->entity_id)->first(), - 'recurring_invoice' => $invitation = RecurringInvoice::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), + 'invoice' => $invitation = InvoiceInvitation::withTrashed()->where('invoice_id', $this->entity_id)->first(), + 'quote' => $invitation = QuoteInvitation::withTrashed()->where('quote_id', $this->entity_id)->first(), + 'credit' => $invitation = CreditInvitation::withTrashed()->where('credit_id', $this->entity_id)->first(), + 'recurring_invoice' => $invitation = RecurringInvoiceInvitation::withTrashed()->where('recurring_invoice_id', $this->entity_id)->first(), }; if($invitation) From ec04f3fd1e71a4601e4961c8c7ef45dff107665e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 08:53:28 +1100 Subject: [PATCH 25/51] Refactor for live previews --- app/Http/Controllers/PreviewController.php | 45 ++++++++++++------- .../Preview/PreviewInvoiceRequest.php | 20 ++++++++- .../RecurringInvoiceInvitationFactory.php | 30 +++++++++++++ 3 files changed, 78 insertions(+), 17 deletions(-) create mode 100644 database/factories/RecurringInvoiceInvitationFactory.php diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 2797bae0eea1..15c5f1612a24 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -68,24 +68,34 @@ class PreviewController extends BaseController public function live(PreviewInvoiceRequest $request) { - $time = time(); - nlog($time); + $start = microtime(true); +nlog("1 ".$start); $invitation = $request->resolveInvitation(); + $client = $request->getClient(); + $settings = $client->getMergedSettings(); App::forgetInstance('translator'); $t = app('translator'); App::setLocale($invitation->contact->preferredLocale()); - $t->replace(Ninja::transformTranslations($invitation->{$request->entity}->client->getMergedSettings())); - - $html = new HtmlEngine($invitation); - $variables = $html->generateLabelsAndValues(); + $t->replace(Ninja::transformTranslations($settings)); +nlog("2 ".microtime(true)); + $entity_prop = str_replace("recurring_", "", $request->entity); $entity_obj = $invitation->{$request->entity}; - // if (! $invitation->{$request->entity}->id ?? true) { - // $invitation->{$request->entity}->service()->fillDefaults(); - // } + if(!$entity_obj->id) { + $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); + $entity_obj->footer = $settings->{$entity_prop."_footer"}; + $entity_obj->terms = $settings->{$entity_prop."_terms"}; + $entity_obj->public_notes = $request->getClient()->public_notes; + $invitation->{$request->entity} = $entity_obj; + } + + $html = new HtmlEngine($invitation); + $html->settings = $settings; + $variables = $html->generateLabelsAndValues(); +nlog("3 ".microtime(true)); $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); @@ -105,18 +115,18 @@ class PreviewController extends BaseController $state = [ 'template' => $template->elements([ - 'client' => $entity_obj->client, + 'client' => $client, 'entity' => $entity_obj, - 'pdf_variables' => (array) $entity_obj->company->settings->pdf_variables, + 'pdf_variables' => (array) $settings->pdf_variables, '$product' => $design->design->product, 'variables' => $variables, ]), 'variables' => $variables, 'options' => [ - 'all_pages_header' => $entity_obj->client->getSetting('all_pages_header'), - 'all_pages_footer' => $entity_obj->client->getSetting('all_pages_footer'), + 'all_pages_header' => $client->getSetting('all_pages_header'), + 'all_pages_footer' => $client->getSetting('all_pages_footer'), ], - 'process_markdown' => $entity_obj->client->company->markdown_enabled, + 'process_markdown' => $client->company->markdown_enabled, ]; $maker = new PdfMaker($state); @@ -125,6 +135,8 @@ class PreviewController extends BaseController ->design($template) ->build(); +nlog("4 ".microtime(true)); + if (request()->query('html') == 'true') { return $maker->getCompiledHTML(); } @@ -153,9 +165,12 @@ class PreviewController extends BaseController $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); +nlog("5 ".microtime(true)); +nlog("total = ".microtime(true)-$start); + return response()->streamDownload(function () use ($pdf) { echo $pdf; - }, 'preview.pdf', ['Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache']); + }, 'preview.pdf', ['Content-Disposition' => 'inline', 'Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache', 'Server-Timing' => microtime(true)-$start]); } diff --git a/app/Http/Requests/Preview/PreviewInvoiceRequest.php b/app/Http/Requests/Preview/PreviewInvoiceRequest.php index 2188efa37c98..57bd0cf22ef0 100644 --- a/app/Http/Requests/Preview/PreviewInvoiceRequest.php +++ b/app/Http/Requests/Preview/PreviewInvoiceRequest.php @@ -15,9 +15,7 @@ use App\Models\Quote; use App\Models\Client; use App\Models\Credit; use App\Models\Invoice; -use App\Libraries\MultiDB; use App\Http\Requests\Request; -use App\Models\CompanyGateway; use App\Models\QuoteInvitation; use App\Utils\Traits\MakesHash; use Illuminate\Validation\Rule; @@ -34,6 +32,8 @@ class PreviewInvoiceRequest extends Request private string $entity_plural = ''; + private ?Client $client = null; + /** * Determine if the user is authorized to make this request. * @@ -104,9 +104,25 @@ class PreviewInvoiceRequest extends Request $invitation = $this->stubInvitation(); } + public function getClient(): ?Client + { + if(!$this->client) + $this->client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + + return $this->client; + } + + public function setClient(Client $client): self + { + $this->client = $client; + + return $this; + } + public function stubInvitation() { $client = Client::query()->with('contacts', 'company', 'user')->withTrashed()->find($this->client_id); + $this->setClient($client); $invitation = false; match($this->entity) { diff --git a/database/factories/RecurringInvoiceInvitationFactory.php b/database/factories/RecurringInvoiceInvitationFactory.php new file mode 100644 index 000000000000..fe7db151f11d --- /dev/null +++ b/database/factories/RecurringInvoiceInvitationFactory.php @@ -0,0 +1,30 @@ + Str::random(40), + ]; + } +} From a93ff3e1e5c2154626ecd2be9ffd4d93f5a35192 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 10:44:19 +1100 Subject: [PATCH 26/51] Clean up --- app/Http/Controllers/PreviewController.php | 141 +++++++++++------- .../Requests/Preview/DesignPreviewRequest.php | 13 +- 2 files changed, 99 insertions(+), 55 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 15c5f1612a24..596024f30368 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -64,26 +64,39 @@ class PreviewController extends BaseController { Cache::pull("preview_".auth()->user()->id); } - - public function live(PreviewInvoiceRequest $request) + + /** + * Refactor - 2023-10-19 + * + * New method does not require Transactions. + * + * @param PreviewInvoiceRequest $request + * @return mixed + */ + public function live(PreviewInvoiceRequest $request): mixed { + if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { + return response()->json(['message' => 'This server cannot handle this request.'], 400); + } + $start = microtime(true); -nlog("1 ".$start); + /** Build models */ $invitation = $request->resolveInvitation(); $client = $request->getClient(); $settings = $client->getMergedSettings(); + /** Set translations */ App::forgetInstance('translator'); $t = app('translator'); App::setLocale($invitation->contact->preferredLocale()); $t->replace(Ninja::transformTranslations($settings)); -nlog("2 ".microtime(true)); $entity_prop = str_replace("recurring_", "", $request->entity); $entity_obj = $invitation->{$request->entity}; + /** Update necessary objecty props */ if(!$entity_obj->id) { $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); $entity_obj->footer = $settings->{$entity_prop."_footer"}; @@ -92,10 +105,10 @@ nlog("2 ".microtime(true)); $invitation->{$request->entity} = $entity_obj; } + /** Generate variables */ $html = new HtmlEngine($invitation); $html->settings = $settings; $variables = $html->generateLabelsAndValues(); -nlog("3 ".microtime(true)); $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); @@ -135,15 +148,15 @@ nlog("3 ".microtime(true)); ->design($template) ->build(); -nlog("4 ".microtime(true)); + /** Generate HTML */ + $html = $maker->getCompiledHTML(true); - if (request()->query('html') == 'true') { - return $maker->getCompiledHTML(); - } + if (request()->query('html') == 'true') + return $html; //if phantom js...... inject here.. if (config('ninja.phantomjs_pdf_generation') || config('ninja.pdf_generator') == 'phantom') { - return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); + return (new Phantom)->convertHtmlToPdf($html); } /** @var \App\Models\User $user */ @@ -152,34 +165,69 @@ nlog("4 ".microtime(true)); if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { - $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - + $pdf = (new NinjaPdf())->build($html); $numbered_pdf = $this->pageNumbering($pdf, $company); - if ($numbered_pdf) { + if ($numbered_pdf) $pdf = $numbered_pdf; - } return $pdf; } - $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + $pdf = (new PreviewPdf($html, $company))->handle(); -nlog("5 ".microtime(true)); -nlog("total = ".microtime(true)-$start); + if (Ninja::isHosted()) { + LightLogs::create(new LivePreview()) + ->increment() + ->batch(); + } + /** Return PDF */ return response()->streamDownload(function () use ($pdf) { echo $pdf; - }, 'preview.pdf', ['Content-Disposition' => 'inline', 'Content-Type' => 'application/pdf','Cache-Control:' => 'no-cache', 'Server-Timing' => microtime(true)-$start]); + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + 'Server-Timing' => microtime(true)-$start + ]); } + + /** + * Returns the mocked PDF for the invoice design preview. + * + * Only used in Settings > Invoice Design as a general overview + * + * @param DesignPreviewRequest $request + * @return mixed + */ + public function design(DesignPreviewRequest $request): mixed + { + $start = microtime(true); + + /** @var \App\Models\User $user */ + $user = auth()->user(); + + /** @var \App\Models\Company $company */ + $company = $user->company(); + + $pdf = (new PdfMock($request->all(), $company))->build()->getPdf(); + + $response = Response::make($pdf, 200); + $response->header('Content-Type', 'application/pdf'); + $response->header('Server-Timing', microtime(true)-$start); + + return $response; + } + /** * Returns a template filled with entity variables. - * + * + * Used in the Custom Designer to preview design changes * @return mixed */ - public function show() { if (request()->has('entity') && @@ -187,6 +235,7 @@ nlog("total = ".microtime(true)-$start); ! empty(request()->input('entity')) && ! empty(request()->input('entity_id')) && request()->has('body')) { + $design_object = json_decode(json_encode(request()->input('design'))); if (! is_object($design_object)) { @@ -243,56 +292,50 @@ nlog("total = ".microtime(true)-$start); return (new Phantom)->convertHtmlToPdf($maker->getCompiledHTML(true)); } - /** @var \App\Models\User $user */ $user = auth()->user(); - $company = $user->company(); if (config('ninja.invoiceninja_hosted_pdf_generation') || config('ninja.pdf_generator') == 'hosted_ninja') { $pdf = (new NinjaPdf())->build($maker->getCompiledHTML(true)); - $numbered_pdf = $this->pageNumbering($pdf, $company); - - if ($numbered_pdf) { + if ($numbered_pdf) $pdf = $numbered_pdf; - } return $pdf; + } - $file_path = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + $pdf = (new PreviewPdf($maker->getCompiledHTML(true), $company))->handle(); + + return response()->streamDownload(function () use ($pdf) { + echo $pdf; + }, 'preview.pdf', [ + 'Content-Disposition' => 'inline', + 'Content-Type' => 'application/pdf', + 'Cache-Control:' => 'no-cache', + ]); + - return response()->download($file_path, basename($file_path), ['Cache-Control:' => 'no-cache'])->deleteFileAfterSend(true); } return $this->blankEntity(); } - public function design(DesignPreviewRequest $request) - { - /** @var \App\Models\User $user */ - $user = auth()->user(); - - /** @var \App\Models\Company $company */ - $company = $user->company(); - - $pdf = (new PdfMock($request->all(), $company))->build()->getPdf(); - - $response = Response::make($pdf, 200); - $response->header('Content-Type', 'application/pdf'); - - return $response; - } - + + + /** + * @deprecated due to usage of transactions + * + * @param mixed $request + * @return void + */ public function livex(PreviewInvoiceRequest $request) { - // if(Cache::has("preview_".auth()->user()->id)) - // return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); - - nlog("should not see a lot of these"); + if(Cache::has("preview_".auth()->user()->id)) + return response()->json(['message' => 'Please wait a few seconds before trying again, this many requests are not good.'], 400); if (Ninja::isHosted() && !in_array($request->getHost(), ['preview.invoicing.co','staging.invoicing.co'])) { return response()->json(['message' => 'This server cannot handle this request.'], 400); @@ -463,7 +506,6 @@ nlog("total = ".microtime(true)-$start); $response->header('Content-Type', 'application/pdf'); $response->header('Server-Timing', microtime(true)-$start); - nlog("returning a response"); $this->purgeCache(); return $response; } @@ -652,7 +694,6 @@ nlog("total = ".microtime(true)-$start); $response = Response::make($file_path, 200); $response->header('Content-Type', 'application/pdf'); - return $response; } } diff --git a/app/Http/Requests/Preview/DesignPreviewRequest.php b/app/Http/Requests/Preview/DesignPreviewRequest.php index f3706c9f5b20..ac2b72fb1ad9 100644 --- a/app/Http/Requests/Preview/DesignPreviewRequest.php +++ b/app/Http/Requests/Preview/DesignPreviewRequest.php @@ -32,11 +32,14 @@ class DesignPreviewRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', Invoice::class) || - auth()->user()->can('create', Quote::class) || - auth()->user()->can('create', RecurringInvoice::class) || - auth()->user()->can('create', Credit::class) || - auth()->user()->can('create', PurchaseOrder::class); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', Invoice::class) || + $user->can('create', Quote::class) || + $user->can('create', RecurringInvoice::class) || + $user->can('create', Credit::class) || + $user->can('create', PurchaseOrder::class); } public function rules() From 9059117f99cc50d2e0cac6d39a04916a3d011ebf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 10:45:34 +1100 Subject: [PATCH 27/51] v5.7.32 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 557bab7d4518..5a3ee7ad5530 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.31 \ No newline at end of file +5.7.32 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index df657179e40b..56b9f06de32f 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.31'), - 'app_tag' => env('APP_TAG','5.7.31'), + 'app_version' => env('APP_VERSION','5.7.32'), + 'app_tag' => env('APP_TAG','5.7.32'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From c67f9cdaf49712cbee97222192ca2d0f99090254 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 10:50:25 +1100 Subject: [PATCH 28/51] Minor fixes --- app/Http/Controllers/PreviewController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 596024f30368..ad68dd371c70 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -95,6 +95,7 @@ class PreviewController extends BaseController $entity_prop = str_replace("recurring_", "", $request->entity); $entity_obj = $invitation->{$request->entity}; + $entity_obj->fill($request->all()); /** Update necessary objecty props */ if(!$entity_obj->id) { From e5bd186b617edf962eb007356cb0270fc84b6f71 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 19 Oct 2023 11:23:13 +1100 Subject: [PATCH 29/51] Fixes for tax calculations --- app/Http/Controllers/ClientController.php | 3 +- app/Jobs/Client/UpdateTaxData.php | 65 +--------------------- app/Services/Tax/Providers/TaxProvider.php | 2 + 3 files changed, 5 insertions(+), 65 deletions(-) diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 7b305b09ff65..0bb9508f7646 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -308,7 +308,8 @@ class ClientController extends BaseController */ public function updateTaxData(PurgeClientRequest $request, Client $client) { - (new UpdateTaxData($client, $client->company))->handle(); + if($client->company->account->isPaid()) + (new UpdateTaxData($client, $client->company))->handle(); return $this->itemResponse($client->fresh()); } diff --git a/app/Jobs/Client/UpdateTaxData.php b/app/Jobs/Client/UpdateTaxData.php index f28781bfe080..89a25bdd865c 100644 --- a/app/Jobs/Client/UpdateTaxData.php +++ b/app/Jobs/Client/UpdateTaxData.php @@ -52,7 +52,7 @@ class UpdateTaxData implements ShouldQueue { MultiDB::setDb($this->company->db); - if($this->company->account->isFreeHostedClient()) + if($this->company->account->isFreeHostedClient() || $this->client->country_id != 840) return; $tax_provider = new \App\Services\Tax\Providers\TaxProvider($this->company, $this->client); @@ -73,69 +73,6 @@ class UpdateTaxData implements ShouldQueue nlog("problem getting tax data => ".$e->getMessage()); } - /* - if(!$tax_provider->updatedTaxStatus() && $this->client->country_id == 840){ - - $calculated_state = false; - - if(array_key_exists($this->client->shipping_state, USStates::get())) { - $calculated_state = $this->client->shipping_state; - $calculated_postal_code = $this->client->shipping_postal_code; - $calculated_city = $this->client->shipping_city; - } - elseif(array_key_exists($this->client->state, USStates::get())){ - $calculated_state = $this->client->state; - $calculated_postal_code = $this->client->postal_code; - $calculated_city = $this->client->city; - } - else { - - try{ - $calculated_state = USStates::getState($this->client->shipping_postal_code); - $calculated_postal_code = $this->client->shipping_postal_code; - $calculated_city = $this->client->shipping_city; - } - catch(\Exception $e){ - nlog("could not calculate state from postal code => {$this->client->shipping_postal_code} or from state {$this->client->shipping_state}"); - } - - if(!$calculated_state) { - try { - $calculated_state = USStates::getState($this->client->postal_code); - $calculated_postal_code = $this->client->postal_code; - $calculated_city = $this->client->city; - } catch(\Exception $e) { - nlog("could not calculate state from postal code => {$this->client->postal_code} or from state {$this->client->state}"); - } - } - - if($this->company->tax_data?->seller_subregion) - $calculated_state = $this->company->tax_data?->seller_subregion; - - nlog("i am trying"); - - if(!$calculated_state) { - nlog("could not determine state"); - return; - } - - } - - $data = [ - 'seller_subregion' => $this->company->tax_data?->seller_subregion ?: '', - 'geoPostalCode' => $this->client->postal_code ?? '', - 'geoCity' => $this->client->city ?? '', - 'geoState' => $calculated_state, - 'taxSales' => $this->company->tax_data->regions->US->subregions?->{$calculated_state}?->taxSales ?? 0, - ]; - - $tax_data = new Response($data); - - $this->client->tax_data = $tax_data; - $this->client->saveQuietly(); - - } - */ } public function middleware() diff --git a/app/Services/Tax/Providers/TaxProvider.php b/app/Services/Tax/Providers/TaxProvider.php index 9598813b80f9..00b540bbc0a4 100644 --- a/app/Services/Tax/Providers/TaxProvider.php +++ b/app/Services/Tax/Providers/TaxProvider.php @@ -225,6 +225,8 @@ class TaxProvider */ private function configureEuTax(): self { + throw new \Exception("No tax region defined for this country"); + $this->provider = EuTax::class; return $this; From 574c800c2ea3eb52876258e3443cd1bb7e67f5fb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 07:46:45 +1100 Subject: [PATCH 30/51] v5.7.33 --- VERSION.txt | 2 +- app/Helpers/Epc/EpcQrGenerator.php | 14 ++++++++------ app/Http/Controllers/PreviewController.php | 9 +++++---- config/ninja.php | 4 ++-- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 5a3ee7ad5530..71c2c6e276ca 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.7.32 \ No newline at end of file +5.7.33 \ No newline at end of file diff --git a/app/Helpers/Epc/EpcQrGenerator.php b/app/Helpers/Epc/EpcQrGenerator.php index 342c5f775f06..965e2dad1f13 100644 --- a/app/Helpers/Epc/EpcQrGenerator.php +++ b/app/Helpers/Epc/EpcQrGenerator.php @@ -15,6 +15,7 @@ use App\Models\Company; use App\Models\Invoice; use App\Models\RecurringInvoice; use App\Utils\Ninja; +use BaconQrCode\Exception\InvalidArgumentException; use BaconQrCode\Renderer\Image\SvgImageBackEnd; use BaconQrCode\Renderer\ImageRenderer; use BaconQrCode\Renderer\RendererStyle\RendererStyle; @@ -50,8 +51,7 @@ class EpcQrGenerator ); $writer = new Writer($renderer); - if($this->validateFields()) - return ''; + $this->validateFields(); $qr = $writer->writeString($this->encodeMessage(), 'utf-8'); @@ -59,10 +59,15 @@ class EpcQrGenerator {$qr}"; } catch(\Throwable $e) { + nlog("EPC QR failure => ".$e->getMessage()); return ''; } catch(\Exception $e) { + nlog("EPC QR failure => ".$e->getMessage()); return ''; - } + } catch( InvalidArgumentException $e) { + nlog("EPC QR failure => ".$e->getMessage()); + return ''; + } } @@ -88,16 +93,13 @@ class EpcQrGenerator private function validateFields() { if (Ninja::isSelfHost() && isset($this->company?->custom_fields?->company2)) { - return true; nlog('The BIC field is not present and _may_ be a required fields for EPC QR codes'); } if (Ninja::isSelfHost() && isset($this->company?->custom_fields?->company1)) { - return true; nlog('The IBAN field is required'); } - return false; } private function formatMoney($value) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index ad68dd371c70..8b5d499d8de8 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -111,11 +111,12 @@ class PreviewController extends BaseController $html->settings = $settings; $variables = $html->generateLabelsAndValues(); - $design = \App\Models\Design::withTrashed()->find($entity_obj->design_id ?? 2); + + $design = \App\Models\Design::query()->withTrashed()->find($entity_obj->design_id ?? 2); /* Catch all in case migration doesn't pass back a valid design */ if (! $design) { - $design = \App\Models\Design::find(2); + $design = \App\Models\Design::query()->find(2); } if ($design->is_custom) { @@ -329,8 +330,8 @@ class PreviewController extends BaseController /** * @deprecated due to usage of transactions * - * @param mixed $request - * @return void + * @param PreviewInvoiceRequest $request + * @return mixed */ public function livex(PreviewInvoiceRequest $request) { diff --git a/config/ninja.php b/config/ninja.php index 56b9f06de32f..d5f584da4e29 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -15,8 +15,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => env('APP_VERSION','5.7.32'), - 'app_tag' => env('APP_TAG','5.7.32'), + 'app_version' => env('APP_VERSION','5.7.33'), + 'app_tag' => env('APP_TAG','5.7.33'), 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''), From aca580780abbaf239fd17f36a474aa69de7c0adf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 09:22:33 +1100 Subject: [PATCH 31/51] Ensure order of Item exports --- app/Export/CSV/InvoiceItemExport.php | 16 +++++++++------- app/Export/CSV/PurchaseOrderItemExport.php | 1 + app/Export/CSV/QuoteItemExport.php | 1 + 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 5ac3c9fb2fbf..ba2dc00da4f5 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -137,16 +137,16 @@ class InvoiceItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') - $key = 'type'; + if($tmp_key == 'type_id') + $tmp_key = 'type'; - if($key == 'tax_id') - $key = 'tax_category'; + if($tmp_key == 'tax_id') + $tmp_key = 'tax_category'; - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; @@ -156,6 +156,8 @@ class InvoiceItemExport extends BaseExport $transformed_items = array_merge($transformed_invoice, $item_array); $entity = $this->decorateAdvancedFields($invoice, $transformed_items); + + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index fb09ea6fa8e9..1ba5db7a12a6 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -147,6 +147,7 @@ class PurchaseOrderItemExport extends BaseExport $transformed_items = array_merge($transformed_purchase_order, $item_array); $entity = $this->decorateAdvancedFields($purchase_order, $transformed_items); + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 48bc25b95e6a..5e327af612d8 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -152,6 +152,7 @@ class QuoteItemExport extends BaseExport $transformed_items = array_merge($transformed_quote, $item_array); $entity = $this->decorateAdvancedFields($quote, $transformed_items); + $entity = array_merge(array_flip(array_values($this->input['report_keys'])), $entity); $this->storage_array[] = $entity; } From e54dec8762f72eff1ef06d9947210be5cc6cf524 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 09:39:25 +1100 Subject: [PATCH 32/51] Fixes for exports --- app/Export/CSV/PurchaseOrderItemExport.php | 14 +++++++------- app/Export/CSV/QuoteItemExport.php | 14 +++++++------- app/Http/Requests/Client/StoreClientRequest.php | 2 -- .../GroupSetting/StoreGroupSettingRequest.php | 10 ++++++++-- tests/Feature/Export/ReportCsvGenerationTest.php | 3 +-- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/Export/CSV/PurchaseOrderItemExport.php b/app/Export/CSV/PurchaseOrderItemExport.php index 1ba5db7a12a6..3738ec5b6dd0 100644 --- a/app/Export/CSV/PurchaseOrderItemExport.php +++ b/app/Export/CSV/PurchaseOrderItemExport.php @@ -127,18 +127,18 @@ class PurchaseOrderItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') { - $keyval = 'type'; + if($tmp_key == 'type_id') { + $tmp_key = 'type'; } - if($key == 'tax_id') { - $keyval = 'tax_category'; + if($tmp_key == 'tax_id') { + $tmp_key = 'tax_category'; } - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; } diff --git a/app/Export/CSV/QuoteItemExport.php b/app/Export/CSV/QuoteItemExport.php index 5e327af612d8..0db08629c664 100644 --- a/app/Export/CSV/QuoteItemExport.php +++ b/app/Export/CSV/QuoteItemExport.php @@ -133,16 +133,16 @@ class QuoteItemExport extends BaseExport if (str_contains($key, "item.")) { - $key = str_replace("item.", "", $key); + $tmp_key = str_replace("item.", "", $key); - if($key == 'type_id') - $key = 'type'; + if($tmp_key == 'type_id') + $tmp_key = 'type'; - if($key == 'tax_id') - $key = 'tax_category'; + if($tmp_key == 'tax_id') + $tmp_key = 'tax_category'; - if (property_exists($item, $key)) { - $item_array[$key] = $item->{$key}; + if (property_exists($item, $tmp_key)) { + $item_array[$key] = $item->{$tmp_key}; } else { $item_array[$key] = ''; diff --git a/app/Http/Requests/Client/StoreClientRequest.php b/app/Http/Requests/Client/StoreClientRequest.php index a8ae0fb5fbae..7262b6ef5d0b 100644 --- a/app/Http/Requests/Client/StoreClientRequest.php +++ b/app/Http/Requests/Client/StoreClientRequest.php @@ -180,8 +180,6 @@ class StoreClientRequest extends Request public function messages() { return [ - // 'unique' => ctrans('validation.unique', ['attribute' => ['email','number']), - //'required' => trans('validation.required', ['attribute' => 'email']), 'contacts.*.email.required' => ctrans('validation.email', ['attribute' => 'email']), 'currency_code' => 'Currency code does not exist', ]; diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index ec629276068a..7099cc6317ae 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -26,12 +26,18 @@ class StoreGroupSettingRequest extends Request */ public function authorize() : bool { - return auth()->user()->can('create', GroupSetting::class) && auth()->user()->account->hasFeature(Account::FEATURE_API); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + return $user->can('create', GroupSetting::class) && $user->account->hasFeature(Account::FEATURE_API); } public function rules() { - $rules['name'] = 'required|unique:group_settings,name,null,null,company_id,'.auth()->user()->companyId(); + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $rules['name'] = 'required|unique:group_settings,name,null,null,company_id,'.$user->companyId(); $rules['settings'] = new ValidClientGroupSettingsRule(); diff --git a/tests/Feature/Export/ReportCsvGenerationTest.php b/tests/Feature/Export/ReportCsvGenerationTest.php index 87c967c883bf..90b12ab96adc 100644 --- a/tests/Feature/Export/ReportCsvGenerationTest.php +++ b/tests/Feature/Export/ReportCsvGenerationTest.php @@ -1171,7 +1171,7 @@ class ReportCsvGenerationTest extends TestCase public function testQuoteItemsCustomColumnsCsvGeneration() { - \App\Models\Quote::factory()->create([ + $q = \App\Models\Quote::factory()->create([ 'user_id' => $this->user->id, 'company_id' => $this->company->id, 'client_id' => $this->client->id, @@ -1217,7 +1217,6 @@ class ReportCsvGenerationTest extends TestCase $csv = $response->streamedContent(); - $this->assertEquals('bob', $this->getFirstValueByColumn($csv, 'Client Name')); $this->assertEquals('1234', $this->getFirstValueByColumn($csv, 'Quote Number')); $this->assertEquals('10', $this->getFirstValueByColumn($csv, 'Item Quantity')); From 979036a27b66745f6672c5d28d4c96c4992466ac Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 10:34:17 +1100 Subject: [PATCH 33/51] Fixes for item exports --- app/Export/CSV/BaseExport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index fd1c1b5a3ef4..84c92439569c 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -1154,9 +1154,9 @@ class BaseExport $clean_row[$key]['entity'] = $report_keys[0]; $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; $clean_row[$key]['hashed_id'] = $report_keys[0] == $entity ? null : $resource->{$report_keys[0]}->hashed_id ?? null; - $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]]; + $clean_row[$key]['value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value]; $clean_row[$key]['identifier'] = $value; - $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$report_keys[1]]; + $clean_row[$key]['display_value'] = isset($row[$column_key]) ? $row[$column_key] : $row[$value]; } From 238d6668e85eb372ade5bdbc37b17acfaa03980e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 16:30:11 +1100 Subject: [PATCH 34/51] Improvements to type casts --- app/DataMapper/Settings/SettingsData.php | 515 ++++++++++++++++++ .../GroupSetting/StoreGroupSettingRequest.php | 55 +- .../UpdateGroupSettingRequest.php | 15 +- tests/Feature/GroupSettingTest.php | 90 ++- tests/Unit/GroupSettingsTest.php | 4 + 5 files changed, 658 insertions(+), 21 deletions(-) create mode 100644 app/DataMapper/Settings/SettingsData.php diff --git a/app/DataMapper/Settings/SettingsData.php b/app/DataMapper/Settings/SettingsData.php new file mode 100644 index 000000000000..b76379806ff6 --- /dev/null +++ b/app/DataMapper/Settings/SettingsData.php @@ -0,0 +1,515 @@ + $value) { + + try{ + settype($object->{$key}, gettype($this->{$key})); + } + catch(\Exception | \Error | \Throwable $e){ + + if(property_exists($this, $key)) + $object->{$key} = $this->{$key}; + else + unset($object->{$key}); + + } + + // if(!property_exists($this, $key)) { + // unset($object->{$key}); + // } + // elseif(is_array($object->{$key}) && gettype($this->{$key} != 'array')){ + // $object->{$key} = $this->{$key}; + // } + // else { + // settype($object->{$key}, gettype($this->{$key})); + // } + } + } + $this->object = $object; + + return $this; + } + + public function toObject(): object + { + return (object)$this->object; + } + + public function toArray(): array + { + return (array)$this->object; + } +} \ No newline at end of file diff --git a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php index 7099cc6317ae..d35e7ac2bc78 100644 --- a/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/StoreGroupSettingRequest.php @@ -11,11 +11,13 @@ namespace App\Http\Requests\GroupSetting; -use App\DataMapper\ClientSettings; -use App\Http\Requests\Request; -use App\Http\ValidationRules\ValidClientGroupSettingsRule; use App\Models\Account; use App\Models\GroupSetting; +use App\Http\Requests\Request; +use App\DataMapper\ClientSettings; +use App\DataMapper\CompanySettings; +use App\DataMapper\Settings\SettingsData; +use App\Http\ValidationRules\ValidClientGroupSettingsRule; class StoreGroupSettingRequest extends Request { @@ -48,15 +50,12 @@ class StoreGroupSettingRequest extends Request { $input = $this->all(); - $group_settings = ClientSettings::defaults(); - - if (array_key_exists('settings', $input) && ! empty($input['settings'])) { - foreach ($input['settings'] as $key => $value) { - $group_settings->{$key} = $value; - } + if (array_key_exists('settings', $input)) { + $input['settings'] = $this->filterSaveableSettings($input['settings']); + } + else { + $input['settings'] = (array)ClientSettings::defaults(); } - - $input['settings'] = (array)$group_settings; $this->replace($input); } @@ -67,4 +66,38 @@ class StoreGroupSettingRequest extends Request 'settings' => 'settings must be a valid json structure', ]; } + + /** + * For the hosted platform, we restrict the feature settings. + * + * This method will trim the company settings object + * down to the free plan setting properties which + * are saveable + * + * @param object $settings + * @return array $settings + */ + private function filterSaveableSettings($settings) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + + $settings_data = new SettingsData(); + $settings = $settings_data->cast($settings)->toObject(); + + if (! $user->account->isFreeHostedClient()) { + return (array)$settings; + } + + $saveable_casts = CompanySettings::$free_plan_casts; + + foreach ($settings as $key => $value) { + if (! array_key_exists($key, $saveable_casts)) { + unset($settings->{$key}); + } + } + + return (array)$settings; + } + } diff --git a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php index 84120cc3475a..a264d74c018d 100644 --- a/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php +++ b/app/Http/Requests/GroupSetting/UpdateGroupSettingRequest.php @@ -11,8 +11,9 @@ namespace App\Http\Requests\GroupSetting; -use App\DataMapper\CompanySettings; use App\Http\Requests\Request; +use App\DataMapper\CompanySettings; +use App\DataMapper\Settings\SettingsData; use App\Http\ValidationRules\ValidClientGroupSettingsRule; class UpdateGroupSettingRequest extends Request @@ -62,10 +63,14 @@ class UpdateGroupSettingRequest extends Request */ private function filterSaveableSettings($settings) { - $account = $this->group_setting->company->account; + /** @var \App\Models\User $user */ + $user = auth()->user(); - if (! $account->isFreeHostedClient()) { - return $settings; + $settings_data = new SettingsData(); + $settings = $settings_data->cast($settings)->toObject(); + + if (! $user->account->isFreeHostedClient()) { + return (array)$settings; } $saveable_casts = CompanySettings::$free_plan_casts; @@ -75,7 +80,7 @@ class UpdateGroupSettingRequest extends Request unset($settings->{$key}); } } - + return (array)$settings; } } diff --git a/tests/Feature/GroupSettingTest.php b/tests/Feature/GroupSettingTest.php index da637d39506c..98bad3daa703 100644 --- a/tests/Feature/GroupSettingTest.php +++ b/tests/Feature/GroupSettingTest.php @@ -11,20 +11,21 @@ namespace Tests\Feature; +use Tests\TestCase; +use Tests\MockAccountData; use App\Utils\Traits\MakesHash; use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Support\Facades\Session; -use Tests\MockAccountData; -use Tests\TestCase; +use App\DataMapper\Settings\SettingsData; +use Spatie\LaravelData\Support\Wrapping\WrapExecutionType; class GroupSettingTest extends TestCase { use MakesHash; - - //use DatabaseTransactions; use MockAccountData; + public $faker; + protected function setUp(): void { parent::setUp(); @@ -38,6 +39,85 @@ class GroupSettingTest extends TestCase $this->makeTestData(); } + public function testCastingMagic() + { + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = ''; + $settings->tax_rate1 = 0; + $s = new SettingsData(); + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + $settings = null; + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = "1"; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("1", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("1", $settings['tax_name1']); + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = []; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("", $settings['tax_name1']); + + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = new \stdClass; + $settings->tax_rate1 = 0; + + $settings = $s->cast($settings)->toObject(); + + $this->assertEquals("", $settings->tax_name1); + + $settings = $s->cast($settings)->toArray(); + $this->assertEquals("", $settings['tax_name1']); + + + + // nlog(json_encode($settings)); + } + + public function testTaxNameInGroupFilters() + { + $settings = new \stdClass; + $settings->currency_id = '1'; + $settings->tax_name1 = ''; + $settings->tax_rate1 = 0; + + $data = [ + 'name' => 'testX', + 'settings' => $settings, + ]; + + $response = $this->withHeaders([ + 'X-API-SECRET' => config('ninja.api_secret'), + 'X-API-TOKEN' => $this->token, + ])->postJson('/api/v1/group_settings', $data); + + $response->assertStatus(200); + + $arr = $response->json(); + + $this->assertEquals("", (string)NULL); + $this->assertNotNull($arr['data']['settings']['tax_name1']); + } + public function testAddGroupFilters() { diff --git a/tests/Unit/GroupSettingsTest.php b/tests/Unit/GroupSettingsTest.php index 6dd5b662eba0..4874753e5fa2 100644 --- a/tests/Unit/GroupSettingsTest.php +++ b/tests/Unit/GroupSettingsTest.php @@ -28,6 +28,10 @@ class GroupSettingsTest extends TestCase use DatabaseTransactions; use ClientGroupSettingsSaver; + public $company_settings; + public $client_settings; + public $settings; + protected function setUp() :void { parent::setUp(); From 33c8c713b1099865a152102a99cf6bcd21692554 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 16:40:53 +1100 Subject: [PATCH 35/51] fixes for deleted payments being displayed on invoices with variable --- app/Services/Payment/DeletePayment.php | 6 +++++- app/Utils/HtmlEngine.php | 6 +++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/Services/Payment/DeletePayment.php b/app/Services/Payment/DeletePayment.php index 1e803faf10cd..8884dbdef622 100644 --- a/app/Services/Payment/DeletePayment.php +++ b/app/Services/Payment/DeletePayment.php @@ -109,7 +109,11 @@ class DeletePayment if ($paymentable_invoice->balance == $paymentable_invoice->amount) { $paymentable_invoice->service()->setStatus(Invoice::STATUS_SENT)->save(); - } else { + } + elseif($paymentable_invoice->balance == 0){ + $paymentable_invoice->service()->setStatus(Invoice::STATUS_PAID)->save(); + } + else { $paymentable_invoice->service()->setStatus(Invoice::STATUS_PARTIAL)->save(); } } else { diff --git a/app/Utils/HtmlEngine.php b/app/Utils/HtmlEngine.php index 538b40b1d311..01d2d7d6fe43 100644 --- a/app/Utils/HtmlEngine.php +++ b/app/Utils/HtmlEngine.php @@ -670,16 +670,16 @@ class HtmlEngine $data['$payment.transaction_reference'] = ['value' => '', 'label' => ctrans('texts.transaction_reference')]; - if ($this->entity_string == 'invoice' && $this->entity->payments()->exists()) { + if ($this->entity_string == 'invoice' && $this->entity->net_payments()->exists()) { $payment_list = '

'; - foreach ($this->entity->payments as $payment) { + foreach ($this->entity->net_payments as $payment) { $payment_list .= ctrans('texts.payment_subject') . ": " . $this->formatDate($payment->date, $this->client->date_format()) . " :: " . Number::formatMoney($payment->amount, $this->client) ." :: ". GatewayType::getAlias($payment->gateway_type_id) . "
"; } $data['$payments'] = ['value' => $payment_list, 'label' => ctrans('texts.payments')]; - $payment = $this->entity->payments()->first(); + $payment = $this->entity->net_payments()->first(); $data['$payment.custom1'] = ['value' => $payment->custom_value1, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment1')]; $data['$payment.custom2'] = ['value' => $payment->custom_value2, 'label' => $this->helpers->makeCustomField($this->company->custom_fields, 'payment2')]; From 28f3de3f284498f4ce3e13bc0d293d2edea30194 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Fri, 20 Oct 2023 21:36:45 +1100 Subject: [PATCH 36/51] Update postmark to only update existing records with message IDs --- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 18dde45e430c..8e4429ca148d 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -56,6 +56,23 @@ class ProcessPostmarkWebhook implements ShouldQueue { } + private function getSystemLog(string $message_id): ?SystemLog + { + return SystemLog::query() + ->where('company_id', $this->invitation->company_id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->whereJsonContains('log', ['MessageID' => $message_id]) + ->orderBy('id','desc') + ->first(); + + } + + private function updateSystemLog(SystemLog $system_log, array $data): void + { + $system_log->log = $data; + $system_log->save(); + } + /** * Execute the job. * @@ -135,6 +152,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl){ + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger( $data, SystemLog::CATEGORY_MAIL, @@ -166,6 +190,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger( $data, SystemLog::CATEGORY_MAIL, @@ -217,6 +248,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_BOUNCED, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); // if(config('ninja.notification.slack')) @@ -263,6 +301,13 @@ class ProcessPostmarkWebhook implements ShouldQueue $data = array_merge($this->request, ['history' => $this->fetchMessage()]); + $sl = $this->getSystemLog($this->request['MessageID']); + + if($sl) { + $this->updateSystemLog($sl, $data); + return; + } + (new SystemLogger($data, SystemLog::CATEGORY_MAIL, SystemLog::EVENT_MAIL_SPAM_COMPLAINT, SystemLog::TYPE_WEBHOOK_RESPONSE, $this->invitation->contact->client, $this->invitation->company))->handle(); if (config('ninja.notification.slack')) { From 2e9a45c3bd852795aa1b31ff15d83cf7870b64b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Beganovi=C4=87?= Date: Fri, 20 Oct 2023 18:57:13 +0200 Subject: [PATCH 37/51] Fixes for client identifier --- app/Export/CSV/ClientExport.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Export/CSV/ClientExport.php b/app/Export/CSV/ClientExport.php index e99434b19863..16f9ca154d70 100644 --- a/app/Export/CSV/ClientExport.php +++ b/app/Export/CSV/ClientExport.php @@ -189,7 +189,7 @@ class ClientExport extends BaseExport $clean_row[$key]['id'] = $report_keys[1] ?? $report_keys[0]; $clean_row[$key]['hashed_id'] = $report_keys[0] == 'client' ? null : $resource->{$report_keys[0]}->hashed_id ?? null; $clean_row[$key]['value'] = $row[$column_key]; - $clean_row[$key]['identifier'] = $key; + $clean_row[$key]['identifier'] = $value; if(in_array($clean_row[$key]['id'], ['paid_to_date', 'balance', 'credit_balance','payment_balance'])) $clean_row[$key]['display_value'] = Number::formatMoney($row[$column_key], $resource); From 4a539988f1c67a109d61e2bcb8c3722f7a734d04 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sat, 21 Oct 2023 07:50:58 +1100 Subject: [PATCH 38/51] Add client.number to maps --- app/Export/CSV/BaseExport.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Export/CSV/BaseExport.php b/app/Export/CSV/BaseExport.php index 84c92439569c..fa6ab80ef33f 100644 --- a/app/Export/CSV/BaseExport.php +++ b/app/Export/CSV/BaseExport.php @@ -88,6 +88,7 @@ class BaseExport protected array $client_report_keys = [ "name" => "client.name", + "number" => "client.number", "user" => "client.user", "assigned_user" => "client.assigned_user", "balance" => "client.balance", From 98848691aa9be8abf4eaa39f4aa7fb410d32e23e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 22 Oct 2023 14:43:51 +1100 Subject: [PATCH 39/51] Add support for Libre Office --- app/Http/Requests/Request.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 8d7eb2acd05b..22231c2393bb 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -20,7 +20,7 @@ class Request extends FormRequest use MakesHash; use RuntimeFormRequest; - protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv|max:100000'; + protected $file_validation = 'sometimes|file|mimes:png,ai,jpeg,tiff,pdf,gif,psd,txt,doc,xls,ppt,xlsx,docx,pptx,webp,xml,zip,csv,ods,odt,odp|max:100000'; /** * Get the validation rules that apply to the request. * From 1b2459eb84578760b5a5d07bbdf4dc30b020a138 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 22 Oct 2023 17:15:56 +1100 Subject: [PATCH 40/51] Add slight delay for postmark webhooks to catch up --- app/Http/Controllers/PostMarkController.php | 2 +- .../portal/ninja2020/components/entity-documents.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/PostMarkController.php b/app/Http/Controllers/PostMarkController.php index 5639d3ccd308..b7a630cb51d1 100644 --- a/app/Http/Controllers/PostMarkController.php +++ b/app/Http/Controllers/PostMarkController.php @@ -62,7 +62,7 @@ class PostMarkController extends BaseController public function webhook(Request $request) { if ($request->header('X-API-SECURITY') && $request->header('X-API-SECURITY') == config('services.postmark.token')) { - ProcessPostmarkWebhook::dispatch($request->all()); + ProcessPostmarkWebhook::dispatch($request->all())->delay(10); return response()->json(['message' => 'Success'], 200); } diff --git a/resources/views/portal/ninja2020/components/entity-documents.blade.php b/resources/views/portal/ninja2020/components/entity-documents.blade.php index ce2c51790418..6cf532ad8865 100644 --- a/resources/views/portal/ninja2020/components/entity-documents.blade.php +++ b/resources/views/portal/ninja2020/components/entity-documents.blade.php @@ -1,4 +1,4 @@ -@if ($entity->documents->count() > 0 || $entity->company->documents->count() > 0 || ($entity->expense && $entity->expense->invoice_documents) || ($entity->task && $entity->company->invoice_task_documents)) +@if ($entity->documents()->where('is_public',1)->count() > 0 || $entity->company->documents()->where('is_public',1)->count() > 0 || ($entity->expense && $entity->expense->invoice_documents) || ($entity->task && $entity->company->invoice_task_documents))
From 40b56a71331a8297a732ec49f82835a7fb0d6062 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Sun, 22 Oct 2023 21:54:17 +1100 Subject: [PATCH 41/51] Logging cleanup --- app/Http/Controllers/Bank/YodleeController.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 34bfa509c6ed..8de11cf308a2 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -166,8 +166,8 @@ class YodleeController extends BaseController public function refreshWebhook(Request $request) { //we should ignore this one - nlog("yodlee refresh"); - nlog($request->all()); + // nlog("yodlee refresh"); + // nlog($request->all()); return response()->json(['message' => 'Success'], 200); @@ -236,8 +236,8 @@ class YodleeController extends BaseController public function refreshUpdatesWebhook(Request $request) { //notifies a user if there are problems with yodlee accessing the data - nlog("update refresh"); - nlog($request->all()); + // nlog("update refresh"); + // nlog($request->all()); return response()->json(['message' => 'Success'], 200); From daf66943d4b26e3bc83e366158e4c9988aa24bb9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 23 Oct 2023 09:48:05 +1100 Subject: [PATCH 42/51] Add reactivate email paths for PostMark --- app/Http/Controllers/ClientController.php | 29 + app/Jobs/PostMark/ProcessPostmarkWebhook.php | 1 + app/Models/Client.php | 5 +- openapi/api-docs.yaml | 13076 +++++++++-------- openapi/paths/clients.yaml | 86 + routes/api.php | 2 + 6 files changed, 6703 insertions(+), 6496 deletions(-) diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index 0bb9508f7646..db90d3ca05c0 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -14,6 +14,7 @@ namespace App\Http\Controllers; use App\Utils\Ninja; use App\Models\Client; use App\Models\Account; +use Postmark\PostmarkClient; use Illuminate\Http\Response; use App\Factory\ClientFactory; use App\Filters\ClientFilters; @@ -36,6 +37,7 @@ use App\Http\Requests\Client\CreateClientRequest; use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UploadClientRequest; use App\Http\Requests\Client\DestroyClientRequest; +use App\Http\Requests\Client\ReactivateClientEmailRequest; /** * Class ClientController. @@ -313,4 +315,31 @@ class ClientController extends BaseController return $this->itemResponse($client->fresh()); } + + /** + * Reactivate a client email + * + * @param ReactivateClientEmailRequest $request + * @param string $bounce_id + * @return \Illuminate\Http\JsonResponse + */ + public function reactivateEmail(ReactivateClientEmailRequest $request, string $bounce_id) + { + + $postmark = new PostmarkClient(config('services.postmark.token')); + + try { + + $response = $postmark->activateBounce((int)$bounce_id); + + return response()->json(['message' => 'Success'], 200); + + } + catch(\Exception $e){ + + return response()->json(['message' => $e->getMessage(), 400]); + + } + + } } diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 8e4429ca148d..35a90f79b340 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -356,6 +356,7 @@ class ProcessPostmarkWebhook implements ShouldQueue $events = collect($messageDetail->messageevents)->map(function ($event) { return [ + 'bounce_id' => $event?->Details?->BounceID ?? '', 'recipient' => $event->Recipient ?? '', 'status' => $event->Type ?? '', 'delivery_message' => $event->Details->DeliveryMessage ?? $event->Details->Summary ?? '', diff --git a/app/Models/Client.php b/app/Models/Client.php index 5abb007ad357..2c06040cc671 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -306,7 +306,10 @@ class Client extends BaseModel implements HasLocalePreference return $this->hasMany(ClientContact::class)->where('is_primary', true); } - public function company() :BelongsTo + /** + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo + */ + public function company() { return $this->belongsTo(Company::class); } diff --git a/openapi/api-docs.yaml b/openapi/api-docs.yaml index d82f89702337..f5d9ac5a9279 100644 --- a/openapi/api-docs.yaml +++ b/openapi/api-docs.yaml @@ -8236,6 +8236,392 @@ paths: default: $ref: "#/components/responses/default" + /api/v1/vendors: + get: + tags: + - vendors + summary: "List vendors" + description: "Lists vendors, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available" + operationId: getVendors + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - $ref: "#/components/parameters/index" + responses: + 200: + description: "A list of vendors" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Vendor' + meta: + type: object + $ref: '#/components/schemas/Meta' + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + post: + tags: + - vendors + summary: "Create vendor" + description: "Adds a vendor to a company" + operationId: storeVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns the saved clivendorent object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/vendors/{id}": + get: + tags: + - vendors + summary: "Show vendor" + description: "Displays a vendor by id" + operationId: showVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The vendor Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the vendor object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + put: + tags: + - vendors + summary: "Update vendor" + description: "Handles the updating of a vendor by id" + operationId: updateVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Vendor Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the vendor object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + delete: + tags: + - vendors + summary: "Delete vendor" + description: "Handles the deletion of a vendor by id" + operationId: deleteVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Vendor Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns a HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/vendors/{id}/edit": + get: + tags: + - vendors + summary: "Edit vendor" + description: "Displays a vendor by id" + operationId: editVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Vendor Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the vendor object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/vendors/create: + get: + tags: + - vendors + summary: "Blank vendor" + description: "Returns a blank vendor with default values" + operationId: getVendorsCreate + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "A blank vendor object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/vendors/bulk: + post: + tags: + - vendors + summary: "Bulk vendor actions" + description: "" + operationId: bulkVendors + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/index" + requestBody: + description: "User credentials" + required: true + content: + application/json: + schema: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned" + type: integer + example: "[0,1,2,3]" + responses: + 200: + description: "The Vendor User response" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/vendors/{id}/upload": + put: + tags: + - vendors + summary: "Uploads a vendor document" + description: "Handles the uploading of a document to a vendor" + operationId: uploadVendor + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Vendor Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Vendor object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Vendor" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" /api/v1/products: get: tags: @@ -8679,21 +9065,59 @@ paths: default: $ref: '#/components/responses/default' - /api/v1/tasks: + /api/v1/recurring_invoices: get: tags: - - tasks - summary: "List tasks" - description: "Lists tasks, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the tasks, these are handled by the TaskFilters class which defines the methods available" - operationId: getTasks + - Recurring Invoices + summary: "List recurring invoices" + description: | + Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. + + operationId: getRecurringInvoices parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/index" + - $ref: "#/components/parameters/client_id" + - $ref: "#/components/parameters/created_at" + - $ref: "#/components/parameters/updated_at" + - $ref: "#/components/parameters/is_deleted" + - $ref: "#/components/parameters/filter_deleted_clients" + - $ref: "#/components/parameters/vendor_id" + - name: filter + in: query + description: | + Searches across a range of columns including: + - custom_value1 + - custom_value2 + - custom_value3 + - custom_value4 + required: false + schema: + type: string + example: ?filter=bob + - name: client_status + in: query + description: | + A comma separated list of invoice status strings. Valid options include: + - all + - active + - paused + - completed + required: false + schema: + type: string + example: ?client_status=active,paused + - name: sort + in: query + description: Returns the list sorted by column in ascending or descending order. + required: false + schema: + type: string + example: id|desc number|desc balance|asc responses: 200: - description: "A list of tasks" + description: "A list of recurring_invoices" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -8709,7 +9133,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Task' + $ref: '#/components/schemas/RecurringInvoice' meta: type: object $ref: '#/components/schemas/Meta' @@ -8727,17 +9151,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - tasks - summary: "Create task" - description: "Adds an task to a company" - operationId: storeTask + - Recurring Invoices + summary: "Create recurring invoice" + description: "Adds a Recurring Invoice to the system" + operationId: storeRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "Returns the saved task object" + description: "Returns the saved RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -8748,7 +9172,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -8761,20 +9185,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/tasks/{id}": + + "/api/v1/recurring_invoices/{id}": get: tags: - - tasks - summary: "Show task" - description: "Displays a task by id" - operationId: showTask + - Recurring Invoices + summary: "Show recurring invoice" + description: "Displays an RecurringInvoice by id" + operationId: showRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -8782,7 +9207,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the task object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -8793,7 +9218,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -8808,17 +9233,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - tasks - summary: "Update task" - description: "Handles the updating of a task by id" - operationId: updateTask + - Recurring Invoices + summary: "Update recurring invoice" + description: "Handles the updating of an RecurringInvoice by id" + operationId: updateRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The task Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -8826,7 +9251,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the task object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -8837,7 +9262,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -8852,17 +9277,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - tasks - summary: "Delete task" - description: "Handles the deletion of a task by id" - operationId: deleteTask + - Recurring Invoices + summary: "Delete recurring invoice" + description: "Handles the deletion of an RecurringInvoice by id" + operationId: deleteRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -8890,20 +9315,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/tasks/{id}/edit": + "/api/v1/recurring_invoices/{id}/edit": get: tags: - - tasks - summary: "Edit task" - description: "Displays a task by id" - operationId: editTask + - Recurring Invoices + summary: "Edit recurring invoice" + description: "Displays an RecurringInvoice by id" + operationId: editRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Task Hashed ID" + description: "The RecurringInvoice Hashed ID" required: true schema: type: string @@ -8911,7 +9336,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the client object" + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -8922,7 +9347,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -8935,439 +9360,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/tasks/create: + + /api/v1/recurring_invoices/create: get: tags: - - tasks - summary: "Blank task" - description: "Returns a blank task with default values" - operationId: getTasksCreate - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "A blank task object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Task" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/tasks/bulk: - post: - tags: - - tasks - summary: "Bulk task actions" - description: "" - operationId: bulkTasks - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/index" - requestBody: - description: "User credentials" - required: true - content: - application/json: - schema: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned" - type: integer - example: "[0,1,2,3]" - responses: - 200: - description: "The Task User response" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Task" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/tasks/{id}/upload": - put: - tags: - - tasks - summary: "Uploads a task document" - description: "Handles the uploading of a document to a task" - operationId: uploadTask - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Task Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the Task object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Task" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/tasks/sort: - post: - tags: - - tasks - summary: "Sort tasks on KanBan" - description: "Sorts tasks after drag and drop on the KanBan." - operationId: sortTasks - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns an Ok, 200 HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/projects: - get: - tags: - - projects - summary: "List projects" - description: "Lists projects" - operationId: getProjects - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/index" - responses: - 200: - description: "A list of projects" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/Project' - meta: - type: object - $ref: '#/components/schemas/Meta' - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - post: - tags: - - projects - summary: "Create project" - description: "Adds an project to a company" - operationId: storeProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns the saved project object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Project" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/projects/{id}": - get: - tags: - - projects - summary: "Show project" - description: "Displays a project by id" - operationId: showProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Project Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the expense object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Project" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - put: - tags: - - projects - summary: "Update project" - description: "Handles the updating of a project by id" - operationId: updateProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Project Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the project object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Project" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - delete: - tags: - - projects - summary: "Delete project" - description: "Handles the deletion of a project by id" - operationId: deleteProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Project Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns a HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/projects/{id}/edit": - get: - tags: - - projects - summary: "Edit project" - description: "Displays a project by id" - operationId: editProject - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The Project Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the project object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Project" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/projects/create: - get: - tags: - - projects - summary: "Blank project" + - Recurring Invoices + summary: "Blank recurring invoice" description: "Returns a blank object with default values" - operationId: getProjectsCreate + operationId: getRecurringInvoicesCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank project object" + description: "A blank RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9378,7 +9385,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -9391,31 +9398,55 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/projects/bulk: + /api/v1/recurring_invoices/bulk: post: tags: - - projects - summary: "Bulk project actions" - description: "" - operationId: bulkProjects + - Recurring Invoices + summary: "Bulk recurring invoice actions" + description: | + There are multiple actions that are available including: + + operationId: bulkRecurringInvoices parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/index" requestBody: - description: "User credentials" + description: "Bulk action details" required: true content: application/json: schema: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned" - type: integer - example: "[0,1,2,3]" + type: object + properties: + action: + type: string + description: | + The action to be performed, options include: + - `start` + Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. + If you do not wish to have the recurring invoice catch up, you should set the next_send_date to the correct date you wish the recurring invoice to commence from. + - `stop` + Stops the recurring invoice. + - `send_now` + Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. + - `restore` + Restores the recurring invoice from an archived or deleted state. + - `archive` + Archives the recurring invoice. The recurring invoice will not fire in this state. + - `delete` + Deletes a recurring invoice. + ids: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned - ['D2J234DFA','D2J234DFA','D2J234DFA']" + type: string + example: + action: start + ids: "['D2J234DFA','D2J234DFA','D2J234DFA']" responses: 200: - description: "The Project User response" + description: "The RecurringInvoice response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9426,7 +9457,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -9439,20 +9470,74 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/projects/{id}/upload": - put: + "/api/v1/recurring_invoices/{id}/{action}": + get: + deprecated: true tags: - - projects - summary: "Uploads a project document" - description: "Handles the uploading of a document to a project" - operationId: uploadProject + - Recurring Invoices + summary: "Custom recurring invoice action" + description: "Performs a custom action on an RecurringInvoice.\n\n The current range of actions are as follows\n - clone_to_RecurringInvoice\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" + operationId: actionRecurringInvoice parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Project Hashed ID" + description: "The RecurringInvoice Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + - name: action + in: path + description: "The action string to be performed" + required: true + schema: + type: string + format: string + example: clone_to_quote + responses: + 200: + description: "Returns the RecurringInvoice object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/RecurringInvoice" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/recurring_invoice/{invitation_key}/download": + get: + tags: + - Recurring Invoices + summary: "Download recurring invoice PDF" + description: "Downloads a specific invoice" + operationId: downloadRecurringInvoice + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: invitation_key + in: path + description: "The Recurring Invoice Invitation Key" required: true schema: type: string @@ -9460,7 +9545,48 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Project object" + description: "Returns the recurring invoice pdf" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/recurring_invoices/{id}/upload": + put: + tags: + - Recurring Invoices + summary: "Add recurring invoice document" + description: "Handles the uploading of a document to a recurring_invoice" + operationId: uploadRecurringInvoice + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The RecurringInvoice Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the RecurringInvoice object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -9471,7 +9597,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + $ref: "#/components/schemas/RecurringInvoice" 401: $ref: "#/components/responses/401" 403: @@ -10141,6 +10267,92 @@ paths: description: 'Server error' default: $ref: '#/components/responses/default' + /api/v1/reactivate_email/{bounce_id}: + post: + tags: + - clients + summary: 'Removes email suppression of a user in the system' + description: 'Emails are suppressed by PostMark, when they receive a Hard bounce / Spam Complaint. This endpoint allows you to remove the suppression and send emails to the user again.' + operationId: reactivateEmail + parameters: + - $ref: '#/components/parameters/X-API-TOKEN' + - $ref: '#/components/parameters/X-Requested-With' + - $ref: '#/components/parameters/include' + - name: bounce_id + in: path + description: 'The postmark Bounce ID reference' + required: true + schema: + type: string + format: string + example: 123243 + responses: + 200: + description: 'Success' + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: '#/components/headers/X-MINIMUM-CLIENT-VERSION' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + 400: + description: 'Postmark exception - generated if the suppression cannot be removed for any reason' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: '#/components/responses/default' + /api/v1/clients/{client}/updateTaxData: + post: + tags: + - clients + summary: 'Update tax data' + description: 'Updates the clients tax data - if their address has changed' + operationId: updateClientTaxData + parameters: + - $ref: '#/components/parameters/X-API-TOKEN' + - $ref: '#/components/parameters/X-Requested-With' + - $ref: '#/components/parameters/include' + - name: client + in: path + description: 'The Client Hashed ID reference' + required: true + schema: + type: string + format: string + example: V2J234DFA + responses: + 200: + description: 'Success' + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: '#/components/headers/X-MINIMUM-CLIENT-VERSION' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + 400: + description: 'Postmark exception - generated if the suppression cannot be removed for any reason' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: '#/components/responses/default' /api/v1/credits: get: tags: @@ -10572,56 +10784,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments: + /api/v1/projects: get: tags: - - payments - summary: "List payments" - description: "Lists payments, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the payments, these are handled by the PaymentFilters class which defines the methods available" - operationId: getPayments + - projects + summary: "List projects" + description: "Lists projects" + operationId: getProjects parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/status" - - $ref: "#/components/parameters/client_id" - - $ref: "#/components/parameters/created_at" - - $ref: "#/components/parameters/updated_at" - - $ref: "#/components/parameters/is_deleted" - - $ref: "#/components/parameters/filter_deleted_clients" - - $ref: "#/components/parameters/vendor_id" - - name: filter - in: query - description: | - Searches across a range of columns including: - - amount - - date - - custom_value1 - - custom_value2 - - custom_value3 - - custom_value4 - required: false - schema: - type: string - example: ?filter=10 - - name: number - in: query - description: | - Search payments by payment number - required: false - schema: - type: string - example: ?number=0001 - - name: sort - in: query - description: Returns the list sorted by column in ascending or descending order. - required: false - schema: - type: string - example: id|desc number|desc balance|asc + - $ref: "#/components/parameters/index" responses: 200: - description: "A list of payments" + description: "A list of projects" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10637,7 +10814,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Payment' + $ref: '#/components/schemas/Project' meta: type: object $ref: '#/components/schemas/Meta' @@ -10655,24 +10832,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - payments - summary: "Create payment" - description: "Adds an Payment to the system" - operationId: storePayment + - projects + summary: "Create project" + description: "Adds an project to a company" + operationId: storeProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - requestBody: - description: "The payment request" - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" responses: 200: - description: "Returns the saved Payment object" + description: "Returns the saved project object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10683,7 +10853,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10696,21 +10866,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - - "/api/v1/payments/{id}": + "/api/v1/projects/{id}": get: tags: - - payments - summary: "Show payment" - description: "Displays an Payment by id" - operationId: showPayment + - projects + summary: "Show project" + description: "Displays a project by id" + operationId: showProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The Project Hashed ID" required: true schema: type: string @@ -10718,7 +10887,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the expense object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10729,7 +10898,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10744,17 +10913,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - payments - summary: "Update payment" - description: "Handles the updating of an Payment by id" - operationId: updatePayment + - projects + summary: "Update project" + description: "Handles the updating of a project by id" + operationId: updateProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The Project Hashed ID" required: true schema: type: string @@ -10762,7 +10931,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the project object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10773,7 +10942,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10788,17 +10957,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - payments - summary: "Delete payment" - description: "Handles the deletion of an Payment by id" - operationId: deletePayment + - projects + summary: "Delete project" + description: "Handles the deletion of a project by id" + operationId: deleteProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The Project Hashed ID" required: true schema: type: string @@ -10826,20 +10995,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/payments/{id}/edit": + "/api/v1/projects/{id}/edit": get: tags: - - payments - summary: "Edit payment" - description: "Displays an Payment by id" - operationId: editPayment + - projects + summary: "Edit project" + description: "Displays a project by id" + operationId: editProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The Project Hashed ID" required: true schema: type: string @@ -10847,7 +11016,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the project object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10858,7 +11027,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10871,20 +11040,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments/create: + /api/v1/projects/create: get: tags: - - payments - summary: "Blank payment" + - projects + summary: "Blank project" description: "Returns a blank object with default values" - operationId: getPaymentsCreate + operationId: getProjectsCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank Payment object" + description: "A blank project object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10895,7 +11064,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -10908,57 +11077,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/payments/refund: + /api/v1/projects/bulk: post: tags: - - payments - summary: "Refund payment" - description: "Adds an Refund to the system" - operationId: storeRefund - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - requestBody: - description: "The refund request" - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" - responses: - 200: - description: "Returns the saved Payment object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/Payment" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/payments/bulk: - post: - tags: - - payments - summary: "Bulk payment actions" + - projects + summary: "Bulk project actions" description: "" - operationId: bulkPayments + operationId: bulkProjects parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -10976,7 +11101,7 @@ paths: example: "[0,1,2,3]" responses: 200: - description: "The Payment response" + description: "The Project User response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -10987,7 +11112,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Project" 401: $ref: "#/components/responses/401" 403: @@ -11000,21 +11125,455 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/payments/{id}/{action}": - get: - deprecated: true + "/api/v1/projects/{id}/upload": + put: tags: - - payments - summary: "Custom payment actions" - description: "Performs a custom action on an Payment.\n\n The current range of actions are as follows\n - clone_to_Payment\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" - operationId: actionPayment + - projects + summary: "Uploads a project document" + description: "Handles the uploading of a document to a project" + operationId: uploadProject parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Payment Hashed ID" + description: "The Project Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Project object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Project" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes: + get: + tags: + - quotes + summary: "List quotes" + description: "Lists quotes, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available" + operationId: getQuotes + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - $ref: "#/components/parameters/status" + - $ref: "#/components/parameters/client_id" + - $ref: "#/components/parameters/created_at" + - $ref: "#/components/parameters/updated_at" + - $ref: "#/components/parameters/is_deleted" + - $ref: "#/components/parameters/filter_deleted_clients" + - $ref: "#/components/parameters/vendor_id" + - name: filter + in: query + description: | + Searches across a range of columns including: + - number + - custom_value1 + - custom_value2 + - custom_value3 + - custom_value4 + required: false + schema: + type: string + example: ?filter=bob + - name: client_status + in: query + description: | + A comma separated list of quote status strings. Valid options include: + - all + - draft + - sent + - approved + - expired + - upcoming + required: false + schema: + type: string + example: ?client_status=paid,unpaid + - name: number + in: query + description: | + Search quote by quote number + required: false + schema: + type: string + example: ?number=Q-001 + - name: sort + in: query + description: Returns the list sorted by column in ascending or descending order. + required: false + schema: + type: string + example: id|desc number|desc balance|asc + responses: + 200: + description: "A list of quotes" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Quote' + meta: + type: object + $ref: '#/components/schemas/Meta' + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + post: + tags: + - quotes + summary: "Create quote" + description: "Adds an Quote to the system" + operationId: storeQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns the saved Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}": + get: + tags: + - quotes + summary: "Show quote" + description: "Displays an Quote by id" + operationId: showQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + put: + tags: + - quotes + summary: "Update quote" + description: "Handles the updating of an Quote by id" + operationId: updateQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + delete: + tags: + - quotes + summary: "Delete quote" + description: "Handles the deletion of an Quote by id" + operationId: deleteQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns a HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/edit": + get: + tags: + - quotes + summary: "Edit quote" + description: "Displays an Quote by id" + operationId: editQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes/create: + get: + tags: + - quotes + summary: "Blank quote" + description: "Returns a blank object with default values" + operationId: getQuotesCreate + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "A blank Quote object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/quotes/bulk: + post: + tags: + - quotes + summary: "Bulk quote actions" + description: "" + operationId: bulkQuotes + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/index" + requestBody: + description: "Hashed ids" + required: true + content: + application/json: + schema: + type: array + items: + description: "Array of hashed IDs to be bulk 'actioned" + type: integer + example: "[0,1,2,3]" + responses: + 200: + description: "The Quote response" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Quote" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/{action}": + get: + deprecated: true + tags: + - quotes + summary: "Performs a custom action on an Quote" + description: "Performs a custom action on an Quote.\n\n The current range of actions are as follows\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - convert\n - convert_to_invoice\n - email" + operationId: actionQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" required: true schema: type: string @@ -11030,7 +11589,7 @@ paths: example: clone_to_quote responses: 200: - description: "Returns the Payment object" + description: "Returns the Quote object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11041,7 +11600,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Quote" 401: $ref: "#/components/responses/401" 403: @@ -11054,21 +11613,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - - "/api/v1/payments/{id}/upload": - put: + "/api/v1/quote/{invitation_key}/download": + get: tags: - - payments - summary: "Upload a payment document" - description: "Handles the uploading of a document to a payment" - operationId: uploadPayment + - quotes + summary: "Download quote PDF" + description: "Downloads a specific quote" + operationId: downloadQuote parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - - name: id + - name: invitation_key in: path - description: "The Payment Hashed ID" + description: "The Quote Invitation Key" required: true schema: type: string @@ -11076,7 +11634,48 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Payment object" + description: "Returns the quote pdf" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + "/api/v1/quotes/{id}/upload": + put: + tags: + - quotes + summary: "Upload a quote document" + description: "Handles the uploading of a document to a quote" + operationId: uploadQuote + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + - name: id + in: path + description: "The Quote Hashed ID" + required: true + schema: + type: string + format: string + example: D2J234DFA + responses: + 200: + description: "Returns the Quote object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -11087,7 +11686,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + $ref: "#/components/schemas/Quote" 401: $ref: "#/components/responses/401" 403: @@ -11813,558 +12412,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/recurring_invoices: + /api/v1/payments: get: tags: - - Recurring Invoices - summary: "List recurring invoices" - description: | - Lists invoices with the option to chain multiple query parameters allowing fine grained filtering of the list. - - operationId: getRecurringInvoices - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - $ref: "#/components/parameters/client_id" - - $ref: "#/components/parameters/created_at" - - $ref: "#/components/parameters/updated_at" - - $ref: "#/components/parameters/is_deleted" - - $ref: "#/components/parameters/filter_deleted_clients" - - $ref: "#/components/parameters/vendor_id" - - name: filter - in: query - description: | - Searches across a range of columns including: - - custom_value1 - - custom_value2 - - custom_value3 - - custom_value4 - required: false - schema: - type: string - example: ?filter=bob - - name: client_status - in: query - description: | - A comma separated list of invoice status strings. Valid options include: - - all - - active - - paused - - completed - required: false - schema: - type: string - example: ?client_status=active,paused - - name: sort - in: query - description: Returns the list sorted by column in ascending or descending order. - required: false - schema: - type: string - example: id|desc number|desc balance|asc - responses: - 200: - description: "A list of recurring_invoices" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/RecurringInvoice' - meta: - type: object - $ref: '#/components/schemas/Meta' - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - post: - tags: - - Recurring Invoices - summary: "Create recurring invoice" - description: "Adds a Recurring Invoice to the system" - operationId: storeRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "Returns the saved RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - - "/api/v1/recurring_invoices/{id}": - get: - tags: - - Recurring Invoices - summary: "Show recurring invoice" - description: "Displays an RecurringInvoice by id" - operationId: showRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - put: - tags: - - Recurring Invoices - summary: "Update recurring invoice" - description: "Handles the updating of an RecurringInvoice by id" - operationId: updateRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - delete: - tags: - - Recurring Invoices - summary: "Delete recurring invoice" - description: "Handles the deletion of an RecurringInvoice by id" - operationId: deleteRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns a HTTP status" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/edit": - get: - tags: - - Recurring Invoices - summary: "Edit recurring invoice" - description: "Displays an RecurringInvoice by id" - operationId: editRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - - /api/v1/recurring_invoices/create: - get: - tags: - - Recurring Invoices - summary: "Blank recurring invoice" - description: "Returns a blank object with default values" - operationId: getRecurringInvoicesCreate - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - responses: - 200: - description: "A blank RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/recurring_invoices/bulk: - post: - tags: - - Recurring Invoices - summary: "Bulk recurring invoice actions" - description: | - There are multiple actions that are available including: - - operationId: bulkRecurringInvoices - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/index" - requestBody: - description: "Bulk action details" - required: true - content: - application/json: - schema: - type: object - properties: - action: - type: string - description: | - The action to be performed, options include: - - `start` - Starts (or restarts) the recurring invoice. **note** if the recurring invoice has been stopped for a long time, it will attempt to catch back up firing a new Invoice every hour per interval that has been missed. - If you do not wish to have the recurring invoice catch up, you should set the next_send_date to the correct date you wish the recurring invoice to commence from. - - `stop` - Stops the recurring invoice. - - `send_now` - Force sends the recurring invoice - this option is only available when the recurring invoice is in a draft state. - - `restore` - Restores the recurring invoice from an archived or deleted state. - - `archive` - Archives the recurring invoice. The recurring invoice will not fire in this state. - - `delete` - Deletes a recurring invoice. - ids: - type: array - items: - description: "Array of hashed IDs to be bulk 'actioned - ['D2J234DFA','D2J234DFA','D2J234DFA']" - type: string - example: - action: start - ids: "['D2J234DFA','D2J234DFA','D2J234DFA']" - responses: - 200: - description: "The RecurringInvoice response" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/{action}": - get: - deprecated: true - tags: - - Recurring Invoices - summary: "Custom recurring invoice action" - description: "Performs a custom action on an RecurringInvoice.\n\n The current range of actions are as follows\n - clone_to_RecurringInvoice\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" - operationId: actionRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - - name: action - in: path - description: "The action string to be performed" - required: true - schema: - type: string - format: string - example: clone_to_quote - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoice/{invitation_key}/download": - get: - tags: - - Recurring Invoices - summary: "Download recurring invoice PDF" - description: "Downloads a specific invoice" - operationId: downloadRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: invitation_key - in: path - description: "The Recurring Invoice Invitation Key" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the recurring invoice pdf" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/recurring_invoices/{id}/upload": - put: - tags: - - Recurring Invoices - summary: "Add recurring invoice document" - description: "Handles the uploading of a document to a recurring_invoice" - operationId: uploadRecurringInvoice - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: id - in: path - description: "The RecurringInvoice Hashed ID" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the RecurringInvoice object" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - content: - application/json: - schema: - $ref: "#/components/schemas/RecurringInvoice" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - /api/v1/quotes: - get: - tags: - - quotes - summary: "List quotes" - description: "Lists quotes, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the quotes, these are handled by the QuoteFilters class which defines the methods available" - operationId: getQuotes + - payments + summary: "List payments" + description: "Lists payments, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the payments, these are handled by the PaymentFilters class which defines the methods available" + operationId: getPayments parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -12380,7 +12434,8 @@ paths: in: query description: | Searches across a range of columns including: - - number + - amount + - date - custom_value1 - custom_value2 - custom_value3 @@ -12388,29 +12443,15 @@ paths: required: false schema: type: string - example: ?filter=bob - - name: client_status - in: query - description: | - A comma separated list of quote status strings. Valid options include: - - all - - draft - - sent - - approved - - expired - - upcoming - required: false - schema: - type: string - example: ?client_status=paid,unpaid + example: ?filter=10 - name: number in: query description: | - Search quote by quote number + Search payments by payment number required: false schema: type: string - example: ?number=Q-001 + example: ?number=0001 - name: sort in: query description: Returns the list sorted by column in ascending or descending order. @@ -12420,7 +12461,7 @@ paths: example: id|desc number|desc balance|asc responses: 200: - description: "A list of quotes" + description: "A list of payments" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12436,7 +12477,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Quote' + $ref: '#/components/schemas/Payment' meta: type: object $ref: '#/components/schemas/Meta' @@ -12454,17 +12495,24 @@ paths: $ref: "#/components/responses/default" post: tags: - - quotes - summary: "Create quote" - description: "Adds an Quote to the system" - operationId: storeQuote + - payments + summary: "Create payment" + description: "Adds an Payment to the system" + operationId: storePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" + requestBody: + description: "The payment request" + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" responses: 200: - description: "Returns the saved Quote object" + description: "Returns the saved Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12475,7 +12523,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12488,20 +12536,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/quotes/{id}": + + "/api/v1/payments/{id}": get: tags: - - quotes - summary: "Show quote" - description: "Displays an Quote by id" - operationId: showQuote + - payments + summary: "Show payment" + description: "Displays an Payment by id" + operationId: showPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12509,7 +12558,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12520,7 +12569,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12535,17 +12584,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - quotes - summary: "Update quote" - description: "Handles the updating of an Quote by id" - operationId: updateQuote + - payments + summary: "Update payment" + description: "Handles the updating of an Payment by id" + operationId: updatePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12553,7 +12602,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12564,7 +12613,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12579,17 +12628,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - quotes - summary: "Delete quote" - description: "Handles the deletion of an Quote by id" - operationId: deleteQuote + - payments + summary: "Delete payment" + description: "Handles the deletion of an Payment by id" + operationId: deletePayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12617,20 +12666,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/edit": + "/api/v1/payments/{id}/edit": get: tags: - - quotes - summary: "Edit quote" - description: "Displays an Quote by id" - operationId: editQuote + - payments + summary: "Edit payment" + description: "Displays an Payment by id" + operationId: editPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12638,7 +12687,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12649,7 +12698,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12662,20 +12711,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/quotes/create: + /api/v1/payments/create: get: tags: - - quotes - summary: "Blank quote" + - payments + summary: "Blank payment" description: "Returns a blank object with default values" - operationId: getQuotesCreate + operationId: getPaymentsCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank Quote object" + description: "A blank Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12686,7 +12735,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12699,19 +12748,63 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/quotes/bulk: + /api/v1/payments/refund: post: tags: - - quotes - summary: "Bulk quote actions" + - payments + summary: "Refund payment" + description: "Adds an Refund to the system" + operationId: storeRefund + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + requestBody: + description: "The refund request" + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" + responses: + 200: + description: "Returns the saved Payment object" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + $ref: "#/components/schemas/Payment" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/payments/bulk: + post: + tags: + - payments + summary: "Bulk payment actions" description: "" - operationId: bulkQuotes + operationId: bulkPayments parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/index" requestBody: - description: "Hashed ids" + description: "User credentials" required: true content: application/json: @@ -12723,7 +12816,7 @@ paths: example: "[0,1,2,3]" responses: 200: - description: "The Quote response" + description: "The Payment response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12734,7 +12827,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12747,21 +12840,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/{action}": + "/api/v1/payments/{id}/{action}": get: deprecated: true tags: - - quotes - summary: "Performs a custom action on an Quote" - description: "Performs a custom action on an Quote.\n\n The current range of actions are as follows\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - convert\n - convert_to_invoice\n - email" - operationId: actionQuote + - payments + summary: "Custom payment actions" + description: "Performs a custom action on an Payment.\n\n The current range of actions are as follows\n - clone_to_Payment\n - clone_to_quote\n - history\n - delivery_note\n - mark_paid\n - download\n - archive\n - delete\n - email" + operationId: actionPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12777,7 +12870,7 @@ paths: example: clone_to_quote responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12788,7 +12881,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -12801,61 +12894,21 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/quote/{invitation_key}/download": - get: - tags: - - quotes - summary: "Download quote PDF" - description: "Downloads a specific quote" - operationId: downloadQuote - parameters: - - $ref: "#/components/parameters/X-API-TOKEN" - - $ref: "#/components/parameters/X-Requested-With" - - $ref: "#/components/parameters/include" - - name: invitation_key - in: path - description: "The Quote Invitation Key" - required: true - schema: - type: string - format: string - example: D2J234DFA - responses: - 200: - description: "Returns the quote pdf" - headers: - X-MINIMUM-CLIENT-VERSION: - $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" - X-RateLimit-Remaining: - $ref: "#/components/headers/X-RateLimit-Remaining" - X-RateLimit-Limit: - $ref: "#/components/headers/X-RateLimit-Limit" - 401: - $ref: "#/components/responses/401" - 403: - $ref: "#/components/responses/403" - 422: - $ref: '#/components/responses/422' - 429: - $ref: '#/components/responses/429' - 5XX: - description: 'Server error' - default: - $ref: "#/components/responses/default" - "/api/v1/quotes/{id}/upload": + + "/api/v1/payments/{id}/upload": put: tags: - - quotes - summary: "Upload a quote document" - description: "Handles the uploading of a document to a quote" - operationId: uploadQuote + - payments + summary: "Upload a payment document" + description: "Handles the uploading of a document to a payment" + operationId: uploadPayment parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Quote Hashed ID" + description: "The Payment Hashed ID" required: true schema: type: string @@ -12863,7 +12916,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Quote object" + description: "Returns the Payment object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -12874,7 +12927,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + $ref: "#/components/schemas/Payment" 401: $ref: "#/components/responses/401" 403: @@ -13366,13 +13419,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/vendors: + /api/v1/tasks: get: tags: - - vendors - summary: "List vendors" - description: "Lists vendors, search and filters allow fine grained lists to be generated.\n\n Query parameters can be added to performed more fine grained filtering of the vendors, these are handled by the VendorFilters class which defines the methods available" - operationId: getVendors + - tasks + summary: "List tasks" + description: "Lists tasks, search and filters allow fine grained lists to be generated.\n *\n * Query parameters can be added to performed more fine grained filtering of the tasks, these are handled by the TaskFilters class which defines the methods available" + operationId: getTasks parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -13380,7 +13433,7 @@ paths: - $ref: "#/components/parameters/index" responses: 200: - description: "A list of vendors" + description: "A list of tasks" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13396,7 +13449,7 @@ paths: data: type: array items: - $ref: '#/components/schemas/Vendor' + $ref: '#/components/schemas/Task' meta: type: object $ref: '#/components/schemas/Meta' @@ -13414,17 +13467,17 @@ paths: $ref: "#/components/responses/default" post: tags: - - vendors - summary: "Create vendor" - description: "Adds a vendor to a company" - operationId: storeVendor + - tasks + summary: "Create task" + description: "Adds an task to a company" + operationId: storeTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "Returns the saved clivendorent object" + description: "Returns the saved task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13435,7 +13488,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13448,20 +13501,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/vendors/{id}": + "/api/v1/tasks/{id}": get: tags: - - vendors - summary: "Show vendor" - description: "Displays a vendor by id" - operationId: showVendor + - tasks + summary: "Show task" + description: "Displays a task by id" + operationId: showTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The vendor Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -13469,7 +13522,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the vendor object" + description: "Returns the task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13480,7 +13533,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13495,17 +13548,17 @@ paths: $ref: "#/components/responses/default" put: tags: - - vendors - summary: "Update vendor" - description: "Handles the updating of a vendor by id" - operationId: updateVendor + - tasks + summary: "Update task" + description: "Handles the updating of a task by id" + operationId: updateTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Vendor Hashed ID" + description: "The task Hashed ID" required: true schema: type: string @@ -13513,7 +13566,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the vendor object" + description: "Returns the task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13524,7 +13577,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13539,17 +13592,17 @@ paths: $ref: "#/components/responses/default" delete: tags: - - vendors - summary: "Delete vendor" - description: "Handles the deletion of a vendor by id" - operationId: deleteVendor + - tasks + summary: "Delete task" + description: "Handles the deletion of a task by id" + operationId: deleteTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Vendor Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -13577,20 +13630,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/vendors/{id}/edit": + "/api/v1/tasks/{id}/edit": get: tags: - - vendors - summary: "Edit vendor" - description: "Displays a vendor by id" - operationId: editVendor + - tasks + summary: "Edit task" + description: "Displays a task by id" + operationId: editTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Vendor Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -13598,7 +13651,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the vendor object" + description: "Returns the client object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13609,7 +13662,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13622,20 +13675,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/vendors/create: + /api/v1/tasks/create: get: tags: - - vendors - summary: "Blank vendor" - description: "Returns a blank vendor with default values" - operationId: getVendorsCreate + - tasks + summary: "Blank task" + description: "Returns a blank task with default values" + operationId: getTasksCreate parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" responses: 200: - description: "A blank vendor object" + description: "A blank task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13646,7 +13699,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13659,13 +13712,13 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - /api/v1/vendors/bulk: + /api/v1/tasks/bulk: post: tags: - - vendors - summary: "Bulk vendor actions" + - tasks + summary: "Bulk task actions" description: "" - operationId: bulkVendors + operationId: bulkTasks parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" @@ -13683,7 +13736,7 @@ paths: example: "[0,1,2,3]" responses: 200: - description: "The Vendor User response" + description: "The Task User response" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13694,7 +13747,7 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" 401: $ref: "#/components/responses/401" 403: @@ -13707,20 +13760,20 @@ paths: description: 'Server error' default: $ref: "#/components/responses/default" - "/api/v1/vendors/{id}/upload": + "/api/v1/tasks/{id}/upload": put: tags: - - vendors - summary: "Uploads a vendor document" - description: "Handles the uploading of a document to a vendor" - operationId: uploadVendor + - tasks + summary: "Uploads a task document" + description: "Handles the uploading of a document to a task" + operationId: uploadTask parameters: - $ref: "#/components/parameters/X-API-TOKEN" - $ref: "#/components/parameters/X-Requested-With" - $ref: "#/components/parameters/include" - name: id in: path - description: "The Vendor Hashed ID" + description: "The Task Hashed ID" required: true schema: type: string @@ -13728,7 +13781,7 @@ paths: example: D2J234DFA responses: 200: - description: "Returns the Vendor object" + description: "Returns the Task object" headers: X-MINIMUM-CLIENT-VERSION: $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" @@ -13739,7 +13792,40 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + $ref: "#/components/schemas/Task" + 401: + $ref: "#/components/responses/401" + 403: + $ref: "#/components/responses/403" + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: "#/components/responses/default" + /api/v1/tasks/sort: + post: + tags: + - tasks + summary: "Sort tasks on KanBan" + description: "Sorts tasks after drag and drop on the KanBan." + operationId: sortTasks + parameters: + - $ref: "#/components/parameters/X-API-TOKEN" + - $ref: "#/components/parameters/X-Requested-With" + - $ref: "#/components/parameters/include" + responses: + 200: + description: "Returns an Ok, 200 HTTP status" + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: "#/components/headers/X-MINIMUM-CLIENT-VERSION" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" 401: $ref: "#/components/responses/401" 403: @@ -13845,43 +13931,43 @@ components: - last: https://invoicing.co/api/v1/invoices?page=1 - prev: null - next: null - 429: - description: 'Rate Limit Exceeded' - content: - application/json: - schema: - $ref: '#/components/schemas/RateLimiterError' - 403: description: 'Authorization error' content: application/json: schema: $ref: '#components/schemas/AuthorizationError' - 422: - description: 'Validation error' - content: - application/json: - schema: - $ref: '#/components/schemas/ValidationError' + default: description: 'Unexpected Error' content: application/json: schema: $ref: '#/components/schemas/Error' - 400: - description: 'Invalid user input' + 429: + description: 'Rate Limit Exceeded' content: application/json: schema: - $ref: '#components/schemas/InvalidInputError' + $ref: '#/components/schemas/RateLimiterError' + 422: + description: 'Validation error' + content: + application/json: + schema: + $ref: '#/components/schemas/ValidationError' 401: description: 'Authentication error' content: application/json: schema: $ref: '#components/schemas/AuthenticationError' + 400: + description: 'Invalid user input' + content: + application/json: + schema: + $ref: '#components/schemas/InvalidInputError' parameters: X-API-SECRET: name: X-API-SECRET @@ -14457,194 +14543,243 @@ components: type: string example: JSON type: object - Product: - type: object + Credit: properties: id: - type: string - description: 'The hashed product ID.' - example: eP01N - readOnly: true - company_id: - type: string - description: 'The hashed ID of the company that owns this product.' - example: eP01N - readOnly: true - user_id: - type: string - description: 'The hashed ID of the user that created this product.' - example: n30m4 - readOnly: true - assigned_user_id: - type: string - description: 'The hashed ID of the user assigned to this product.' - example: pR0j3 - project_id: - type: string - description: 'The hashed ID of the project that this product is associated with.' - example: pR0j3 - vendor_id: - type: string - description: 'The hashed ID of the vendor that this product is associated with.' - example: pR0j3 - custom_value1: - type: string - description: 'Custom value field 1.' - example: 'Custom value 1' - custom_value2: - type: string - description: 'Custom value field 2.' - example: 'Custom value 2' - custom_value3: - type: string - description: 'Custom value field 3.' - example: 'Custom value 3' - custom_value4: - type: string - description: 'Custom value field 4.' - example: 'Custom value 4' - product_key: - type: string - description: 'The product key.' - example: '1234' - notes: - type: string - description: 'Notes about the product.' - example: 'These are some notes about the product.' - cost: - type: number - format: double - description: 'The cost of the product. (Your purchase price for this product)' - example: 10.0 - price: - type: number - format: double - description: 'The price of the product that you are charging.' - example: 20.0 - quantity: - type: number - format: double - description: 'The quantity of the product. (used as a default)' - example: 5.0 - tax_name1: - type: string - description: 'The name of tax 1.' - example: 'Tax 1' - tax_rate1: - type: number - format: double - description: 'The rate of tax 1.' - example: 10.0 - tax_name2: - type: string - description: 'The name of tax 2.' - example: 'Tax 2' - tax_rate2: - type: number - format: double - description: 'The rate of tax 2.' - example: 5.0 - tax_name3: - type: string - description: 'The name of tax 3.' - example: 'Tax 3' - tax_rate3: - type: number - format: double - description: 'The rate of tax 3.' - example: 0.0 - archived_at: - type: integer - format: timestamp - description: 'The timestamp when the product was archived.' - example: '2022-03-18T15:00:00Z' - readOnly: true - created_at: - type: integer - format: timestamp - description: 'The timestamp when the product was created.' - example: '2022-03-18T15:00:00Z' - readOnly: true - updated_at: - description: Timestamp - type: integer - format: timestamp - example: '2022-03-18T12:34:56.789Z' - readOnly: true - is_deleted: - type: boolean - description: 'Boolean flag determining if the product has been deleted' - example: false - readOnly: true - in_stock_quantity: - type: integer - format: int32 - description: The quantity of the product that is currently in stock - default: 0 - stock_notification: - type: boolean - description: Indicates whether stock notifications are enabled for this product - default: true - stock_notification_threshold: - type: integer - format: int32 - description: The minimum quantity threshold for which stock notifications will be triggered - default: 0 - max_quantity: - type: integer - format: int32 - description: The maximum quantity that can be ordered for this product - product_image: - type: string - description: The URL of the product image - format: uri-reference - tax_id: - type: string - default: '1' - description: | - The tax category id for this product.' - - The following constants are available (default = '1') - - ``` - PRODUCT_TYPE_PHYSICAL = '1' - PRODUCT_TYPE_SERVICE = '2' - PRODUCT_TYPE_DIGITAL = '3' - PRODUCT_TYPE_SHIPPING = '4' - PRODUCT_TYPE_EXEMPT = '5' - PRODUCT_TYPE_REDUCED_TAX = '6' - PRODUCT_TYPE_OVERRIDE_TAX = '7' - PRODUCT_TYPE_ZERO_RATED = '8' - PRODUCT_TYPE_REVERSE_TAX = '9' - ``` - example: '1' - - ExpenseCategory: - properties: - id: - description: 'The expense hashed id' + description: "The unique hashed ID of the credit" type: string example: Opnel5aKBz - name: - description: 'The expense category name' - type: string - example: Accounting user_id: - description: 'The user hashed id' + description: "The unique hashed ID of the user associated with the credit" type: string - example: XS987sD - is_deleted: - description: 'Flag determining whether the expense category has been deleted' + example: 1a2b3c4d5e + assigned_user_id: + description: "The unique hashed ID of the assigned user responsible for the credit" + type: string + example: 6f7g8h9i0j + company_id: + description: "The unique hashed ID of the company associated with the credit" + type: string + example: k1l2m3n4o5 + client_id: + description: "The unique hashed ID of the client associated with the credit" + type: string + example: p1q2r3s4t5 + status_id: + description: "The ID representing the current status of the credit" + type: string + example: 3 + invoice_id: + description: "The unique hashed ID of the linked invoice to which the credit is applied" + type: string + example: u1v2w3x4y5 + number: + description: "The unique alphanumeric credit number per company" + type: string + example: QUOTE_101 + po_number: + description: "The purchase order number referred to by the credit" + type: string + example: PO_12345 + terms: + description: "The terms associated with the credit" + type: string + example: "Net 30" + public_notes: + description: "Public notes for the credit" + type: string + example: "Thank you for your business." + private_notes: + description: "Private notes for internal use, not visible to the client" + type: string + example: "Client is requesting a discount." + footer: + description: "The footer text for the credit" + type: string + example: "Footer text goes here." + custom_value1: + description: "Custom value 1 for additional credit information" + type: string + example: "Custom data 1" + custom_value2: + description: "Custom value 2 for additional credit information" + type: string + example: "Custom data 2" + custom_value3: + description: "Custom value 3 for additional credit information" + type: string + example: "Custom data 3" + custom_value4: + description: "Custom value 4 for additional credit information" + type: string + example: "Custom data 4" + tax_name1: + description: "The name of the first tax applied to the credit" + type: string + example: "VAT" + tax_name2: + description: "The name of the second tax applied to the credit" + type: string + example: "GST" + tax_rate1: + description: "The rate of the first tax applied to the credit" + type: number + format: float + example: 10.00 + tax_rate2: + description: "The rate of the second tax applied to the credit" + type: number + format: float + example: 5.00 + tax_name3: + description: "The name of the third tax applied to the credit" + type: string + example: "PST" + tax_rate3: + description: "The rate of the third tax applied to the credit" + type: number + format: float + example: 8.00 + total_taxes: + description: "The total amount of taxes for the credit" + type: number + format: float + example: 23.00 + line_items: + type: array + description: 'An array of objects which define the line items of the credit' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: "The total amount of the credit" + type: number + format: float + example: 100.00 + balance: + description: "The outstanding balance of the credit" + type: number + format: float + example: 50.00 + paid_to_date: + description: "The total amount paid to date for the credit" + type: number + format: float + example: 50.00 + discount: + description: "The discount applied to the credit" + type: number + format: float + example: 10.00 + partial: + description: "The partial amount applied to the credit" + type: number + format: float + example: 20.00 + is_amount_discount: + description: "Indicates whether the discount applied is a fixed amount or a percentage" type: boolean example: true + is_deleted: + description: "Indicates whether the credit has been deleted" + type: boolean + example: false + uses_inclusive_taxes: + description: "Indicates whether the tax rates applied to the credit are inclusive or exclusive" + type: boolean + example: true + date: + description: "The date the credit was issued" + type: string + format: date + example: "1994-07-30" + last_sent_date: + description: "The date the credit was last sent out" + type: string + format: date + example: "1994-07-30" + next_send_date: + description: "The next scheduled date for sending a credit reminder" + type: string + format: date + example: "1994-07-30" + partial_due_date: + description: "The due date for the partial amount of the credit" + type: string + format: date + example: "1994-07-30" + due_date: + description: "The due date for the total amount of the credit" + type: string + format: date + example: "1994-07-30" + settings: + $ref: "#/components/schemas/CompanySettings" + last_viewed: + description: "The timestamp of the last time the credit was viewed" + type: number + format: integer + example: 1434342123 updated_at: - description: 'The updated at timestamp' - type: integer - example: '2' - created_at: - description: 'The created at timestamp' - type: integer - example: '2' + description: "The timestamp of the last time the credit was updated" + type: number + format: integer + example: 1434342123 + archived_at: + description: "The timestamp of the last time the credit was archived" + type: number + format: integer + example: 1434342123 + custom_surcharge1: + description: "First custom surcharge amount" + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + type: object + + GenericBulkAction: + properties: + action: + type: string + example: archive + description: 'The action to perform ie. archive / restore / delete' + ids: + type: array + items: + format: string + type: string + example: 2J234DFA,D2J234DFA,D2J234DFA + description: string array of client hashed ids type: object ProductRequest: type: object @@ -14806,149 +14941,215 @@ components: ``` example: '1' - CompanyToken: + CompanyLedger: properties: - name: - description: 'The token name' - type: string - example: 'Token Name' - token: - description: 'The token value' - type: string - example: AS3df3jUUH765fhfd9KJuidj3JShjA - is_system: - description: 'Determines whether the token is created by the system rather than a user' - type: boolean - example: 'true' - type: object - Document: - properties: - id: - description: 'The document hashed id' + entity_id: + description: 'This field will reference one of the following entity hashed ID payment_id, invoice_id or credit_id' type: string example: AS3df3A - user_id: - description: 'The user hashed id' + notes: + description: 'The notes which reference this entry of the ledger' type: string - example: '' - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: '' - project_id: - description: 'The project associated with this document' - type: string - example: '' - vendor_id: - description: 'The vendor associated with this documents' - type: string - example: '' - name: - description: 'The document name' - type: string - example: Beauty - url: - description: 'The document url' - type: string - example: Beauty - preview: - description: 'The document preview url' - type: string - example: Beauty - type: - description: 'The document type' - type: string - example: Beauty - disk: - description: 'The document disk' - type: string - example: Beauty - hash: - description: 'The document hashed' - type: string - example: Beauty - is_deleted: - description: 'Flag to determine if the document is deleted' - type: boolean - example: true - is_default: - description: 'Flag to determine if the document is a default doc' - type: boolean - example: true - created_at: - description: Timestamp + example: 'Credit note for invoice #3212' + balance: + description: 'The client balance' type: number - format: integer - example: '134341234234' + format: float + example: '10.00' + adjustment: + description: 'The amount the client balance is adjusted by' + type: number + format: float + example: '10.00' updated_at: description: Timestamp type: number format: integer - example: '134341234234' - deleted_at: + example: '1434342123' + created_at: description: Timestamp type: number format: integer - example: '134341234234' + example: '1434342123' type: object - Payment: + Invoice: properties: id: - description: 'The payment hashed id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - invitation_id: - description: 'The invitation hashed id' - type: string - example: Opnel5aKBz - client_contact_id: - description: 'The client contact hashed id' + description: 'The invoice hashed id' type: string example: Opnel5aKBz + readOnly: true user_id: description: 'The user hashed id' type: string example: Opnel5aKBz - type_id: - description: 'The Payment Type ID' - type: string - example: '1' - date: - description: 'The Payment date' - type: string - example: 1-1-2014 - transaction_reference: - description: 'The transaction reference as defined by the payment gateway' - type: string - example: xcsSxcs124asd + readOnly: true assigned_user_id: description: 'The assigned user hashed id' type: string example: Opnel5aKBz - private_notes: - description: 'The private notes of the payment' + company_id: + description: 'The company hashed id' type: string - example: 'The payment was refunded due to error' - is_manual: - description: 'Flags whether the payment was made manually or processed via a gateway' + example: Opnel5aKBz + readOnly: true + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' + type: string + example: '4' + number: + description: 'The invoice number - is a unique alpha numeric number per invoice per company' + type: string + example: INV_101 + po_number: + description: 'The purchase order associated with this invoice' + type: string + example: PO-1234 + terms: + description: 'The invoice terms' + type: string + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' + type: string + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + invitations: + type: array + description: 'An array of objects which define the invitations of the invoice' + items: + $ref: '#/components/schemas/InvoiceInvitation' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' type: boolean example: true is_deleted: - description: 'Defines if the payment has been deleted' + description: 'Defines if the invoice has been deleted' type: boolean example: true - amount: - description: 'The amount of this payment' + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' + type: boolean + example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + last_viewed: + description: Timestamp type: number - example: 10 - refunded: - description: 'The refunded amount of this payment' - type: number - example: 10 + format: integer + example: '1434342123' updated_at: description: Timestamp type: number @@ -14959,32 +15160,833 @@ components: type: number format: integer example: '1434342123' - company_gateway_id: - description: 'The company gateway id' + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + project_id: + description: 'The project associated with this invoice' type: string - example: '3' - paymentables: - $ref: '#/components/schemas/Paymentable' - invoices: - description: '' - type: array - items: - $ref: '#/components/schemas/InvoicePaymentable' - credits: - description: '' - type: array - items: - $ref: '#/components/schemas/CreditPaymentable' - number: - description: 'The payment number - is a unique alpha numeric number per payment per company' + example: Opnel5aKBz + auto_bill_tries: + description: 'The number of times the invoice has attempted to be auto billed' + type: integer + example: '1' + readOnly: true + auto_bill_enabled: + description: 'Boolean flag determining if the invoice is set to auto bill' + type: boolean + example: true + subscription_id: + description: 'The subscription associated with this invoice' type: string - example: PAY_101 + example: Opnel5aKBz + type: object - - BankTransactionRule: + Company: properties: id: - description: 'The bank transaction rules hashed id' + description: "The unique hashed identifier for the company" + type: string + example: WJxbojagwO + size_id: + description: "The unique identifier representing the company's size category" + type: string + example: '2' + industry_id: + description: "The unique identifier representing the company's industry category" + type: string + example: '5' + slack_webhook_url: + description: "The URL for the company's Slack webhook notifications" + type: string + example: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' + google_analytics_key: + description: "The company's Google Analytics tracking ID" + type: string + example: 'UA-123456789-1' + portal_mode: + description: "The mode determining how client-facing URLs are structured (e.g., subdomain, domain, or iframe)" + type: string + example: subdomain + subdomain: + description: "The subdomain prefix for the company's domain (e.g., 'acme' in acme.domain.com)" + type: string + example: acme + portal_domain: + description: "The fully qualified domain used for client-facing URLs" + type: string + example: 'https://subdomain.invoicing.co' + enabled_tax_rates: + description: "The number of tax rates used per entity" + type: integer + example: '2' + fill_products: + description: "A flag determining whether to auto-fill product descriptions based on the product key" + type: boolean + example: true + convert_products: + description: "A flag determining whether to convert products between different types or units" + type: boolean + example: true + update_products: + description: "A flag determining whether to update product descriptions when the description changes" + type: boolean + example: true + show_product_details: + description: "A flag determining whether to display product details in the user interface" + type: boolean + example: true + show_product_cost: + description: "A flag determining whether to display product cost is shown in the user interface" + type: boolean + example: true + custom_fields: + description: "A mapping of custom fields for various objects within the company" + type: object + enable_product_cost: + description: "A flag determining whether to show or hide the product cost field in the user interface" + type: boolean + example: true + enable_product_quantity: + description: "A flag determining whether to show or hide the product quantity field in the user interface" + type: boolean + example: true + default_quantity: + description: "A flag determining whether to use a default quantity for products" + type: boolean + example: true + custom_surcharge_taxes1: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the first custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes2: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the second custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes3: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the third custom surcharge field" + type: boolean + example: true + custom_surcharge_taxes4: + description: "A flag determining whether to apply taxes on custom surcharge amounts for the fourth custom" + logo: + description: "The company logo file in binary format" + type: string + format: binary + example: logo.png + company_key: + description: "The static company key hash used to identify the Company" + readOnly: true + type: string + example: "Vnb14bRlwiFjc5ckte6cfbygTRkn5IMQ" + client_can_register: + description: "A flag determining whether clients can register for the client portal" + type: boolean + example: true + enabled_modules: + type: integer + description: | + Bitmask representation of the modules that are enabled in the application + + ``` + self::ENTITY_RECURRING_INVOICE => 1, + self::ENTITY_CREDIT => 2, + self::ENTITY_QUOTE => 4, + self::ENTITY_TASK => 8, + self::ENTITY_EXPENSE => 16, + self::ENTITY_PROJECT => 32, + self::ENTITY_VENDOR => 64, + self::ENTITY_TICKET => 128, + self::ENTITY_PROPOSAL => 256, + self::ENTITY_RECURRING_EXPENSE => 512, + self::ENTITY_RECURRING_TASK => 1024, + self::ENTITY_RECURRING_QUOTE => 2048, + ``` + + The default per_page value is 20. + + example: 2048 + db: + readOnly: true + type: string + example: 'db-ninja-01' + first_day_of_week: + description: "The first day of the week for the company" + type: string + example: '1' + first_month_of_year: + description: "The first month for the company financial year" + type: string + example: '1' + enabled_item_tax_rates: + description: "The number of tax rates used per item" + type: integer + example: 2 + is_large: + description: "A flag determining whether the company is considered large" + type: boolean + example: true + default_auto_bill: + type: enum + example: 'always' + description: | + A flag determining whether to auto-bill clients by default + + values: + + - always - Always auto bill + - disabled - Never auto bill + - optin - Allow the client to select their auto bill status with the default being disabled + - optout -Allow the client to select their auto bill status with the default being enabled + mark_expenses_invoiceable: + description: "A flag determining whether to mark expenses as invoiceable by default" + type: boolean + example: true + mark_expenses_paid: + description: "A flag determining whether to mark expenses as paid by default" + type: boolean + example: true + invoice_expense_documents: + description: "A flag determining whether to include expense documents on invoices by default" + type: boolean + example: true + auto_start_tasks: + description: "A flag determining whether to auto-start tasks by default" + type: boolean + example: true + invoice_task_timelog: + description: "A flag determining whether to include task time logs on invoices by default" + type: boolean + example: true + invoice_task_documents: + description: "A flag determining whether to include task documents on invoices by default" + type: boolean + example: true + show_tasks_table: + description: "A flag determining whether to show the tasks table on invoices by default" + type: boolean + example: true + is_disabled: + description: "A flag determining whether the company is disabled" + type: boolean + example: true + default_task_is_date_based: + description: "A flag determining whether to default tasks to be date-based" + type: boolean + example: true + enable_product_discount: + description: "A flag determining whether to show or hide the product discount field in the user interface" + type: boolean + example: true + calculate_expense_tax_by_amount: + description: "A flag determining whether to calculate expense taxes by amount" + type: boolean + example: true + expense_inclusive_taxes: + description: "A flag determining whether to include taxes in the expense amount" + type: boolean + example: true + session_timeout: + description: "The session timeout for the company" + type: integer + example: 60 + oauth_password_required: + description: "A flag determining whether to require a password for `dangerous` actions when using OAuth" + type: boolean + example: true + invoice_task_datelog: + description: "A flag determining whether to include task date logs on invoices by default" + type: boolean + example: true + default_password_timeout: + description: "The default password timeout for the company" + type: integer + example: 60 + show_task_end_date: + description: "A flag determining whether to show the task end date on invoices by default" + type: boolean + example: true + markdown_enabled: + description: "A flag determining whether markdown is enabled for the company" + type: boolean + example: true + report_include_drafts: + description: "A flag determining whether to include draft invoices in reports" + type: boolean + example: true + client_registration_fields: + description: "The client registration fields for the company" + type: object + stop_on_unpaid_recurring: + description: "A flag determining whether to stop recurring invoices when they are unpaid" + type: boolean + example: true + use_quote_terms_on_conversion: + description: "A flag determining whether to use quote terms on conversion to an invoice" + type: boolean + example: true + enable_applying_payments: + description: "A flag determining whether to enable applying payments to invoices" + type: boolean + example: true + track_inventory: + description: "A flag determining whether to track inventory for the company" + type: boolean + example: true + inventory_notification_threshold: + description: "The inventory notification threshold for the company" + type: integer + example: 60 + stock_notification: + description: "A flag determining whether to send stock notifications for the company" + type: boolean + example: true + matomo_url: + description: "The Matomo URL for the company" + type: string + example: 'https://matomo.example.com' + matomo_id: + description: "The Matomo ID for the company" + type: string + example: '1' + enabled_expense_tax_rates: + description: "The number of tax rates used per expense" + type: integer + example: 2 + invoice_task_project: + description: "A flag determining whether to include the project on invoices by default" + type: boolean + example: true + report_include_deleted: + description: "A flag determining whether to include deleted invoices in reports" + type: boolean + example: true + invoice_task_lock: + description: "A flag determining whether to lock tasks when invoiced" + type: boolean + example: true + convert_payment_currency: + description: "A flag determining whether to convert the payment currency" + type: boolean + example: true + convert_expense_currency: + description: "A flag determining whether to convert the expense currency" + type: boolean + example: true + notify_vendor_when_paid: + description: "A flag determining whether to notify the vendor when an expense is paid" + type: boolean + example: true + invoice_task_hours: + description: "A flag determining whether to include the task hours on invoices by default" + type: boolean + example: true + calculate_taxes: + description: "A flag determining whether to calculate taxes for the company" + type: boolean + example: true + tax_data: + description: "The tax data for the company" + type: object + e_invoice_certificate: + description: "The e-invoice certificate for the company" + type: string + example: '-----BEGIN CERTIFICATE-----' + e_invoice_certificate_passphrase: + description: "The e-invoice certificate passphrase for the company" + type: string + example: 'secret' + origin_tax_data: + description: "The origin tax data for the company" + type: object + invoice_task_project_header: + description: "A flag determining whether to include the project header on invoices by default" + type: boolean + example: true + invoice_task_item_description: + description: "A flag determining whether to include the item description on invoices by default" + type: boolean + example: true + + settings: + $ref: '#/components/schemas/CompanySettings' + type: object + InvoiceRequest: + required: + - client_id + properties: + id: + description: 'The invoice hashed id' + type: string + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz + readOnly: true + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' + type: string + example: '4' + readOnly: true + number: + description: 'The invoice number - is a unique alpha numeric number per invoice per company' + type: string + example: INV_101 + po_number: + description: 'The purchase order associated with this invoice' + type: string + example: PO-1234 + terms: + description: 'The invoice terms' + type: string + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' + type: string + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + readOnly: + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + invitations: + type: array + description: 'An array of objects which define the invitations of the invoice' + items: + $ref: '#/components/schemas/InvoiceInvitationRequest' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + readOnly: true + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + readOnly: true + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + readOnly: true + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Defines if the invoice has been deleted' + type: boolean + example: true + readOnly: true + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' + type: boolean + example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + readOnly: true + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + readOnly: true + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + last_viewed: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + project_id: + description: 'The project associated with this invoice' + type: string + example: Opnel5aKBz + type: object + SystemLog: + properties: + id: + description: 'The account hashed id' + type: string + example: AS3df3A + company_id: + description: 'The company hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user_id hashed id' + type: string + example: AS3df3A + client_id: + description: 'The client_id hashed id' + type: string + example: AS3df3A + event_id: + description: 'The Log Type ID' + type: integer + example: 1 + category_id: + description: 'The Category Type ID' + type: integer + example: 1 + type_id: + description: 'The Type Type ID' + type: integer + example: 1 + log: + description: 'The json object of the error' + type: object + example: '{''key'':''value''}' + updated_at: + description: Timestamp + type: string + example: '2' + created_at: + description: Timestamp + type: string + example: '2' + type: object + Expense: + properties: + id: + description: 'The expense hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: '' + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: '' + company_id: + description: 'The company hashed id' + type: string + example: '' + client_id: + description: 'The client hashed id' + type: string + example: '' + invoice_id: + description: 'The related invoice hashed id' + type: string + example: '' + bank_id: + description: 'The bank id related to this expense' + type: string + example: '' + invoice_currency_id: + description: 'The currency id of the related invoice' + type: string + example: '' + expense_currency_id: + description: 'The currency id of the expense' + type: string + example: '' + invoice_category_id: + description: 'The invoice category id' + type: string + example: '' + payment_type_id: + description: 'The payment type id' + type: string + example: '' + recurring_expense_id: + description: 'The related recurring expense this expense was created from' + type: string + example: '' + private_notes: + description: 'The private notes of the expense' + type: string + example: '' + public_notes: + description: 'The public notes of the expense' + type: string + example: '' + transaction_reference: + description: 'The transaction references of the expense' + type: string + example: '' + transcation_id: + description: 'The transaction id of the expense' + type: string + example: '' + custom_value1: + description: 'A custom value' + type: string + example: '' + custom_value2: + description: 'A custom value' + type: string + example: '' + custom_value3: + description: 'A custom value' + type: string + example: '' + custom_value4: + description: 'A custom value' + type: string + example: '' + tax_name1: + description: 'Tax name' + type: string + example: '' + tax_name2: + description: 'Tax name' + type: string + example: '' + tax_rate1: + description: 'Tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'Tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'Tax name' + type: string + example: '' + tax_rate3: + description: 'Tax rate' + type: number + format: float + example: '10.00' + amount: + description: 'The total expense amont' + type: number + format: float + example: '10.00' + foreign_amount: + description: 'The total foreign amount of the expense' + type: number + format: float + example: '10.00' + exchange_rate: + description: 'The exchange rate at the time of the expense' + type: number + format: float + example: '0.80' + date: + description: 'The expense date formate Y-m-d' + type: string + example: '2022-12-01' + payment_date: + description: 'The date of payment for the expense, format Y-m-d' + type: string + example: '' + should_be_invoiced: + description: 'Flag whether the expense should be invoiced' + type: boolean + example: true + is_deleted: + description: 'Boolean determining whether the expense has been deleted' + type: boolean + example: true + invoice_documents: + description: 'Passing the expense documents over to the invoice' + type: boolean + example: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + BankTransaction: + properties: + id: + description: 'The bank integration hashed id' type: string example: AS3df3A company_id: @@ -14995,39 +15997,355 @@ components: description: 'The user hashed id' type: string example: AS3df3A - name: - description: 'The name of the transaction' + transaction_id: + description: 'The id of the transaction rule' + type: integer + example: 343434 + amount: + description: 'The transaction amount' + type: number + example: 10 + currency_id: + description: 'The currency ID of the currency' type: string - example: 'Rule 1' - rules: - description: 'A mapped collection of the sub rules for the BankTransactionRule' - type: array - items: - $ref: '#/components/schemas/BTRules' - auto_convert: - description: 'Flags whether the rule converts the transaction automatically' - type: boolean - example: true - matches_on_all: - description: 'Flags whether all subrules are required for the match' - type: boolean - example: true - applies_to: - description: 'Flags whether the rule applies to a CREDIT or DEBIT' + example: '1' + account_type: + description: 'The account type' + type: string + example: creditCard + description: + description: 'The description of the transaction' + type: string + example: 'Potato purchases for kevin' + category_id: + description: 'The category id' + type: integer + example: 1 + category_type: + description: 'The category description' + type: string + example: Expenses + base_type: + description: 'Either CREDIT or DEBIT' type: string example: CREDIT + date: + description: 'The date of the transaction' + type: string + example: '2022-09-01' + bank_account_id: + description: 'The ID number of the bank account' + type: integer + example: '1' + type: object + ExpenseCategory: + properties: + id: + description: 'The expense hashed id' + type: string + example: Opnel5aKBz + name: + description: 'The expense category name' + type: string + example: Accounting + user_id: + description: 'The user hashed id' + type: string + example: XS987sD + is_deleted: + description: 'Flag determining whether the expense category has been deleted' + type: boolean + example: true + updated_at: + description: 'The updated at timestamp' + type: integer + example: '2' + created_at: + description: 'The created at timestamp' + type: integer + example: '2' + type: object + BankIntegration: + properties: + id: + description: 'The bank integration hashed id' + type: string + example: AS3df3A + company_id: + description: 'The company hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: AS3df3A + provider_bank_name: + description: 'The providers bank name' + type: string + example: 'Chase Bank' + bank_account_id: + description: 'The bank account id' + type: integer + example: '1233434' + bank_account_name: + description: 'The name of the account' + type: string + example: 'My Checking Acc' + bank_account_number: + description: 'The account number' + type: string + example: '111 234 2332' + bank_account_status: + description: 'The status of the bank account' + type: string + example: ACTIVE + bank_account_type: + description: 'The type of account' + type: string + example: CREDITCARD + balance: + description: 'The current bank balance if available' + type: number + example: '1000000' + currency: + description: 'iso_3166_3 code' + type: string + example: USD + type: object + Subscription: + properties: + id: + description: Unique identifier for the subscription + type: string + example: Opnel5aKBz + user_id: + description: Unique identifier for the user associated with the subscription + type: string + example: Ua6Rw4pVbS + product_id: + description: Unique identifier for the product associated with the subscription + type: string + example: Pr5Ft7yBmC + company_id: + description: Unique identifier for the company associated with the subscription + type: string + example: Co7Vn3yLmW + recurring_invoice_id: + description: Unique identifier for the recurring invoice associated with the subscription + type: string + example: Ri2Yt8zJkP + is_recurring: + description: Indicates whether the subscription is recurring + type: boolean + example: 'true' + frequency_id: + description: 'integer const representation of the frequency' + type: string + example: '1' + auto_bill: + description: 'enum setting' + type: string + example: always + promo_code: + description: Promotional code applied to the subscription + type: string + example: PROMOCODE4U + promo_discount: + description: Discount percentage or amount applied to the subscription + type: number + example: 10 + is_amount_discount: + description: Indicates whether the discount is a fixed amount + type: boolean + example: 'true' + allow_cancellation: + description: Indicates whether the subscription can be cancelled + type: boolean + example: 'true' + per_seat_enabled: + description: Indicates whether the subscription pricing is per seat + type: boolean + example: 'true' + currency_id: + description: Unique identifier for the currency used in the subscription + type: integer + example: '1' + max_seats_limit: + description: Maximum number of seats allowed for the subscription + type: integer + example: '100' + trial_enabled: + description: Indicates whether the subscription has a trial period + type: boolean + example: 'true' + trial_duration: + description: Duration of the trial period in days + type: integer + example: '14' + allow_query_overrides: + description: Indicates whether query overrides are allowed for the subscription + type: boolean + example: 'true' + allow_plan_changes: + description: Indicates whether plan changes are allowed for the subscription + type: boolean + example: 'true' + refund_period: + description: Number of days within which refunds can be requested + type: integer + example: '30' + webhook_configuration: + description: Webhook configuration for the subscription + type: string + example: 'expand reference for this' + is_deleted: + description: Indicates whether the subscription has been deleted + type: boolean + example: 'false' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + BulkAction: + type: array + items: + type: integer + example: '[0,1,2,3,]' + FillableInvoice: + properties: + assigned_user_id: + description: "The assigned user's hashed ID" + type: string + example: 'a1b2c3d4' client_id: - description: 'The client hashed id' + description: "The client's hashed ID" type: string - example: AS3df3A - vendor_id: - description: 'The vendor hashed id' + example: 'x1y2z3a4' + number: + description: "The unique alphanumeric invoice number for each invoice per company" type: string - example: AS3df3A - category_id: - description: 'The category hashed id' + example: INV_101 + po_number: + description: "The purchase order number associated with the invoice" type: string - example: AS3df3A + example: 'PO12345' + terms: + description: "The terms and conditions for the invoice" + type: string + example: 'Net 30' + public_notes: + description: "Public notes visible to the client on the invoice" + type: string + example: 'Thank you for your business.' + private_notes: + description: "Private notes for internal use only" + type: string + example: 'Client is a slow payer.' + footer: + description: "The footer text displayed on the invoice" + type: string + example: 'Authorized Signature' + custom_value1: + description: "First custom value for additional information" + type: string + example: 'Project ABC' + custom_value2: + description: "Second custom value for additional information" + type: string + example: 'Department XYZ' + custom_value3: + description: "Third custom value for additional information" + type: string + example: 'Location 123' + custom_value4: + description: "Fourth custom value for additional information" + type: string + example: 'Currency USD' + tax_name1: + description: "Name of the first tax applied to the invoice" + type: string + example: 'VAT' + tax_name2: + description: "Name of the second tax applied to the invoice" + type: string + example: 'GST' + tax_rate1: + description: "Rate of the first tax applied to the invoice" + type: number + example: 10.00 + tax_rate2: + description: "Rate of the second tax applied to the invoice" + type: number + example: 5.00 + tax_name3: + description: "Name of the third tax applied to the invoice" + type: string + example: 'PST' + tax_rate3: + description: "Rate of the third tax applied to the invoice" + type: number + example: 8.00 + line_items: + type: array + description: 'An array of objects which define the line items of the invoice' + items: + $ref: '#/components/schemas/InvoiceItem' + discount: + description: "The discount applied to the invoice" + type: number + example: 10.00 + partial: + description: "The partial amount applied to the invoice" + type: number + example: 20.00 + is_amount_discount: + description: "Indicates whether the discount applied is a fixed amount or a percentage" + type: boolean + example: true + uses_inclusive_taxes: + description: "Indicates whether the tax rates applied to the invoice are inclusive or exclusive" + type: boolean + example: true + date: + description: "The date the invoice was issued" + type: string + example: '1994-07-30' + partial_due_date: + description: "The due date for the partial payment" + type: string + example: '1994-08-15' + due_date: + description: "The due date for the invoice" + type: string + example: '1994-08-30' + custom_surcharge1: + description: "First custom surcharge applied to the invoice" + type: number + example: 10.00 + custom_surcharge2: + description: "Second custom surcharge applied to the invoice" + type: number + example: 15.00 + custom_surcharge3: + description: "Third custom surcharge applied to the invoice" + type: number + example: 5.00 + custom_surcharge4: + description: "Fourth custom surcharge applied to the invoice" + type: number + example: 20.00 type: object RecurringQuote: properties: @@ -15255,6 +16573,1813 @@ components: type: boolean example: true type: object + Paymentable: + properties: + id: + description: 'The paymentable hashed id' + type: string + example: AS3df3A + invoice_id: + description: 'The invoice hashed id' + type: string + example: AS3df3A + credit_id: + description: 'The credit hashed id' + type: string + example: AS3df3A + refunded: + description: 'The amount that has been refunded for this payment' + type: number + format: float + example: '10.00' + amount: + description: 'The amount that has been applied to the payment' + type: number + format: float + example: '10.00' + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + created_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + Meta: + properties: + pagination: + $ref: '#/components/schemas/Pagination' + Pagination: + type: object + properties: + total: + type: integer + description: 'The total number of items' + example: 1 + readOnly: true + count: + type: integer + description: 'The number of items per page' + example: 1 + readOnly: true + per_page: + type: integer + description: 'The number of items per page' + example: 1 + readOnly: true + current_page: + type: integer + description: 'The current page number' + example: 1 + readOnly: true + total_pages: + type: integer + description: 'The total number of pages' + example: 1 + readOnly: true + links: + type: array + description: 'The pagination links' + readOnly: true + Project: + type: object + properties: + id: + description: 'The project hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + assigned_user_id: + description: The assigned user identifier associated with the project + type: string + example: Opnel5aKBz + client_id: + type: string + example: Opnel5aKBz + description: The client identifier associated with the project + name: + type: string + description: The name of the project + example: 'New Project' + task_rate: + type: number + format: float + example: 10 + description: The default rate per task for the project + due_date: + type: string + format: date + example: '2019-01-01' + description: The due date for the project + private_notes: + type: string + description: Private notes associated with the project + budgeted_hours: + type: number + format: float + description: The number of budgeted hours for the project + custom_value1: + type: string + description: Custom value field 1 + custom_value2: + type: string + description: Custom value field 2 + custom_value3: + type: string + description: Custom value field 3 + custom_value4: + type: string + description: Custom value field 4 + created_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the project creation + updated_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the last project update + archived_at: + type: number + format: integer + example: 134341234234 + description: The timestamp of the project deletion + public_notes: + type: string + description: Public notes associated with the project + is_deleted: + type: boolean + description: A flag indicating if the project is deleted + number: + type: string + description: The project number + color: + type: string + description: The color associated with the project + required: + - id + - user_id + - company_id + - name + - task_rate + - budgeted_hours + - is_deleted + - color + + Client: + properties: + id: + description: 'The unique identifier of the client' + type: string + example: Opnel5aKBz + readOnly: true + contacts: + type: array + items: + $ref: '#/components/schemas/ClientContact' + user_id: + description: 'The unique identifier of the user who created the client' + type: string + example: Ua6Rw4pVbS + readOnly: true + assigned_user_id: + description: 'The unique identifier of the user who has been assigned the client' + type: string + example: Ua6Rw4pVbS + company_id: + description: 'The unique identifier of the company the client belongs to' + type: string + example: Co7Vn3yLmW + readOnly: true + name: + description: 'The name of the client company or organization' + type: string + example: "Jim's Housekeeping" + website: + description: 'The website URL of the client company or organization' + type: string + example: 'https://www.jims-housekeeping.com' + private_notes: + description: 'Notes that are only visible to the user who created the client' + type: string + example: 'Client prefers email communication over phone calls' + client_hash: + description: 'A unique hash value for the client' + type: string + example: asdfkjhk342hjhbfdvmnfb1 + readOnly: true + industry_id: + description: 'The unique identifier of the industry the client operates in' + type: number + example: '5' + size_id: + description: 'The unique identifier for the size category of the client company or organization' + type: number + example: '2' + address1: + description: "First line of the client's address" + type: string + example: '123 Main St' + address2: + description: "Second line of the client's address, if needed" + type: string + example: 'Apt 4B' + city: + description: 'The city the client is located in' + type: string + example: 'Beverly Hills' + state: + description: 'The state, province, or locality the client is located in' + type: string + example: 'California' + postal_code: + description: 'The postal code or ZIP code of the client' + type: string + example: '90210' + phone: + description: "The client's phone number" + type: string + example: '555-3434-3434' + country_id: + description: "The unique identifier of the client's country" + type: number + format: integer + example: '1' + custom_value1: + description: 'A custom field for storing additional information' + type: string + example: 'Preferred contact: Email' + custom_value2: + description: 'A custom field for storing additional information' + type: string + example: 'Account manager: John Doe' + custom_value3: + description: 'A custom field for storing additional information' + type: string + example: 'VIP client: Yes' + custom_value4: + description: 'A custom field for storing additional information' + type: string + example: 'Annual contract value: $50,000' + vat_number: + description: "The client's VAT (Value Added Tax) number, if applicable" + type: string + example: 'VAT123456' + id_number: + description: 'A unique identification number for the client, such as a tax ID or business registration number' + type: string + number: + description: 'A system-assigned unique number for the client, typically used for invoicing purposes' + type: string + example: 'CL-0001' + shipping_address1: + description: "First line of the client's shipping address" + type: string + example: '5 Wallaby Way' + shipping_address2: + description: "Second line of the client's shipping address, if needed" + type: string + example: 'Suite 5' + shipping_city: + description: "The city of the client's shipping address" + type: string + example: 'Perth' + shipping_state: + description: "The state, province, or locality of the client's shipping address" + type: string + example: 'Western Australia' + shipping_postal_code: + description: "The postal code or ZIP code of the client's shipping address" + type: string + example: '6110' + shipping_country_id: + description: "The unique identifier of the country for the client's shipping address" + type: number + format: integer + example: '4' + is_deleted: + description: 'A boolean value indicating whether the client has been deleted or not' + type: boolean + example: false + readOnly: true + balance: + description: 'The outstanding balance the client owes' + type: number + format: float + example: '500.00' + readOnly: true + paid_to_date: + description: 'The total amount the client has paid to date' + type: number + format: float + example: '2000.00' + readOnly: true + credit_balance: + description: 'The available credit balance for the client to use on future purchases' + type: number + format: float + example: '100.00' + readOnly: true + last_login: + description: "The timestamp of the client's last login" + type: number + format: integer + example: '1628686031' + readOnly: true + created_at: + description: 'The timestamp when the client was created' + type: number + format: integer + example: '1617629031' + readOnly: true + updated_at: + description: 'The timestamp when the client was last updated' + type: number + format: integer + example: '1628445631' + readOnly: true + group_settings_id: + description: 'The group settings assigned to the client' + type: string + example: Opnel5aKBz + routing_id: + description: 'The routing address id for e-invoicing for this client' + type: string + example: Opnel5aKBz3489-dfkiu-2239-sdsd + is_tax_exempt: + description: 'Flag which defines if the client is exempt from taxes' + type: boolean + example: false + has_valid_vat_number: + description: 'Flag which defines if the client has a valid VAT number' + type: boolean + example: false + readOnly: true + payment_balance: + description: 'Defines the payment balance the client has on file (pre payments / over payments / unapplied amounts)' + type: number + example: 100 + readOnly: true + settings: + $ref: '#/components/schemas/ClientSettings' + type: object + Vendor: + properties: + id: + description: 'The hashed id of the vendor. This is a unique identifier for the vendor.' + type: string + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The hashed id of the user who created the vendor. This is a unique identifier for the user.' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The hashed id of the assigned user to this vendor. This is a unique identifier for the user.' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company. This is a unique identifier for the company.' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed id of the client. This is a unique identifier for the client.' + type: string + example: Opnel5aKBz + contacts: + type: array + items: + $ref: '#/components/schemas/VendorContact' + description: 'An array of contacts associated with the vendor.' + name: + description: 'The name of the vendor.' + type: string + example: 'Harry cafe de wheels' + classification: + description: 'The classification of the vendor.' + type: string + example: 'individual' + website: + description: 'The website of the vendor.' + type: string + example: www.harry.com + private_notes: + description: 'The private notes of the vendor. These notes are only visible to users with appropriate permissions.' + type: string + example: 'Shhh, do not tell the vendor' + industry_id: + description: 'The industry id of the vendor. This is a unique identifier for the industry.' + type: string + example: '1' + size_id: + description: 'The size id of the vendor. This is a unique identifier for the size of the vendor.' + type: string + example: '' + address1: + description: 'The first line of the vendor''s address.' + type: string + example: '' + address2: + description: 'The second line of the vendor''s address.' + type: string + example: '' + city: + description: 'The city of the vendor''s address.' + type: string + example: '' + state: + description: 'The state of the vendor''s address.' + type: string + example: '' + postal_code: + description: 'The postal code of the vendor''s address.' + type: string + example: '' + phone: + description: 'The phone number of the vendor.' + type: string + example: 555-3434-3434 + country_id: + description: 'The country id of the vendor. This is a unique identifier for the country.' + type: string + example: '' + currency_id: + description: 'The currency id of the vendor. This is a unique identifier for the currency.' + type: string + example: '4' + custom_value1: + description: 'The value of the first custom field for the vendor.' + type: string + example: '' + custom_value2: + description: 'The value of the second custom field for the vendor.' + type: string + example: '' + custom_value3: + description: 'The value of the third custom field for the vendor.' + type: string + example: '' + custom_value4: + description: 'The value of the fourth custom field for the vendor.' + type: string + example: '' + vat_number: + description: 'The VAT number of the vendor.' + type: string + example: '' + id_number: + description: 'The ID number of the vendor.' + type: string + example: '' + number: + description: 'The number of the vendor' + type: string + example: '11234' + is_deleted: + description: 'Boolean flag determining if the vendor has been deleted' + type: boolean + example: true + language_id: + description: 'The language id of the vendor. This is a unique identifier for the language.' + type: string + example: '1' + vendor_hash: + description: 'The vendor hash of the vendor. This is a unique identifier for the vendor.' + type: string + example: 'aaa-sss-www' + readOnly: true + transaction_name: + description: 'The transaction name of the vendor.' + type: string + example: 'aaa-sss-www' + last_login: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + settings: + $ref: '#/components/schemas/CompanySettings' + type: object + VendorContact: + properties: + id: + description: 'The hashed id of the vendor contact' + type: string + example: Opnel5aKBz + readOnly: true + user_id: + description: 'The hashed id of the user id' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: Opnel5aKBz + vendor_id: + description: 'The hashed id of the vendor' + type: string + example: Opnel5aKBz + first_name: + description: 'The first name of the contact' + type: string + example: Harry + last_name: + description: 'The last name of the contact' + type: string + example: Windsor + phone: + description: 'The contacts phone number' + type: string + example: 555-123-1234 + custom_value1: + description: 'A custom value' + type: string + example: '2022-10-10' + custom_value2: + description: 'A custom value' + type: string + example: $1000 + custom_value3: + description: 'A custom value' + type: string + example: '' + custom_value4: + description: 'A custom value' + type: string + example: '' + email: + description: 'The contact email address' + type: string + example: harry@windsor.com + is_primary: + description: 'Boolean flag determining if the contact is the primary contact for the vendor' + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + deleted_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + readOnly: true + type: object + Quote: + properties: + id: + description: 'The unique hashed identifier for the quote' + type: string + example: Opnel5aKBz + user_id: + description: 'The unique hashed identifier for the user who created the quote' + type: string + example: '' + assigned_user_id: + description: 'The unique hashed identifier for the user assigned to the quote' + type: string + example: '' + company_id: + description: 'The unique hashed identifier for the company associated with the quote' + type: string + example: '' + client_id: + description: 'The unique hashed identifier for the client associated with the quote' + type: string + example: '' + status_id: + description: 'The status of the quote represented by a unique identifier' + type: string + example: '' + number: + description: 'The unique alpha-numeric quote number for the quote per company' + type: string + example: QUOTE_101 + po_number: + description: 'The purchase order number associated with the quote' + type: string + example: PO-1234 + terms: + description: 'The terms and conditions for the quote' + type: string + example: 'These are some quote terms. Valid for 14 days.' + public_notes: + description: 'Publicly visible notes associated with the quote' + type: string + example: 'These are public notes which the client may see' + private_notes: + description: 'Privately visible notes associated with the quote, not disclosed to the client' + type: string + example: 'These are private notes, not to be disclosed to the client' + footer: + description: 'The footer text of the quote' + type: string + example: 'The text goes in the footer of the quote' + custom_value1: + description: 'First custom value field for additional information' + type: string + example: 'A custom value' + custom_value2: + description: 'Second custom value field for additional information' + type: string + example: 'A custom value' + custom_value3: + description: 'Third custom value field for additional information' + type: string + example: 'A custom value' + custom_value4: + description: 'Fourth custom value field for additional information' + type: string + example: 'A custom value' + tax_name1: + description: 'The name of the first tax applied to the quote' + type: string + example: GST + tax_name2: + description: 'The name of the second tax applied to the quote' + type: string + example: VAT + tax_rate1: + description: 'The rate of the first tax applied to the quote' + type: number + format: float + example: 10.00 + tax_rate2: + description: 'The rate of the second tax applied to the quote' + type: number + format: float + example: 10.00 + tax_name3: + description: 'The name of the third tax applied to the quote' + type: string + example: '' + tax_rate3: + description: 'The rate of the third tax applied to the quote' + type: number + format: float + example: 10.00 + total_taxes: + description: 'The total amount of taxes for the quote' + type: number + format: float + example: 10.00 + line_items: + type: array + description: 'An array of objects which define the line items of the quote' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: 'The total amount of the quote before taxes and discounts' + type: number + format: float + example: 10.00 + balance: + description: 'The balance due for the quote after accounting for payments' + type: number + format: float + example: 10.00 + paid_to_date: + description: 'The total amount paid on the quote so far' + type: number + format: float + example: 10.00 + discount: + description: 'The discount amount or percentage applied to the quote' + type: number + format: float + example: 10.00 + partial: + description: 'The partial or deposit amount for the quote' + type: number + format: float + example: 10.00 + is_amount_discount: + description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Boolean flag indicating if the quote has been deleted' + type: boolean + example: false + uses_inclusive_taxes: + description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' + type: boolean + example: true + date: + description: 'The date the quote was created' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the quote was sent to the client' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next scheduled date for sending a reminder for the quote' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the partial or deposit amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date for the total amount of the quote' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: 'The timestamp of the last time the quote was viewed' + type: number + format: integer + example: 1434342123 + updated_at: + description: 'The timestamp of the last update to the quote' + type: number + format: integer + example: 1434342123 + archived_at: + description: 'The timestamp of when the quote was archived' + type: number + format: integer + example: 1434342123 + custom_surcharge1: + description: 'First custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge3: + description: 'Third custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge4: + description: 'Fourth custom surcharge amount for the quote' + type: number + format: float + example: 10.00 + custom_surcharge_tax1: + description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' + type: boolean + example: true + type: object + Payment: + properties: + id: + description: 'The payment hashed id' + type: string + example: Opnel5aKBz + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + invitation_id: + description: 'The invitation hashed id' + type: string + example: Opnel5aKBz + client_contact_id: + description: 'The client contact hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + type_id: + description: 'The Payment Type ID' + type: string + example: '1' + date: + description: 'The Payment date' + type: string + example: 1-1-2014 + transaction_reference: + description: 'The transaction reference as defined by the payment gateway' + type: string + example: xcsSxcs124asd + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: Opnel5aKBz + private_notes: + description: 'The private notes of the payment' + type: string + example: 'The payment was refunded due to error' + is_manual: + description: 'Flags whether the payment was made manually or processed via a gateway' + type: boolean + example: true + is_deleted: + description: 'Defines if the payment has been deleted' + type: boolean + example: true + amount: + description: 'The amount of this payment' + type: number + example: 10 + refunded: + description: 'The refunded amount of this payment' + type: number + example: 10 + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + company_gateway_id: + description: 'The company gateway id' + type: string + example: '3' + paymentables: + $ref: '#/components/schemas/Paymentable' + invoices: + description: '' + type: array + items: + $ref: '#/components/schemas/InvoicePaymentable' + credits: + description: '' + type: array + items: + $ref: '#/components/schemas/CreditPaymentable' + number: + description: 'The payment number - is a unique alpha numeric number per payment per company' + type: string + example: PAY_101 + type: object + + Task: + properties: + id: + description: 'The hashed id of the task' + type: string + example: Opnel5aKBz + user_id: + description: 'The hashed id of the user who created the task' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user of the task' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed if of the client' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The hashed id of the invoice associated with the task' + type: string + example: Opnel5aKBz + project_id: + description: 'The hashed id of the project associated with the task' + type: string + example: Opnel5aKBz + number: + description: 'The number of the task' + type: string + example: TASK-123 + time_log: + description: 'An array of unix time stamps defining the start and end times of the task' + type: string + example: '[[1,2],[3,4]]' + is_running: + description: 'Determines if the task is still running' + type: boolean + example: true + is_deleted: + description: 'Boolean flag determining if the task has been deleted' + type: boolean + example: true + task_status_id: + description: 'The hashed id of the task status' + type: string + example: Opnel5aKBz + description: + description: 'The task description' + type: string + example: 'A wonder task to work on' + duration: + description: 'The task duration in seconds' + type: integer + example: '3600' + task_status_order: + description: 'The order of the task' + type: integer + example: '4' + rate: + description: 'The task rate' + type: number + example: 10.00 + custom_value1: + description: 'A custom value' + type: string + example: '2022-10-10' + custom_value2: + description: 'A custom value' + type: string + example: $1100 + custom_value3: + description: 'A custom value' + type: string + example: 'I need help' + custom_value4: + description: 'A custom value' + type: string + example: INV-3343 + is_date_based: + description: 'Boolean flag determining if the task is date based' + type: boolean + example: true + calculated_start_date: + description: 'The calculated start date of the task' + type: string + example: '2022-10-10' + readOnly: true + invoice_documents: + description: "Boolean flags which determines whether to include the task documents on the invoice" + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + readOnly: true + type: object + RecurringInvoice: + properties: + id: + description: 'The hashed id of the recurring invoice' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + status_id: + description: 'The invoice status variable' + type: string + example: '4' + frequency_id: + description: 'The recurring invoice frequency' + type: number + example: '4' + remaining_cycles: + description: 'The number of invoices left to be generated' + type: number + example: '4' + number: + description: 'The recurringinvoice number - is a unique alpha numeric number per invoice per company' + type: string + example: INV_101 + po_number: + description: 'The purchase order associated with this recurring invoice' + type: string + example: PO-1234 + terms: + description: 'The invoice terms' + type: string + example: 'These are invoice terms' + public_notes: + description: 'The public notes of the invoice' + type: string + example: 'These are some public notes' + private_notes: + description: 'The private notes of the invoice' + type: string + example: 'These are some private notes' + footer: + description: 'The invoice footer notes' + type: string + example: '' + custom_value1: + description: 'A custom field value' + type: string + example: '2022-10-01' + custom_value2: + description: 'A custom field value' + type: string + example: 'Something custom' + custom_value3: + description: 'A custom field value' + type: string + example: '' + custom_value4: + description: 'A custom field value' + type: string + example: '' + tax_name1: + description: 'The tax name' + type: string + example: '' + tax_name2: + description: 'The tax name' + type: string + example: '' + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + total_taxes: + description: 'The total taxes for the invoice' + type: number + format: float + example: '10.00' + line_items: + description: 'An array of objects which define the line items of the invoice' + type: object + example: '' + amount: + description: 'The invoice amount' + type: number + format: float + example: '10.00' + balance: + description: 'The invoice balance' + type: number + format: float + example: '10.00' + paid_to_date: + description: 'The amount paid on the invoice to date' + type: number + format: float + example: '10.00' + discount: + description: 'The invoice discount, can be an amount or a percentage' + type: number + format: float + example: '10.00' + partial: + description: 'The deposit/partial amount' + type: number + format: float + example: '10.00' + is_amount_discount: + description: 'Flag determining if the discount is an amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Defines if the invoice has been deleted' + type: boolean + example: true + uses_inclusive_taxes: + description: 'Defines the type of taxes used as either inclusive or exclusive' + type: boolean + example: true + date: + description: 'The Invoice Date' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the invoice was sent out' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The Next date for a reminder to be sent' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the deposit/partial amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date of the invoice' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: Timestamp + type: number + format: integer + example: '1434342123' + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + custom_surcharge1: + description: 'First Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge2: + description: 'Second Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge3: + description: 'Third Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge4: + description: 'Fourth Custom Surcharge' + type: number + format: float + example: '10.00' + custom_surcharge_tax1: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Toggles charging taxes on custom surcharge amounts' + type: boolean + example: true + type: object + + InvoiceItem: + type: object + properties: + quantity: + type: integer + example: 1 + description: 'The quantity of the product offered for this line item' + cost: + type: number + format: float + example: 10.00 + description: 'The cost of the product offered for this line item' + product_key: + type: string + example: 'Product key' + description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' + product_cost: + type: number + format: float + example: 10.00 + description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' + notes: + type: string + example: 'Item notes' + description: 'The notes/description for the product offered for this line item' + discount: + type: number + format: float + example: 5.00 + description: 'The discount applied to the product offered for this line item' + is_amount_discount: + type: boolean + example: false + description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' + tax_name1: + type: string + example: 'GST' + description: 'The name of the first tax applied to the product offered for this line item' + tax_rate1: + type: number + format: float + example: 10.00 + description: 'The rate of the first tax applied to the product offered for this line item' + tax_name2: + type: string + example: 'VAT' + description: 'The name of the second tax applied to the product offered for this line item' + tax_rate2: + type: number + format: float + example: 5.00 + description: 'The rate of the second tax applied to the product offered for this line item' + tax_name3: + type: string + example: 'CA Sales Tax' + description: 'The name of the third tax applied to the product offered for this line item' + tax_rate3: + type: number + format: float + example: 3.00 + description: 'The rate of the third tax applied to the product offered for this line item' + sort_id: + type: string + example: '0' + description: 'Deprecated' + deprecated: true + line_total: + type: number + format: float + example: 10.00 + description: 'The total amount of the product offered for this line item' + readOnly: true + gross_line_total: + type: number + format: float + example: 15.00 + description: 'The total amount of the product offered for this line item before discounts' + readOnly: true + tax_amount: + type: number + format: float + example: 1.00 + description: 'The total amount of tax applied to the product offered for this line item' + readOnly: true + date: + type: string + format: date-time + example: '2023-03-19T00:00:00Z' + description: 'Deprecated' + deprecated: true + custom_value1: + type: string + example: 'Custom value 1' + description: 'The first custom value of the product offered for this line item' + custom_value2: + type: string + example: 'Custom value 2' + description: 'The second custom value of the product offered for this line item' + custom_value3: + type: string + example: 'Custom value 3' + description: 'The third custom value of the product offered for this line item' + custom_value4: + type: string + example: 'Custom value 4' + description: 'The fourth custom value of the product offered for this line item' + type_id: + type: string + example: '1' + description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' + default: '1' + tax_id: + type: string + example: '1' + default: '1' + description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' + Activity: + properties: + id: + description: 'The id field of the activity' + type: string + example: Opnel5aKBz + activity_type_id: + description: 'The activity type id' + type: string + example: Opnel5aKBz + client_id: + description: 'The client hashed id' + type: string + example: Opnel5aKBz + company_id: + description: 'The company hashed id' + type: string + example: Opnel5aKBz + user_id: + description: 'The user hashed id' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The invoice hashed id' + type: string + example: Opnel5aKBz + payment_id: + description: 'The payment hashed id' + type: string + example: Opnel5aKBz + credit_id: + description: 'The credit hashed id' + type: string + example: Opnel5aKBz + updated_at: + description: 'Unixtimestamp the last time the record was updated' + type: integer + example: '343421434' + expense_id: + description: 'The expense hashed id' + type: string + example: Opnel5aKBz + is_system: + description: 'Defines is the activity was performed by the system' + type: boolean + example: true + contact_id: + description: 'The contact hashed id' + type: string + example: Opnel5aKBz + task_id: + description: 'The task hashed id' + type: string + example: Opnel5aKBz + notes: + description: 'Activity Notes' + type: string + example: Opnel5aKBz + token_id: + description: 'The hashed ID of the token who performed the action' + type: string + example: Opnel5aKBz + ip: + description: 'The IP Address of the user who performed the action' + type: string + example: 192.168.1.252 + user: + $ref: '#/components/schemas/User' + client: + $ref: '#/components/schemas/Client' + contact: + $ref: '#/components/schemas/ClientContact' + recurring_invoice: + $ref: '#/components/schemas/RecurringInvoice' + invoice: + $ref: '#/components/schemas/Invoice' + credit: + $ref: '#/components/schemas/Credit' + quote: + $ref: '#/components/schemas/Quote' + payment: + $ref: '#/components/schemas/Payment' + expense: + $ref: '#/components/schemas/Expense' + task: + $ref: '#/components/schemas/Task' + purchase_order: + $ref: '#/components/schemas/PurchaseOrder' + vendor: + $ref: '#/components/schemas/Vendor' + vendor_contact: + $ref: '#/components/schemas/VendorContact' + type: object + + ProductBulkAction: + required: + - action + - ids + properties: + action: + type: string + example: archive + description: 'The action to perform ie. archive / restore / delete / set_tax_id' + ids: + type: array + items: + format: string + type: string + example: 2J234DFA,D2J234DFA,D2J234DFA + description: string array of client hashed ids + tax_id: + type: string + example: '1' + description: | + The tax rate id to set on the list of products + + The following constants are available (default = '1') + + ``` + PRODUCT_TYPE_PHYSICAL = '1' + PRODUCT_TYPE_SERVICE = '2' + PRODUCT_TYPE_DIGITAL = '3' + PRODUCT_TYPE_SHIPPING = '4' + PRODUCT_TYPE_EXEMPT = '5' + PRODUCT_TYPE_REDUCED_TAX = '6' + PRODUCT_TYPE_OVERRIDE_TAX = '7' + PRODUCT_TYPE_ZERO_RATED = '8' + PRODUCT_TYPE_REVERSE_TAX = '9' + ``` + type: object + RecurringExpense: + properties: + id: + description: 'The hashed id of the recurring expense' + type: string + example: Opnel5aKBz + user_id: + description: 'The hashed id of the user who created the recurring expense' + type: string + example: Opnel5aKBz + assigned_user_id: + description: 'The hashed id of the user assigned to this recurring expense' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: Opnel5aKBz + client_id: + description: 'The hashed id of the client' + type: string + example: Opnel5aKBz + invoice_id: + description: 'The hashed id of the invoice' + type: string + example: Opnel5aKBz + bank_id: + description: 'The id of the bank associated with this recurring expense' + type: string + example: '22' + invoice_currency_id: + description: 'The currency id of the invoice associated with this recurring expense' + type: string + example: '1' + expense_currency_id: + description: 'The currency id of the expense associated with this recurring expense' + type: string + example: '1' + invoice_category_id: + description: 'The category id of the invoice' + type: string + example: '1' + payment_type_id: + description: 'The payment type id' + type: string + example: '1' + private_notes: + description: 'The recurring expense private notes' + type: string + example: 'Private and confidential' + public_notes: + description: 'The recurring expense public notes' + type: string + example: 'This is the best client in the world' + transaction_reference: + description: 'The recurring expense transaction reference' + type: string + example: EXP-1223-2333 + transcation_id: + description: 'The transaction id of the recurring expense' + type: string + example: '1233312312' + custom_value1: + description: 'Custom value field' + type: string + example: $1000 + custom_value2: + description: 'Custom value field' + type: string + example: '2022-10-10' + custom_value3: + description: 'Custom value field' + type: string + example: 'short text' + custom_value4: + description: 'Custom value field' + type: string + example: 'very long text' + tax_name1: + description: 'The tax name' + type: string + example: GST + tax_name2: + description: 'The tax name' + type: string + example: VAT + tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '10.00' + tax_name3: + description: 'The tax name' + type: string + example: '' + tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '10.00' + amount: + description: 'The total amount of the recurring expense' + type: number + format: float + example: '10.00' + frequency_id: + description: 'The frequency this recurring expense fires' + type: number + format: int + example: '1' + remaining_cycles: + description: 'The number of remaining cycles for this recurring expense' + type: number + format: int + example: '1' + foreign_amount: + description: 'The foreign currency amount of the recurring expense' + type: number + format: float + example: '10.00' + exchange_rate: + description: 'The exchange rate for the expernse' + type: number + format: float + example: '0.80' + date: + description: 'The date of the expense' + type: string + example: '' + payment_date: + description: 'The date the expense was paid' + type: string + example: '' + should_be_invoiced: + description: 'Boolean flag determining if the expense should be invoiced' + type: boolean + example: true + is_deleted: + description: 'Boolean flag determining if the recurring expense is deleted' + type: boolean + example: true + last_sent_date: + description: 'The Date it was sent last' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next send date' + type: string + format: date + example: '1994-07-30' + invoice_documents: + description: 'Boolean flag determining if the documents associated with this expense should be passed onto the invoice if it is converted to an invoice' + type: boolean + example: true + updated_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + archived_at: + description: Timestamp + type: number + format: integer + example: '1434342123' + type: object + PaymentTerm: + properties: + num_days: + description: 'The payment term length in days' + type: integer + example: '1' + name: + description: 'The payment term length in string format' + type: string + example: 'NET 1' + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + archived_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + ClientGatewayToken: + properties: + id: + description: 'The hashed id of the client gateway token' + type: string + example: Opnel5aKBz + company_id: + description: 'The hashed id of the company' + type: string + example: '2' + client_id: + description: 'The hashed_id of the client' + type: string + example: '2' + token: + description: 'The payment token' + type: string + example: '2' + routing_number: + description: 'THe bank account routing number' + type: string + example: '2' + company_gateway_id: + description: 'The hashed id of the company gateway' + type: string + example: '2' + is_default: + description: 'Flag determining if the token is the default payment method' + type: boolean + example: 'true' + type: object + User: + properties: + id: + description: 'The hashed id of the user' + type: string + example: Opnel5aKBz + readOnly: true + first_name: + description: 'The first name of the user' + type: string + example: Brad + last_name: + description: 'The last name of the user' + type: string + example: Pitt + email: + description: 'The users email address' + type: string + example: brad@pitt.com + phone: + description: 'The users phone number' + type: string + example: 555-1233-23232 + signature: + description: 'The users sign off signature' + type: string + example: 'Have a nice day!' + avatar: + description: 'The users avatar' + type: string + example: 'https://url.to.your/avatar.png' + accepted_terms_version: + description: 'The version of the invoice ninja terms that has been accepted by the user' + type: string + example: 1.0.1 + readOnly: true + oauth_user_id: + description: 'The provider id of the oauth entity' + type: string + example: jkhasdf789as6f675sdf768sdfs + readOnly: true + oauth_provider_id: + description: 'The oauth entity id' + type: string + example: google + readOnly: true + language_id: + description: 'The language id of the user' + type: string + example: 1 + verified_phone_number: + description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA' + type: boolean + example: true + readOnly: true + sms_verification_code: + description: 'The sms verification code for the user. Required to settings up 2FA' + type: string + example: '123456' + readOnly: true + oauth_user_token_expiry: + description: 'The expiry date of the oauth token' + type: string + example: '2022-10-10' + readOnly: true + has_password: + description: 'Boolean flag determining if the user has a password' + type: boolean + example: true + readOnly: true + last_confirmed_email_address: + description: 'The last confirmed email address of the user' + type: string + example: 'bob@gmail.com' + readOnly: true + custom_value1: + description: 'A custom value' + type: string + example: 'Custom value 1' + custom_value2: + description: 'A custom value' + type: string + example: '$1000' + custom_value3: + description: 'A custom value' + type: string + example: 'Custom value 3' + custom_value4: + description: 'A custom value' + type: string + example: 'Custom value 4' + is_deleted: + description: 'Boolean flag determining if the user has been deleted' + type: boolean + example: true + readOnly: true + google_2fa_secret: + description: 'The google 2fa secret for the user' + type: string + example: '123456' + readOnly: true + type: object CompanyGateway: properties: id: @@ -15297,6 +18422,1512 @@ components: type: object + Document: + properties: + id: + description: 'The document hashed id' + type: string + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: '' + assigned_user_id: + description: 'The assigned user hashed id' + type: string + example: '' + project_id: + description: 'The project associated with this document' + type: string + example: '' + vendor_id: + description: 'The vendor associated with this documents' + type: string + example: '' + name: + description: 'The document name' + type: string + example: Beauty + url: + description: 'The document url' + type: string + example: Beauty + preview: + description: 'The document preview url' + type: string + example: Beauty + type: + description: 'The document type' + type: string + example: Beauty + disk: + description: 'The document disk' + type: string + example: Beauty + hash: + description: 'The document hashed' + type: string + example: Beauty + is_deleted: + description: 'Flag to determine if the document is deleted' + type: boolean + example: true + is_default: + description: 'Flag to determine if the document is a default doc' + type: boolean + example: true + created_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + updated_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + deleted_at: + description: Timestamp + type: number + format: integer + example: '134341234234' + type: object + PurchaseOrder: + properties: + id: + description: 'The unique hashed identifier for the purchase order' + type: string + example: Opnel5aKBz + user_id: + description: 'The unique hashed identifier for the user who created the purchase order' + type: string + example: '' + assigned_user_id: + description: 'The unique hashed identifier for the user assigned to the purchase order' + type: string + example: '' + company_id: + description: 'The unique hashed identifier for the company associated with the purchase order' + type: string + example: '' + vendor_id: + description: 'The unique hashed identifier for the vendor associated with the purchase order' + type: string + example: '' + status_id: + description: 'The status of the purchase order represented by a unique identifier' + type: string + example: '' + number: + description: 'The unique alpha-numeric purchase order number per company' + type: string + example: PO_101 + quote_number: + description: 'The quote number associated with this purchase order' + type: string + example: QUOTE_101 + terms: + description: 'The terms and conditions for the purchase order' + type: string + example: 'These are some purchase order terms. Valid for 14 days.' + public_notes: + description: 'Publicly visible notes associated with the purchase order' + type: string + example: 'These are public notes which the vendor may see' + private_notes: + description: 'Privately visible notes associated with the purchase order, not disclosed to the vendor' + type: string + example: 'These are private notes, not to be disclosed to the vendor' + footer: + description: 'The footer text of the purchase order' + type: string + example: 'The text goes in the footer of the purchase order' + custom_value1: + description: 'First custom value field for additional information' + type: string + example: 'A custom value' + custom_value2: + description: 'Second custom value field for additional information' + type: string + example: 'A custom value' + custom_value3: + description: 'Third custom value field for additional information' + type: string + example: 'A custom value' + custom_value4: + description: 'Fourth custom value field for additional information' + type: string + example: 'A custom value' + tax_name1: + description: 'The name of the first tax applied to the purchase order' + type: string + example: GST + tax_name2: + description: 'The name of the second tax applied to the purchase order' + type: string + example: VAT + tax_rate1: + description: 'The rate of the first tax applied to the purchase order' + type: number + format: float + example: 10.00 + tax_rate2: + description: 'The rate of the second tax applied to the purchase order' + type: number + format: float + example: 10.00 + tax_name3: + description: 'The name of the third tax applied to the purchase order' + type: string + example: '' + tax_rate3: + description: 'The rate of the third tax applied to the purchase order' + type: number + format: float + example: 10.00 + total_taxes: + description: 'The total amount of taxes applied to the purchase order' + type: number + format: float + example: 10.00 + line_items: + type: array + description: 'An array of objects which define the line items of the purchase order' + items: + $ref: '#/components/schemas/InvoiceItem' + amount: + description: 'The total amount of the purchase order before taxes and discounts' + type: number + format: float + example: 10.00 + balance: + description: 'The balance due for the purchase order after accounting for payments' + type: number + format: float + example: 10.00 + paid_to_date: + description: 'The total amount paid on the purchase order so far' + type: number + format: float + example: 10.00 + discount: + description: 'The discount amount or percentage applied to the purchase order' + type: number + format: float + example: 10.00 + partial: + description: 'The partial or deposit amount for the purchase order' + type: number + format: float + example: 10.00 + is_amount_discount: + description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' + type: boolean + example: true + is_deleted: + description: 'Boolean flag indicating if the purchase order has been deleted' + type: boolean + example: false + uses_inclusive_taxes: + description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' + type: boolean + example: true + date: + description: 'The date the purchase order was created' + type: string + format: date + example: '1994-07-30' + last_sent_date: + description: 'The last date the purchase order was sent to the vendor' + type: string + format: date + example: '1994-07-30' + next_send_date: + description: 'The next scheduled date for sending a reminder for the purchase order' + type: string + format: date + example: '1994-07-30' + partial_due_date: + description: 'The due date for the partial or deposit amount' + type: string + format: date + example: '1994-07-30' + due_date: + description: 'The due date for the total amount of the purchase order' + type: string + format: date + example: '1994-07-30' + settings: + $ref: '#/components/schemas/CompanySettings' + last_viewed: + description: Timestamp + type: number + format: integer + example: 1434342123 + updated_at: + description: Timestamp + type: number + format: integer + example: 1434342123 + archived_at: + description: Timestamp + type: number + format: integer + example: 1434342123 + custom_surcharge1: + description: 'First custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge2: + description: 'Second custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge3: + description: 'Third custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge4: + description: 'Fourth custom surcharge amount for the purchase order' + type: number + format: float + example: 10.00 + custom_surcharge_tax1: + description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax2: + description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax3: + description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' + type: boolean + example: true + custom_surcharge_tax4: + description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' + type: boolean + example: true + type: object + + + ClientSettings: + required: + - currency_id + properties: + currency_id: + description: 'The default currency id' + type: string + example: true + timezone_id: + description: 'The timezone id' + type: string + example: '15' + date_format_id: + description: 'The date format id' + type: string + example: '15' + military_time: + description: 'Toggles 12/24 hour time' + type: boolean + example: true + language_id: + description: 'The language id' + type: string + example: '1' + show_currency_code: + description: 'Toggles whether the currency symbol or code is shown' + type: boolean + example: true + payment_terms: + description: '-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days' + type: integer + example: '1' + company_gateway_ids: + description: 'A commad separate list of available gateways' + type: string + example: '1,2,3,4' + custom_value1: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value2: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value3: + description: 'A Custom Label' + type: string + example: 'Custom Label' + custom_value4: + description: 'A Custom Label' + type: string + example: 'Custom Label' + default_task_rate: + description: 'The default task rate' + type: number + format: float + example: '10.00' + send_reminders: + description: 'Toggles whether reminders are sent' + type: boolean + example: true + enable_client_portal_tasks: + description: 'Show/hide the tasks panel in the client portal' + type: boolean + example: true + email_style: + description: 'options include plain,light,dark,custom' + type: string + example: light + reply_to_email: + description: 'The reply to email address' + type: string + example: email@gmail.com + bcc_email: + description: 'A comma separate list of BCC emails' + type: string + example: 'email@gmail.com, contact@gmail.com' + pdf_email_attachment: + description: 'Toggles whether to attach PDF as attachment' + type: boolean + example: true + ubl_email_attachment: + description: 'Toggles whether to attach UBL as attachment' + type: boolean + example: true + email_style_custom: + description: 'The custom template' + type: string + example: '' + counter_number_applied: + description: 'enum when the invoice number counter is set, ie when_saved, when_sent, when_paid' + type: string + example: when_sent + quote_number_applied: + description: 'enum when the quote number counter is set, ie when_saved, when_sent' + type: string + example: when_sent + custom_message_dashboard: + description: 'A custom message which is displayed on the dashboard' + type: string + example: 'Please pay invoices immediately' + custom_message_unpaid_invoice: + description: 'A custom message which is displayed in the client portal when a client is viewing a unpaid invoice.' + type: string + example: 'Please pay invoices immediately' + custom_message_paid_invoice: + description: 'A custom message which is displayed in the client portal when a client is viewing a paid invoice.' + type: string + example: 'Thanks for paying this invoice!' + custom_message_unapproved_quote: + description: 'A custom message which is displayed in the client portal when a client is viewing a unapproved quote.' + type: string + example: 'Please approve quote' + lock_invoices: + description: 'Toggles whether invoices are locked once sent and cannot be modified further' + type: boolean + example: true + auto_archive_invoice: + description: 'Toggles whether a invoice is archived immediately following payment' + type: boolean + example: true + auto_archive_quote: + description: 'Toggles whether a quote is archived after being converted to a invoice' + type: boolean + example: true + auto_convert_quote: + description: 'Toggles whether a quote is converted to a invoice when approved' + type: boolean + example: true + inclusive_taxes: + description: 'Boolean flag determining whether inclusive or exclusive taxes are used' + type: boolean + example: true + task_number_pattern: + description: 'Allows customisation of the task number pattern' + type: string + example: '{$year}-{$counter}' + task_number_counter: + description: 'The incrementing counter for tasks' + type: integer + example: '1' + reminder_send_time: + description: 'Time from UTC +0 when the email will be sent to the client' + type: integer + example: '32400' + expense_number_pattern: + description: 'Allows customisation of the expense number pattern' + type: string + example: '{$year}-{$counter}' + expense_number_counter: + description: 'The incrementing counter for expenses' + type: integer + example: '1' + vendor_number_pattern: + description: 'Allows customisation of the vendor number pattern' + type: string + example: '{$year}-{$counter}' + vendor_number_counter: + description: 'The incrementing counter for vendors' + type: integer + example: '1' + ticket_number_pattern: + description: 'Allows customisation of the ticket number pattern' + type: string + example: '{$year}-{$counter}' + ticket_number_counter: + description: 'The incrementing counter for tickets' + type: integer + example: '1' + payment_number_pattern: + description: 'Allows customisation of the payment number pattern' + type: string + example: '{$year}-{$counter}' + payment_number_counter: + description: 'The incrementing counter for payments' + type: integer + example: '1' + invoice_number_pattern: + description: 'Allows customisation of the invoice number pattern' + type: string + example: '{$year}-{$counter}' + invoice_number_counter: + description: 'The incrementing counter for invoices' + type: integer + example: '1' + quote_number_pattern: + description: 'Allows customisation of the quote number pattern' + type: string + example: '{$year}-{$counter}' + quote_number_counter: + description: 'The incrementing counter for quotes' + type: integer + example: '1' + client_number_pattern: + description: 'Allows customisation of the client number pattern' + type: string + example: '{$year}-{$counter}' + client_number_counter: + description: 'The incrementing counter for clients' + type: integer + example: '1' + credit_number_pattern: + description: 'Allows customisation of the credit number pattern' + type: string + example: '{$year}-{$counter}' + credit_number_counter: + description: 'The incrementing counter for credits' + type: integer + example: '1' + recurring_invoice_number_prefix: + description: 'This string is prepended to the recurring invoice number' + type: string + example: R + reset_counter_frequency_id: + description: 'CONSTANT which is used to apply the frequency which the counters are reset' + type: integer + example: '1' + reset_counter_date: + description: 'The explicit date which is used to reset counters' + type: string + example: '2019-01-01' + counter_padding: + description: 'Pads the counter with leading zeros' + type: integer + example: '1' + shared_invoice_quote_counter: + description: 'Flags whether to share the counter for invoices and quotes' + type: boolean + example: true + update_products: + description: 'Determines if client fields are updated from third party APIs' + type: boolean + example: true + convert_products: + description: '' + type: boolean + example: true + fill_products: + description: 'Automatically fill products based on product_key' + type: boolean + example: true + invoice_terms: + description: 'The default invoice terms' + type: string + example: 'Invoice Terms are...' + quote_terms: + description: 'The default quote terms' + type: string + example: 'Quote Terms are...' + invoice_taxes: + description: 'Taxes can be applied to the invoice' + type: number + example: '1' + invoice_design_id: + description: 'The default design id (invoice, quote etc)' + type: string + example: '1' + quote_design_id: + description: 'The default design id (invoice, quote etc)' + type: string + example: '1' + invoice_footer: + description: 'The default invoice footer' + type: string + example: '1' + invoice_labels: + description: 'JSON string of invoice labels' + type: string + example: '1' + tax_rate1: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name1: + description: 'The tax name' + type: string + example: GST + tax_rate2: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name2: + description: 'The tax name' + type: string + example: GST + tax_rate3: + description: 'The tax rate (float)' + type: number + example: '10' + tax_name3: + description: 'The tax name' + type: string + example: GST + payment_type_id: + description: 'The default payment type id' + type: string + example: '1' + custom_fields: + description: 'JSON string of custom fields' + type: string + example: '{}' + email_footer: + description: 'The default email footer' + type: string + example: 'A default email footer' + email_sending_method: + description: 'The email driver to use to send email, options include default, gmail' + type: string + example: default + gmail_sending_user_id: + description: 'The hashed_id of the user account to send email from' + type: string + example: F76sd34D + email_subject_invoice: + description: '' + type: string + example: 'Your Invoice Subject' + email_subject_quote: + description: '' + type: string + example: 'Your Quote Subject' + email_subject_payment: + description: '' + type: string + example: 'Your Payment Subject' + email_template_invoice: + description: 'The full template for invoice emails' + type: string + example: '' + email_template_quote: + description: 'The full template for quote emails' + type: string + example: '' + email_template_payment: + description: 'The full template for payment emails' + type: string + example: '' + email_subject_reminder1: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder2: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder3: + description: 'Email subject for Reminder' + type: string + example: '' + email_subject_reminder_endless: + description: 'Email subject for endless reminders' + type: string + example: '' + email_template_reminder1: + description: 'The full template for Reminder 1' + type: string + example: '' + email_template_reminder2: + description: 'The full template for Reminder 2' + type: string + example: '' + email_template_reminder3: + description: 'The full template for Reminder 3' + type: string + example: '' + email_template_reminder_endless: + description: 'The full template for enless reminders' + type: string + example: '' + enable_portal_password: + description: 'Toggles whether a password is required to log into the client portal' + type: boolean + example: true + show_accept_invoice_terms: + description: 'Toggles whether the terms dialogue is shown to the client' + type: boolean + example: true + show_accept_quote_terms: + description: 'Toggles whether the terms dialogue is shown to the client' + type: boolean + example: true + require_invoice_signature: + description: 'Toggles whether a invoice signature is required' + type: boolean + example: true + require_quote_signature: + description: 'Toggles whether a quote signature is required' + type: boolean + example: true + name: + description: 'The company name' + type: string + example: 'Acme Co' + company_logo: + description: 'The company logo file' + type: object + example: logo.png + website: + description: 'The company website URL' + type: string + example: www.acme.com + address1: + description: 'The company address line 1' + type: string + example: 'Suite 888' + address2: + description: 'The company address line 2' + type: string + example: '5 Jimbo Way' + city: + description: 'The company city' + type: string + example: Sydney + state: + description: 'The company state' + type: string + example: Florisa + postal_code: + description: 'The company zip/postal code' + type: string + example: '90210' + phone: + description: 'The company phone' + type: string + example: 555-213-3948 + email: + description: 'The company email' + type: string + example: joe@acme.co + country_id: + description: 'The country ID' + type: string + example: '1' + vat_number: + description: 'The company VAT/TAX ID number' + type: string + example: '32 120 377 720' + page_size: + description: 'The default page size' + type: string + example: A4 + font_size: + description: 'The font size' + type: number + example: '9' + primary_font: + description: 'The primary font' + type: string + example: roboto + secondary_font: + description: 'The secondary font' + type: string + example: roboto + hide_paid_to_date: + description: 'Flags whether to hide the paid to date field' + type: boolean + example: false + embed_documents: + description: 'Toggled whether to embed documents in the PDF' + type: boolean + example: false + all_pages_header: + description: 'The header for the PDF' + type: boolean + example: false + all_pages_footer: + description: 'The footer for the PDF' + type: boolean + example: false + document_email_attachment: + description: 'Toggles whether to attach documents in the email' + type: boolean + example: false + enable_client_portal_password: + description: 'Toggles password protection of the client portal' + type: boolean + example: false + enable_email_markup: + description: 'Toggles the use of markdown in emails' + type: boolean + example: false + enable_client_portal_dashboard: + description: 'Toggles whether the client dashboard is shown in the client portal' + type: boolean + example: false + enable_client_portal: + description: 'Toggles whether the entire client portal is displayed to the client, or only the context' + type: boolean + example: false + email_template_statement: + description: 'The body of the email for statements' + type: string + example: 'template matter' + email_subject_statement: + description: 'The subject of the email for statements' + type: string + example: 'subject matter' + signature_on_pdf: + description: 'Toggles whether the signature (if available) is displayed on the PDF' + type: boolean + example: false + quote_footer: + description: 'The default quote footer' + type: string + example: 'the quote footer' + email_subject_custom1: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 1' + email_subject_custom2: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 2' + email_subject_custom3: + description: 'Custom reminder template subject' + type: string + example: 'Custom Subject 3' + email_template_custom1: + description: 'Custom reminder template body' + type: string + example: '' + email_template_custom2: + description: 'Custom reminder template body' + type: string + example: '' + email_template_custom3: + description: 'Custom reminder template body' + type: string + example: '' + enable_reminder1: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + enable_reminder2: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + enable_reminder3: + description: 'Toggles whether this reminder is enabled' + type: boolean + example: false + num_days_reminder1: + description: 'The Reminder interval' + type: number + example: '9' + num_days_reminder2: + description: 'The Reminder interval' + type: number + example: '9' + num_days_reminder3: + description: 'The Reminder interval' + type: number + example: '9' + schedule_reminder1: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + schedule_reminder2: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + schedule_reminder3: + description: '(enum: after_invoice_date, before_due_date, after_due_date)' + type: string + example: after_invoice_date + late_fee_amount1: + description: 'The late fee amount for reminder 1' + type: number + example: 10 + late_fee_amount2: + description: 'The late fee amount for reminder 2' + type: number + example: 20 + late_fee_amount3: + description: 'The late fee amount for reminder 2' + type: number + example: 100 + endless_reminder_frequency_id: + description: 'The frequency id of the endless reminder' + type: string + example: '1' + client_online_payment_notification: + description: 'Determines if a client should receive the notification for a online payment' + type: boolean + example: false + client_manual_payment_notification: + description: 'Determines if a client should receive the notification for a manually entered payment' + type: boolean + example: false + enable_e_invoice: + description: 'Determines if e-invoicing is enabled' + type: boolean + example: false + default_expense_payment_type_id: + description: 'The default payment type for expenses' + type: string + example: '0' + e_invoice_type: + description: 'The e-invoice type' + type: string + example: 'EN16931' + mailgun_endpoint: + description: 'The mailgun endpoint - used to determine whether US or EU endpoints are used' + type: string + example: 'api.mailgun.net or api.eu.mailgun.net' + client_initiated_payments: + description: 'Determines if clients can initiate payments directly from the client portal' + type: boolean + example: false + client_initiated_payments_minimum: + description: 'The minimum amount a client can pay' + type: number + example: 10 + sync_invoice_quote_columns: + description: 'Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns' + type: boolean + example: false + show_task_item_description: + description: 'Determines if the task item description is shown on the invoice' + type: boolean + example: false + allow_billable_task_items: + description: 'Determines if task items can be marked as billable' + type: boolean + example: false + accept_client_input_quote_approval: + description: 'Determines if clients can approve quotes and also pass through a PO Number reference' + type: boolean + example: false + custom_sending_email: + description: 'When using Mailgun or Postmark, the FROM email address can be customized using this setting.' + type: string + example: 'bob@gmail.com' + show_paid_stamp: + description: 'Determines if the PAID stamp is shown on the invoice' + type: boolean + example: false + show_shipping_address: + description: 'Determines if the shipping address is shown on the invoice' + type: boolean + example: false + company_logo_size: + description: 'The size of the company logo on the PDF - percentage value between 0 and 100' + type: number + example: 100 + show_email_footer: + description: 'Determines if the email footer is shown on emails' + type: boolean + example: false + email_alignment: + description: 'The alignment of the email body text, options include left / center / right' + type: string + example: 'left' + auto_bill_standard_invoices: + description: 'Determines if standard invoices are automatically billed when they are created or due' + type: boolean + example: false + postmark_secret: + description: 'The Postmark secret API key' + type: string + example: '123456' + mailgun_secret: + description: 'The Mailgun secret API key' + type: string + example: '123456' + mailgun_domain: + description: 'The Mailgun domain' + type: string + example: 'sandbox123456.mailgun.org' + send_email_on_mark_paid: + description: 'Determines if an email is sent when an invoice is marked as paid' + type: boolean + example: false + vendor_portal_enable_uploads: + description: 'Determines if vendors can upload files to the portal' + type: boolean + example: false + besr_id: + description: 'The BESR ID' + type: string + example: '123456' + qr_iban: + description: 'The IBAN for the QR code' + type: string + example: 'CH123456' + email_subject_purchase_order: + description: 'The email subject for purchase orders' + type: string + example: 'Purchase Order' + email_template_purchase_order: + description: 'The email template for purchase orders' + type: string + example: 'Please see attached your purchase order.' + require_purchase_order_signature: + description: 'Determines if a signature is required on purchase orders' + type: boolean + example: false + purchase_order_public_notes: + description: 'The public notes for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_terms: + description: 'The terms for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_footer: + description: 'The footer for purchase orders' + type: string + example: 'Please see attached your purchase order.' + purchase_order_design_id: + description: 'The design id for purchase orders' + type: string + example: 'hd677df' + purchase_order_number_pattern: + description: 'The pattern for purchase order numbers' + type: string + example: 'PO-000000' + purchase_order_number_counter: + description: 'The counter for purchase order numbers' + type: number + example: 1 + page_numbering_alignment: + description: 'The alignment for page numbering: options include left / center / right' + type: string + example: 'left' + page_numbering: + description: 'Determines if page numbering is enabled on Document PDFs' + type: boolean + example: false + auto_archive_invoice_cancelled: + description: 'Determines if invoices are automatically archived when they are cancelled' + type: boolean + example: false + email_from_name: + description: 'The FROM name for emails when using Custom emailers' + type: string + example: 'Bob Smith' + show_all_tasks_client_portal: + description: 'Determines if all tasks are shown on the client portal' + type: boolean + example: false + entity_send_time: + description: 'The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24' + type: integer + example: 9 + shared_invoice_credit_counter: + description: 'Determines if the invoice and credit counter are shared' + type: boolean + example: false + reply_to_name: + description: 'The reply to name for emails' + type: string + example: 'Bob Smith' + hide_empty_columns_on_pdf: + description: 'Determines if empty columns are hidden on PDFs' + type: boolean + example: false + enable_reminder_endless: + description: 'Determines if endless reminders are enabled' + type: boolean + example: false + use_credits_payment: + description: 'Determines if credits can be used as a payment method' + type: boolean + example: false + recurring_invoice_number_pattern: + description: 'The pattern for recurring invoice numbers' + type: string + example: 'R-000000' + recurring_invoice_number_counter: + description: 'The counter for recurring invoice numbers' + type: number + example: 1 + client_portal_under_payment_minimum: + description: 'The minimum payment payment' + type: number + example: 10 + auto_bill_date: + description: 'Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))' + type: string + example: 'on_send_date' + primary_color: + description: 'The primary color for the client portal / document highlights' + type: string + example: '#ffffff' + secondary_color: + description: 'The secondary color for the client portal / document highlights' + type: string + example: '#ffffff' + client_portal_allow_under_payment: + description: 'Determines if clients can pay invoices under the invoice amount due' + type: boolean + example: false + client_portal_allow_over_payment: + description: 'Determines if clients can pay invoices over the invoice amount' + type: boolean + example: false + auto_bill: + description: 'Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing' + type: string + example: 'off' + client_portal_terms: + description: 'The terms which are displayed on the client portal' + type: string + example: 'Please see attached your invoice.' + client_portal_privacy_policy: + description: 'The privacy policy which is displayed on the client portal' + type: string + example: 'These are the terms of use for using the client portal.' + client_can_register: + description: 'Determines if clients can register on the client portal' + type: boolean + example: false + portal_design_id: + description: 'The design id for the client portal' + type: string + example: 'hd677df' + late_fee_endless_percent: + description: 'The late fee percentage for endless late fees' + type: number + example: 10 + late_fee_endless_amount: + description: 'The late fee amount for endless late fees' + type: number + example: 10 + auto_email_invoice: + description: 'Determines if invoices are automatically emailed when they are created' + type: boolean + example: false + email_signature: + description: 'The email signature for emails' + type: string + example: 'Bob Smith' + type: object + Product: + type: object + properties: + id: + type: string + description: 'The hashed product ID.' + example: eP01N + readOnly: true + company_id: + type: string + description: 'The hashed ID of the company that owns this product.' + example: eP01N + readOnly: true + user_id: + type: string + description: 'The hashed ID of the user that created this product.' + example: n30m4 + readOnly: true + assigned_user_id: + type: string + description: 'The hashed ID of the user assigned to this product.' + example: pR0j3 + project_id: + type: string + description: 'The hashed ID of the project that this product is associated with.' + example: pR0j3 + vendor_id: + type: string + description: 'The hashed ID of the vendor that this product is associated with.' + example: pR0j3 + custom_value1: + type: string + description: 'Custom value field 1.' + example: 'Custom value 1' + custom_value2: + type: string + description: 'Custom value field 2.' + example: 'Custom value 2' + custom_value3: + type: string + description: 'Custom value field 3.' + example: 'Custom value 3' + custom_value4: + type: string + description: 'Custom value field 4.' + example: 'Custom value 4' + product_key: + type: string + description: 'The product key.' + example: '1234' + notes: + type: string + description: 'Notes about the product.' + example: 'These are some notes about the product.' + cost: + type: number + format: double + description: 'The cost of the product. (Your purchase price for this product)' + example: 10.0 + price: + type: number + format: double + description: 'The price of the product that you are charging.' + example: 20.0 + quantity: + type: number + format: double + description: 'The quantity of the product. (used as a default)' + example: 5.0 + tax_name1: + type: string + description: 'The name of tax 1.' + example: 'Tax 1' + tax_rate1: + type: number + format: double + description: 'The rate of tax 1.' + example: 10.0 + tax_name2: + type: string + description: 'The name of tax 2.' + example: 'Tax 2' + tax_rate2: + type: number + format: double + description: 'The rate of tax 2.' + example: 5.0 + tax_name3: + type: string + description: 'The name of tax 3.' + example: 'Tax 3' + tax_rate3: + type: number + format: double + description: 'The rate of tax 3.' + example: 0.0 + archived_at: + type: integer + format: timestamp + description: 'The timestamp when the product was archived.' + example: '2022-03-18T15:00:00Z' + readOnly: true + created_at: + type: integer + format: timestamp + description: 'The timestamp when the product was created.' + example: '2022-03-18T15:00:00Z' + readOnly: true + updated_at: + description: Timestamp + type: integer + format: timestamp + example: '2022-03-18T12:34:56.789Z' + readOnly: true + is_deleted: + type: boolean + description: 'Boolean flag determining if the product has been deleted' + example: false + readOnly: true + in_stock_quantity: + type: integer + format: int32 + description: The quantity of the product that is currently in stock + default: 0 + stock_notification: + type: boolean + description: Indicates whether stock notifications are enabled for this product + default: true + stock_notification_threshold: + type: integer + format: int32 + description: The minimum quantity threshold for which stock notifications will be triggered + default: 0 + max_quantity: + type: integer + format: int32 + description: The maximum quantity that can be ordered for this product + product_image: + type: string + description: The URL of the product image + format: uri-reference + tax_id: + type: string + default: '1' + description: | + The tax category id for this product.' + + The following constants are available (default = '1') + + ``` + PRODUCT_TYPE_PHYSICAL = '1' + PRODUCT_TYPE_SERVICE = '2' + PRODUCT_TYPE_DIGITAL = '3' + PRODUCT_TYPE_SHIPPING = '4' + PRODUCT_TYPE_EXEMPT = '5' + PRODUCT_TYPE_REDUCED_TAX = '6' + PRODUCT_TYPE_OVERRIDE_TAX = '7' + PRODUCT_TYPE_ZERO_RATED = '8' + PRODUCT_TYPE_REVERSE_TAX = '9' + ``` + example: '1' + + ClientContactRequest: + properties: + id: + description: 'The hashed if of the contact' + type: string + example: Opnel5aKBz + readOnly: true + first_name: + description: 'The first name of the contact' + type: string + example: John + last_name: + description: 'The last name of the contact' + type: string + example: Doe + phone: + description: 'The phone number of the contact' + type: string + example: 555-152-4524 + custom_value1: + description: 'A Custom field value' + type: string + example: '' + custom_value2: + description: 'A Custom field value' + type: string + example: '' + custom_value3: + description: 'A Custom field value' + type: string + example: '' + custom_value4: + description: 'A Custom field value' + type: string + example: '' + email: + description: 'The email of the contact' + type: string + example: '' + password: + description: 'The hashed password of the contact' + type: string + example: '*****' + send_email: + description: 'Boolean value determines is this contact should receive emails' + type: boolean + example: true + type: object + ClientRequest: + required: + - contacts + - country_id + properties: + id: + description: 'The unique identifier of the client' + type: string + example: Opnel5aKBz + readOnly: true + contacts: + type: array + description: 'A list of contacts associated with the client' + items: + $ref: '#/components/schemas/ClientContactRequest' + name: + description: 'The name of the client company or organization' + type: string + example: "Jim's Housekeeping" + website: + description: 'The website URL of the client company or organization' + type: string + example: 'https://www.jims-housekeeping.com' + private_notes: + description: 'Notes that are only visible to the user who created the client' + type: string + example: 'Client prefers email communication over phone calls' + industry_id: + description: 'The unique identifier of the industry the client operates in' + type: number + example: '5' + size_id: + description: 'The unique identifier for the size category of the client company or organization' + type: number + example: '2' + address1: + description: "First line of the client's address" + type: string + example: '123 Main St' + address2: + description: "Second line of the client's address, if needed" + type: string + example: 'Apt 4B' + city: + description: 'The city the client is located in' + type: string + example: 'Beverly Hills' + state: + description: 'The state, province, or locality the client is located in' + type: string + example: 'California' + postal_code: + description: 'The postal code or ZIP code of the client' + type: string + example: '90210' + phone: + description: "The client's phone number" + type: string + example: '555-3434-3434' + country_id: + description: "The unique identifier of the client's country" + type: number + format: integer + example: '1' + custom_value1: + description: 'A custom field for storing additional information' + type: string + example: 'Preferred contact: Email' + custom_value2: + description: 'A custom field for storing additional information' + type: string + example: 'Account manager: John Doe' + custom_value3: + description: 'A custom field for storing additional information' + type: string + example: 'VIP client: Yes' + custom_value4: + description: 'A custom field for storing additional information' + type: string + example: 'Annual contract value: $50,000' + vat_number: + description: "The client's VAT (Value Added Tax) number, if applicable" + type: string + example: 'VAT123456' + id_number: + description: 'A unique identification number for the client, such as a tax ID or business registration number' + type: string + number: + description: 'A system-assigned unique number for the client, typically used for invoicing purposes' + type: string + example: 'CL-0001' + shipping_address1: + description: "First line of the client's shipping address" + type: string + example: '5 Wallaby Way' + shipping_address2: + description: "Second line of the client's shipping address, if needed" + type: string + example: 'Suite 5' + shipping_city: + description: "The city of the client's shipping address" + type: string + example: 'Perth' + shipping_state: + description: "The state, province, or locality of the client's shipping address" + type: string + example: 'Western Australia' + shipping_postal_code: + description: "The postal code or ZIP code of the client's shipping address" + type: string + example: '6110' + shipping_country_id: + description: "The unique identifier of the country for the client's shipping address" + type: number + format: integer + example: '4' + is_deleted: + description: 'A boolean value indicating whether the client has been deleted or not' + type: boolean + example: false + readOnly: true + group_settings_id: + description: 'The group settings assigned to the client' + type: string + example: Opnel5aKBz + routing_id: + description: 'The routing address id for e-invoicing for this client' + type: string + example: Opnel5aKBz3489-dfkiu-2239-sdsd + is_tax_exempt: + description: 'Flag which defines if the client is exempt from taxes' + type: boolean + example: false + has_valid_vat_number: + description: 'Flag which defines if the client has a valid VAT number' + type: boolean + example: false + readOnly: true + classification: + description: 'The classification of the client' + type: string + example: 'individual' + settings: + $ref: '#/components/schemas/ClientSettings' + type: object + Error: + properties: + message: + description: 'Something terrible went wrong' + type: string + example: 'Unexpected error' + code: + description: 'The HTTP error code, ie 5xx 4xx' + type: integer + example: '500' + type: object + BTRules: + properties: + data_key: + description: 'The key to search' + type: string + example: 'description,amount' + operator: + description: 'The operator flag of the search' + type: string + example: '>' + value: + description: 'The value to search for' + type: string + example: bob + type: object CompanySettings: required: - currency_id @@ -16139,1630 +20770,6 @@ components: type: string example: 'individual' type: object - SystemLog: - properties: - id: - description: 'The account hashed id' - type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user_id hashed id' - type: string - example: AS3df3A - client_id: - description: 'The client_id hashed id' - type: string - example: AS3df3A - event_id: - description: 'The Log Type ID' - type: integer - example: 1 - category_id: - description: 'The Category Type ID' - type: integer - example: 1 - type_id: - description: 'The Type Type ID' - type: integer - example: 1 - log: - description: 'The json object of the error' - type: object - example: '{''key'':''value''}' - updated_at: - description: Timestamp - type: string - example: '2' - created_at: - description: Timestamp - type: string - example: '2' - type: object - ClientGatewayToken: - properties: - id: - description: 'The hashed id of the client gateway token' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: '2' - client_id: - description: 'The hashed_id of the client' - type: string - example: '2' - token: - description: 'The payment token' - type: string - example: '2' - routing_number: - description: 'THe bank account routing number' - type: string - example: '2' - company_gateway_id: - description: 'The hashed id of the company gateway' - type: string - example: '2' - is_default: - description: 'Flag determining if the token is the default payment method' - type: boolean - example: 'true' - type: object - - GenericBulkAction: - properties: - action: - type: string - example: archive - description: 'The action to perform ie. archive / restore / delete' - ids: - type: array - items: - format: string - type: string - example: 2J234DFA,D2J234DFA,D2J234DFA - description: string array of client hashed ids - type: object - InvoiceInvitationRequest: - required: - - client_contact_id - properties: - id: - description: 'The entity invitation hashed id' - type: string - example: Opnel5aKBz - readOnly: true - client_contact_id: - description: 'The client contact hashed id' - type: string - example: Opnel5aKBz - key: - description: 'The invitation key' - type: string - example: Opnel5aKBz4343343566236gvbb - readOnly: true - link: - description: 'The invitation link' - type: string - example: 'https://www.example.com/invitations/Opnel5aKBz4343343566236gvbb' - readOnly: true - sent_date: - description: 'The invitation sent date' - type: string - format: date-time - readOnly: true - viewed_date: - description: 'The invitation viewed date' - type: string - format: date-time - readOnly: true - opened_date: - description: 'The invitation opened date' - type: string - format: date-time - readOnly: true - updated_at: - description: 'Timestamp' - type: number - format: integer - example: '1434342123' - readOnly: true - archived_at: - description: 'Timestamp' - type: number - format: integer - example: '1434342123' - readOnly: true - email_error: - description: 'The email error' - type: string - example: 'The email error' - readOnly: true - email_status: - description: 'The email status' - type: string - readOnly: true - - Client: - properties: - id: - description: 'The unique identifier of the client' - type: string - example: Opnel5aKBz - readOnly: true - contacts: - type: array - items: - $ref: '#/components/schemas/ClientContact' - user_id: - description: 'The unique identifier of the user who created the client' - type: string - example: Ua6Rw4pVbS - readOnly: true - assigned_user_id: - description: 'The unique identifier of the user who has been assigned the client' - type: string - example: Ua6Rw4pVbS - company_id: - description: 'The unique identifier of the company the client belongs to' - type: string - example: Co7Vn3yLmW - readOnly: true - name: - description: 'The name of the client company or organization' - type: string - example: "Jim's Housekeeping" - website: - description: 'The website URL of the client company or organization' - type: string - example: 'https://www.jims-housekeeping.com' - private_notes: - description: 'Notes that are only visible to the user who created the client' - type: string - example: 'Client prefers email communication over phone calls' - client_hash: - description: 'A unique hash value for the client' - type: string - example: asdfkjhk342hjhbfdvmnfb1 - readOnly: true - industry_id: - description: 'The unique identifier of the industry the client operates in' - type: number - example: '5' - size_id: - description: 'The unique identifier for the size category of the client company or organization' - type: number - example: '2' - address1: - description: "First line of the client's address" - type: string - example: '123 Main St' - address2: - description: "Second line of the client's address, if needed" - type: string - example: 'Apt 4B' - city: - description: 'The city the client is located in' - type: string - example: 'Beverly Hills' - state: - description: 'The state, province, or locality the client is located in' - type: string - example: 'California' - postal_code: - description: 'The postal code or ZIP code of the client' - type: string - example: '90210' - phone: - description: "The client's phone number" - type: string - example: '555-3434-3434' - country_id: - description: "The unique identifier of the client's country" - type: number - format: integer - example: '1' - custom_value1: - description: 'A custom field for storing additional information' - type: string - example: 'Preferred contact: Email' - custom_value2: - description: 'A custom field for storing additional information' - type: string - example: 'Account manager: John Doe' - custom_value3: - description: 'A custom field for storing additional information' - type: string - example: 'VIP client: Yes' - custom_value4: - description: 'A custom field for storing additional information' - type: string - example: 'Annual contract value: $50,000' - vat_number: - description: "The client's VAT (Value Added Tax) number, if applicable" - type: string - example: 'VAT123456' - id_number: - description: 'A unique identification number for the client, such as a tax ID or business registration number' - type: string - number: - description: 'A system-assigned unique number for the client, typically used for invoicing purposes' - type: string - example: 'CL-0001' - shipping_address1: - description: "First line of the client's shipping address" - type: string - example: '5 Wallaby Way' - shipping_address2: - description: "Second line of the client's shipping address, if needed" - type: string - example: 'Suite 5' - shipping_city: - description: "The city of the client's shipping address" - type: string - example: 'Perth' - shipping_state: - description: "The state, province, or locality of the client's shipping address" - type: string - example: 'Western Australia' - shipping_postal_code: - description: "The postal code or ZIP code of the client's shipping address" - type: string - example: '6110' - shipping_country_id: - description: "The unique identifier of the country for the client's shipping address" - type: number - format: integer - example: '4' - is_deleted: - description: 'A boolean value indicating whether the client has been deleted or not' - type: boolean - example: false - readOnly: true - balance: - description: 'The outstanding balance the client owes' - type: number - format: float - example: '500.00' - readOnly: true - paid_to_date: - description: 'The total amount the client has paid to date' - type: number - format: float - example: '2000.00' - readOnly: true - credit_balance: - description: 'The available credit balance for the client to use on future purchases' - type: number - format: float - example: '100.00' - readOnly: true - last_login: - description: "The timestamp of the client's last login" - type: number - format: integer - example: '1628686031' - readOnly: true - created_at: - description: 'The timestamp when the client was created' - type: number - format: integer - example: '1617629031' - readOnly: true - updated_at: - description: 'The timestamp when the client was last updated' - type: number - format: integer - example: '1628445631' - readOnly: true - group_settings_id: - description: 'The group settings assigned to the client' - type: string - example: Opnel5aKBz - routing_id: - description: 'The routing address id for e-invoicing for this client' - type: string - example: Opnel5aKBz3489-dfkiu-2239-sdsd - is_tax_exempt: - description: 'Flag which defines if the client is exempt from taxes' - type: boolean - example: false - has_valid_vat_number: - description: 'Flag which defines if the client has a valid VAT number' - type: boolean - example: false - readOnly: true - payment_balance: - description: 'Defines the payment balance the client has on file (pre payments / over payments / unapplied amounts)' - type: number - example: 100 - readOnly: true - settings: - $ref: '#/components/schemas/ClientSettings' - type: object - CompanyUser: - properties: - permissions: - description: 'The company user permissions' - type: string - example: '[create_invoice]' - settings: - description: 'Settings that are used for the frontend applications to store user preferences / metadata' - type: object - example: 'json object' - react_settings: - description: 'Dedicated settings object for the react web application' - type: object' - example: 'json object' - is_owner: - description: 'Determines whether the user owns this company' - type: boolean - example: true - is_admin: - description: 'Determines whether the user is the admin of this company' - type: boolean - example: true - is_locked: - description: 'Determines whether the users access to this company has been locked' - type: boolean - example: true - updated_at: - description: 'The last time the record was modified, format Unix Timestamp' - type: integer - example: '1231232312321' - deleted_at: - description: 'Timestamp when the user was archived, format Unix Timestamp' - type: integer - example: '12312312321' - account: - $ref: '#/components/schemas/Account' - company: - $ref: '#/components/schemas/Company' - user: - $ref: '#/components/schemas/User' - token: - $ref: '#/components/schemas/CompanyToken' - type: object - Design: - properties: - id: - description: 'The design hashed id' - type: string - example: AS3df3A - name: - description: 'The design name' - type: string - example: Beauty - design: - description: 'The design HTML' - type: string - example: '' - is_custom: - description: 'Flag to determine if the design is a custom user design' - type: boolean - example: true - is_active: - description: 'Flag to determine if the design is available for use' - type: boolean - example: true - is_deleted: - description: 'Flag to determine if the design is deleted' - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - deleted_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - InvoiceRequest: - required: - - client_id - properties: - id: - description: 'The invoice hashed id' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - readOnly: true - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - readOnly: true - number: - description: 'The invoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - readOnly: - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - invitations: - type: array - description: 'An array of objects which define the invitations of the invoice' - items: - $ref: '#/components/schemas/InvoiceInvitationRequest' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - readOnly: true - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - readOnly: true - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - readOnly: true - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - readOnly: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - readOnly: true - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - readOnly: true - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - project_id: - description: 'The project associated with this invoice' - type: string - example: Opnel5aKBz - type: object - FillableInvoice: - properties: - assigned_user_id: - description: "The assigned user's hashed ID" - type: string - example: 'a1b2c3d4' - client_id: - description: "The client's hashed ID" - type: string - example: 'x1y2z3a4' - number: - description: "The unique alphanumeric invoice number for each invoice per company" - type: string - example: INV_101 - po_number: - description: "The purchase order number associated with the invoice" - type: string - example: 'PO12345' - terms: - description: "The terms and conditions for the invoice" - type: string - example: 'Net 30' - public_notes: - description: "Public notes visible to the client on the invoice" - type: string - example: 'Thank you for your business.' - private_notes: - description: "Private notes for internal use only" - type: string - example: 'Client is a slow payer.' - footer: - description: "The footer text displayed on the invoice" - type: string - example: 'Authorized Signature' - custom_value1: - description: "First custom value for additional information" - type: string - example: 'Project ABC' - custom_value2: - description: "Second custom value for additional information" - type: string - example: 'Department XYZ' - custom_value3: - description: "Third custom value for additional information" - type: string - example: 'Location 123' - custom_value4: - description: "Fourth custom value for additional information" - type: string - example: 'Currency USD' - tax_name1: - description: "Name of the first tax applied to the invoice" - type: string - example: 'VAT' - tax_name2: - description: "Name of the second tax applied to the invoice" - type: string - example: 'GST' - tax_rate1: - description: "Rate of the first tax applied to the invoice" - type: number - example: 10.00 - tax_rate2: - description: "Rate of the second tax applied to the invoice" - type: number - example: 5.00 - tax_name3: - description: "Name of the third tax applied to the invoice" - type: string - example: 'PST' - tax_rate3: - description: "Rate of the third tax applied to the invoice" - type: number - example: 8.00 - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - discount: - description: "The discount applied to the invoice" - type: number - example: 10.00 - partial: - description: "The partial amount applied to the invoice" - type: number - example: 20.00 - is_amount_discount: - description: "Indicates whether the discount applied is a fixed amount or a percentage" - type: boolean - example: true - uses_inclusive_taxes: - description: "Indicates whether the tax rates applied to the invoice are inclusive or exclusive" - type: boolean - example: true - date: - description: "The date the invoice was issued" - type: string - example: '1994-07-30' - partial_due_date: - description: "The due date for the partial payment" - type: string - example: '1994-08-15' - due_date: - description: "The due date for the invoice" - type: string - example: '1994-08-30' - custom_surcharge1: - description: "First custom surcharge applied to the invoice" - type: number - example: 10.00 - custom_surcharge2: - description: "Second custom surcharge applied to the invoice" - type: number - example: 15.00 - custom_surcharge3: - description: "Third custom surcharge applied to the invoice" - type: number - example: 5.00 - custom_surcharge4: - description: "Fourth custom surcharge applied to the invoice" - type: number - example: 20.00 - type: object - Paymentable: - properties: - id: - description: 'The paymentable hashed id' - type: string - example: AS3df3A - invoice_id: - description: 'The invoice hashed id' - type: string - example: AS3df3A - credit_id: - description: 'The credit hashed id' - type: string - example: AS3df3A - refunded: - description: 'The amount that has been refunded for this payment' - type: number - format: float - example: '10.00' - amount: - description: 'The amount that has been applied to the payment' - type: number - format: float - example: '10.00' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - created_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - BankTransaction: - properties: - id: - description: 'The bank integration hashed id' - type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user hashed id' - type: string - example: AS3df3A - transaction_id: - description: 'The id of the transaction rule' - type: integer - example: 343434 - amount: - description: 'The transaction amount' - type: number - example: 10 - currency_id: - description: 'The currency ID of the currency' - type: string - example: '1' - account_type: - description: 'The account type' - type: string - example: creditCard - description: - description: 'The description of the transaction' - type: string - example: 'Potato purchases for kevin' - category_id: - description: 'The category id' - type: integer - example: 1 - category_type: - description: 'The category description' - type: string - example: Expenses - base_type: - description: 'Either CREDIT or DEBIT' - type: string - example: CREDIT - date: - description: 'The date of the transaction' - type: string - example: '2022-09-01' - bank_account_id: - description: 'The ID number of the bank account' - type: integer - example: '1' - type: object - Meta: - properties: - pagination: - $ref: '#/components/schemas/Pagination' - Pagination: - type: object - properties: - total: - type: integer - description: 'The total number of items' - example: 1 - readOnly: true - count: - type: integer - description: 'The number of items per page' - example: 1 - readOnly: true - per_page: - type: integer - description: 'The number of items per page' - example: 1 - readOnly: true - current_page: - type: integer - description: 'The current page number' - example: 1 - readOnly: true - total_pages: - type: integer - description: 'The total number of pages' - example: 1 - readOnly: true - links: - type: array - description: 'The pagination links' - readOnly: true - - FeesAndLimits: - properties: - min_limit: - description: 'The minimum amount accepted for this gateway' - type: string - example: '2' - max_limit: - description: 'The maximum amount accepted for this gateway' - type: string - example: '2' - fee_amount: - description: 'The gateway fee amount' - type: number - format: float - example: '2.0' - fee_percent: - description: 'The gateway fee percentage' - type: number - format: float - example: '2.0' - fee_tax_name1: - description: 'Fee tax name' - type: string - example: GST - fee_tax_name2: - description: 'Fee tax name' - type: string - example: VAT - fee_tax_name3: - description: 'Fee tax name' - type: string - example: 'CA Sales Tax' - fee_tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.0' - fee_tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '17.5' - fee_tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '25.0' - fee_cap: - description: 'If set the fee amount will be no higher than this amount' - type: number - format: float - example: '2.0' - adjust_fee_percent: - description: 'Adjusts the fee to match the exact gateway fee.' - type: boolean - example: true - type: object - InvoiceItem: - type: object - properties: - quantity: - type: integer - example: 1 - description: 'The quantity of the product offered for this line item' - cost: - type: number - format: float - example: 10.00 - description: 'The cost of the product offered for this line item' - product_key: - type: string - example: 'Product key' - description: 'The product key of the product offered for this line item (Referred to as Product in the product tab)' - product_cost: - type: number - format: float - example: 10.00 - description: 'The cost of the product offered for this line item (Referred to as Cost in the product tab)' - notes: - type: string - example: 'Item notes' - description: 'The notes/description for the product offered for this line item' - discount: - type: number - format: float - example: 5.00 - description: 'The discount applied to the product offered for this line item' - is_amount_discount: - type: boolean - example: false - description: 'Indicates whether the discount applied to the product offered for this line item is a fixed amount or a percentage' - tax_name1: - type: string - example: 'GST' - description: 'The name of the first tax applied to the product offered for this line item' - tax_rate1: - type: number - format: float - example: 10.00 - description: 'The rate of the first tax applied to the product offered for this line item' - tax_name2: - type: string - example: 'VAT' - description: 'The name of the second tax applied to the product offered for this line item' - tax_rate2: - type: number - format: float - example: 5.00 - description: 'The rate of the second tax applied to the product offered for this line item' - tax_name3: - type: string - example: 'CA Sales Tax' - description: 'The name of the third tax applied to the product offered for this line item' - tax_rate3: - type: number - format: float - example: 3.00 - description: 'The rate of the third tax applied to the product offered for this line item' - sort_id: - type: string - example: '0' - description: 'Deprecated' - deprecated: true - line_total: - type: number - format: float - example: 10.00 - description: 'The total amount of the product offered for this line item' - readOnly: true - gross_line_total: - type: number - format: float - example: 15.00 - description: 'The total amount of the product offered for this line item before discounts' - readOnly: true - tax_amount: - type: number - format: float - example: 1.00 - description: 'The total amount of tax applied to the product offered for this line item' - readOnly: true - date: - type: string - format: date-time - example: '2023-03-19T00:00:00Z' - description: 'Deprecated' - deprecated: true - custom_value1: - type: string - example: 'Custom value 1' - description: 'The first custom value of the product offered for this line item' - custom_value2: - type: string - example: 'Custom value 2' - description: 'The second custom value of the product offered for this line item' - custom_value3: - type: string - example: 'Custom value 3' - description: 'The third custom value of the product offered for this line item' - custom_value4: - type: string - example: 'Custom value 4' - description: 'The fourth custom value of the product offered for this line item' - type_id: - type: string - example: '1' - description: '1 = product, 2 = service, 3 unpaid gateway fee, 4 paid gateway fee, 5 late fee, 6 expense' - default: '1' - tax_id: - type: string - example: '1' - default: '1' - description: 'The tax ID of the product: 1 product, 2 service, 3 digital, 4 shipping, 5 exempt, 5 reduced tax, 7 override, 8 zero rate, 9 reverse tax' - Project: - type: object - properties: - id: - description: 'The project hashed id' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: The assigned user identifier associated with the project - type: string - example: Opnel5aKBz - client_id: - type: string - example: Opnel5aKBz - description: The client identifier associated with the project - name: - type: string - description: The name of the project - example: 'New Project' - task_rate: - type: number - format: float - example: 10 - description: The default rate per task for the project - due_date: - type: string - format: date - example: '2019-01-01' - description: The due date for the project - private_notes: - type: string - description: Private notes associated with the project - budgeted_hours: - type: number - format: float - description: The number of budgeted hours for the project - custom_value1: - type: string - description: Custom value field 1 - custom_value2: - type: string - description: Custom value field 2 - custom_value3: - type: string - description: Custom value field 3 - custom_value4: - type: string - description: Custom value field 4 - created_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the project creation - updated_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the last project update - archived_at: - type: number - format: integer - example: 134341234234 - description: The timestamp of the project deletion - public_notes: - type: string - description: Public notes associated with the project - is_deleted: - type: boolean - description: A flag indicating if the project is deleted - number: - type: string - description: The project number - color: - type: string - description: The color associated with the project - required: - - id - - user_id - - company_id - - name - - task_rate - - budgeted_hours - - is_deleted - - color - - PaymentTerm: - properties: - num_days: - description: 'The payment term length in days' - type: integer - example: '1' - name: - description: 'The payment term length in string format' - type: string - example: 'NET 1' - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - archived_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - BankIntegration: - properties: - id: - description: 'The bank integration hashed id' - type: string - example: AS3df3A - company_id: - description: 'The company hashed id' - type: string - example: AS3df3A - user_id: - description: 'The user hashed id' - type: string - example: AS3df3A - provider_bank_name: - description: 'The providers bank name' - type: string - example: 'Chase Bank' - bank_account_id: - description: 'The bank account id' - type: integer - example: '1233434' - bank_account_name: - description: 'The name of the account' - type: string - example: 'My Checking Acc' - bank_account_number: - description: 'The account number' - type: string - example: '111 234 2332' - bank_account_status: - description: 'The status of the bank account' - type: string - example: ACTIVE - bank_account_type: - description: 'The type of account' - type: string - example: CREDITCARD - balance: - description: 'The current bank balance if available' - type: number - example: '1000000' - currency: - description: 'iso_3166_3 code' - type: string - example: USD - type: object - PurchaseOrder: - properties: - id: - description: 'The unique hashed identifier for the purchase order' - type: string - example: Opnel5aKBz - user_id: - description: 'The unique hashed identifier for the user who created the purchase order' - type: string - example: '' - assigned_user_id: - description: 'The unique hashed identifier for the user assigned to the purchase order' - type: string - example: '' - company_id: - description: 'The unique hashed identifier for the company associated with the purchase order' - type: string - example: '' - vendor_id: - description: 'The unique hashed identifier for the vendor associated with the purchase order' - type: string - example: '' - status_id: - description: 'The status of the purchase order represented by a unique identifier' - type: string - example: '' - number: - description: 'The unique alpha-numeric purchase order number per company' - type: string - example: PO_101 - quote_number: - description: 'The quote number associated with this purchase order' - type: string - example: QUOTE_101 - terms: - description: 'The terms and conditions for the purchase order' - type: string - example: 'These are some purchase order terms. Valid for 14 days.' - public_notes: - description: 'Publicly visible notes associated with the purchase order' - type: string - example: 'These are public notes which the vendor may see' - private_notes: - description: 'Privately visible notes associated with the purchase order, not disclosed to the vendor' - type: string - example: 'These are private notes, not to be disclosed to the vendor' - footer: - description: 'The footer text of the purchase order' - type: string - example: 'The text goes in the footer of the purchase order' - custom_value1: - description: 'First custom value field for additional information' - type: string - example: 'A custom value' - custom_value2: - description: 'Second custom value field for additional information' - type: string - example: 'A custom value' - custom_value3: - description: 'Third custom value field for additional information' - type: string - example: 'A custom value' - custom_value4: - description: 'Fourth custom value field for additional information' - type: string - example: 'A custom value' - tax_name1: - description: 'The name of the first tax applied to the purchase order' - type: string - example: GST - tax_name2: - description: 'The name of the second tax applied to the purchase order' - type: string - example: VAT - tax_rate1: - description: 'The rate of the first tax applied to the purchase order' - type: number - format: float - example: 10.00 - tax_rate2: - description: 'The rate of the second tax applied to the purchase order' - type: number - format: float - example: 10.00 - tax_name3: - description: 'The name of the third tax applied to the purchase order' - type: string - example: '' - tax_rate3: - description: 'The rate of the third tax applied to the purchase order' - type: number - format: float - example: 10.00 - total_taxes: - description: 'The total amount of taxes applied to the purchase order' - type: number - format: float - example: 10.00 - line_items: - type: array - description: 'An array of objects which define the line items of the purchase order' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: 'The total amount of the purchase order before taxes and discounts' - type: number - format: float - example: 10.00 - balance: - description: 'The balance due for the purchase order after accounting for payments' - type: number - format: float - example: 10.00 - paid_to_date: - description: 'The total amount paid on the purchase order so far' - type: number - format: float - example: 10.00 - discount: - description: 'The discount amount or percentage applied to the purchase order' - type: number - format: float - example: 10.00 - partial: - description: 'The partial or deposit amount for the purchase order' - type: number - format: float - example: 10.00 - is_amount_discount: - description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Boolean flag indicating if the purchase order has been deleted' - type: boolean - example: false - uses_inclusive_taxes: - description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' - type: boolean - example: true - date: - description: 'The date the purchase order was created' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the purchase order was sent to the vendor' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next scheduled date for sending a reminder for the purchase order' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the partial or deposit amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date for the total amount of the purchase order' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: Timestamp - type: number - format: integer - example: 1434342123 - updated_at: - description: Timestamp - type: number - format: integer - example: 1434342123 - archived_at: - description: Timestamp - type: number - format: integer - example: 1434342123 - custom_surcharge1: - description: 'First custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge3: - description: 'Third custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge4: - description: 'Fourth custom surcharge amount for the purchase order' - type: number - format: float - example: 10.00 - custom_surcharge_tax1: - description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' - type: boolean - example: true - type: object - - - CompanyLedger: - properties: - entity_id: - description: 'This field will reference one of the following entity hashed ID payment_id, invoice_id or credit_id' - type: string - example: AS3df3A - notes: - description: 'The notes which reference this entry of the ledger' - type: string - example: 'Credit note for invoice #3212' - balance: - description: 'The client balance' - type: number - format: float - example: '10.00' - adjustment: - description: 'The amount the client balance is adjusted by' - type: number - format: float - example: '10.00' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - created_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - Task: - properties: - id: - description: 'The hashed id of the task' - type: string - example: Opnel5aKBz - user_id: - description: 'The hashed id of the user who created the task' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user of the task' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed if of the client' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The hashed id of the invoice associated with the task' - type: string - example: Opnel5aKBz - project_id: - description: 'The hashed id of the project associated with the task' - type: string - example: Opnel5aKBz - number: - description: 'The number of the task' - type: string - example: TASK-123 - time_log: - description: 'An array of unix time stamps defining the start and end times of the task' - type: string - example: '[[1,2],[3,4]]' - is_running: - description: 'Determines if the task is still running' - type: boolean - example: true - is_deleted: - description: 'Boolean flag determining if the task has been deleted' - type: boolean - example: true - task_status_id: - description: 'The hashed id of the task status' - type: string - example: Opnel5aKBz - description: - description: 'The task description' - type: string - example: 'A wonder task to work on' - duration: - description: 'The task duration in seconds' - type: integer - example: '3600' - task_status_order: - description: 'The order of the task' - type: integer - example: '4' - rate: - description: 'The task rate' - type: number - example: 10.00 - custom_value1: - description: 'A custom value' - type: string - example: '2022-10-10' - custom_value2: - description: 'A custom value' - type: string - example: $1100 - custom_value3: - description: 'A custom value' - type: string - example: 'I need help' - custom_value4: - description: 'A custom value' - type: string - example: INV-3343 - is_date_based: - description: 'Boolean flag determining if the task is date based' - type: boolean - example: true - calculated_start_date: - description: 'The calculated start date of the task' - type: string - example: '2022-10-10' - readOnly: true - invoice_documents: - description: "Boolean flags which determines whether to include the task documents on the invoice" - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - readOnly: true - type: object ClientContact: properties: id: @@ -17876,2831 +20883,241 @@ components: format: integer example: '134341234234' type: object - ClientContactRequest: + + FeesAndLimits: properties: - id: - description: 'The hashed if of the contact' + min_limit: + description: 'The minimum amount accepted for this gateway' type: string - example: Opnel5aKBz - readOnly: true - first_name: - description: 'The first name of the contact' + example: '2' + max_limit: + description: 'The maximum amount accepted for this gateway' type: string - example: John - last_name: - description: 'The last name of the contact' + example: '2' + fee_amount: + description: 'The gateway fee amount' + type: number + format: float + example: '2.0' + fee_percent: + description: 'The gateway fee percentage' + type: number + format: float + example: '2.0' + fee_tax_name1: + description: 'Fee tax name' type: string - example: Doe - phone: - description: 'The phone number of the contact' + example: GST + fee_tax_name2: + description: 'Fee tax name' type: string - example: 555-152-4524 - custom_value1: - description: 'A Custom field value' + example: VAT + fee_tax_name3: + description: 'Fee tax name' type: string - example: '' - custom_value2: - description: 'A Custom field value' - type: string - example: '' - custom_value3: - description: 'A Custom field value' - type: string - example: '' - custom_value4: - description: 'A Custom field value' - type: string - example: '' - email: - description: 'The email of the contact' - type: string - example: '' - password: - description: 'The hashed password of the contact' - type: string - example: '*****' - send_email: - description: 'Boolean value determines is this contact should receive emails' + example: 'CA Sales Tax' + fee_tax_rate1: + description: 'The tax rate' + type: number + format: float + example: '10.0' + fee_tax_rate2: + description: 'The tax rate' + type: number + format: float + example: '17.5' + fee_tax_rate3: + description: 'The tax rate' + type: number + format: float + example: '25.0' + fee_cap: + description: 'If set the fee amount will be no higher than this amount' + type: number + format: float + example: '2.0' + adjust_fee_percent: + description: 'Adjusts the fee to match the exact gateway fee.' type: boolean example: true type: object - VendorContact: + Account: properties: id: - description: 'The hashed id of the vendor contact' + description: 'The account hashed id' type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The hashed id of the user id' + example: AS3df3A + account_sms_verified: + description: 'Boolean flag if the account has been verified by sms' type: string - example: Opnel5aKBz + example: true + type: object + BankTransactionRule: + properties: + id: + description: 'The bank transaction rules hashed id' + type: string + example: AS3df3A company_id: - description: 'The hashed id of the company' + description: 'The company hashed id' type: string - example: Opnel5aKBz + example: AS3df3A + user_id: + description: 'The user hashed id' + type: string + example: AS3df3A + name: + description: 'The name of the transaction' + type: string + example: 'Rule 1' + rules: + description: 'A mapped collection of the sub rules for the BankTransactionRule' + type: array + items: + $ref: '#/components/schemas/BTRules' + auto_convert: + description: 'Flags whether the rule converts the transaction automatically' + type: boolean + example: true + matches_on_all: + description: 'Flags whether all subrules are required for the match' + type: boolean + example: true + applies_to: + description: 'Flags whether the rule applies to a CREDIT or DEBIT' + type: string + example: CREDIT + client_id: + description: 'The client hashed id' + type: string + example: AS3df3A vendor_id: - description: 'The hashed id of the vendor' + description: 'The vendor hashed id' type: string - example: Opnel5aKBz - first_name: - description: 'The first name of the contact' + example: AS3df3A + category_id: + description: 'The category hashed id' type: string - example: Harry - last_name: - description: 'The last name of the contact' - type: string - example: Windsor - phone: - description: 'The contacts phone number' - type: string - example: 555-123-1234 - custom_value1: - description: 'A custom value' - type: string - example: '2022-10-10' - custom_value2: - description: 'A custom value' - type: string - example: $1000 - custom_value3: - description: 'A custom value' - type: string - example: '' - custom_value4: - description: 'A custom value' - type: string - example: '' - email: - description: 'The contact email address' - type: string - example: harry@windsor.com - is_primary: - description: 'Boolean flag determining if the contact is the primary contact for the vendor' - type: boolean - example: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - deleted_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true + example: AS3df3A type: object - Subscription: + CompanyToken: properties: - id: - description: Unique identifier for the subscription - type: string - example: Opnel5aKBz - user_id: - description: Unique identifier for the user associated with the subscription - type: string - example: Ua6Rw4pVbS - product_id: - description: Unique identifier for the product associated with the subscription - type: string - example: Pr5Ft7yBmC - company_id: - description: Unique identifier for the company associated with the subscription - type: string - example: Co7Vn3yLmW - recurring_invoice_id: - description: Unique identifier for the recurring invoice associated with the subscription - type: string - example: Ri2Yt8zJkP - is_recurring: - description: Indicates whether the subscription is recurring - type: boolean - example: 'true' - frequency_id: - description: 'integer const representation of the frequency' - type: string - example: '1' - auto_bill: - description: 'enum setting' - type: string - example: always - promo_code: - description: Promotional code applied to the subscription - type: string - example: PROMOCODE4U - promo_discount: - description: Discount percentage or amount applied to the subscription - type: number - example: 10 - is_amount_discount: - description: Indicates whether the discount is a fixed amount - type: boolean - example: 'true' - allow_cancellation: - description: Indicates whether the subscription can be cancelled - type: boolean - example: 'true' - per_seat_enabled: - description: Indicates whether the subscription pricing is per seat - type: boolean - example: 'true' - currency_id: - description: Unique identifier for the currency used in the subscription - type: integer - example: '1' - max_seats_limit: - description: Maximum number of seats allowed for the subscription - type: integer - example: '100' - trial_enabled: - description: Indicates whether the subscription has a trial period - type: boolean - example: 'true' - trial_duration: - description: Duration of the trial period in days - type: integer - example: '14' - allow_query_overrides: - description: Indicates whether query overrides are allowed for the subscription - type: boolean - example: 'true' - allow_plan_changes: - description: Indicates whether plan changes are allowed for the subscription - type: boolean - example: 'true' - refund_period: - description: Number of days within which refunds can be requested - type: integer - example: '30' - webhook_configuration: - description: Webhook configuration for the subscription - type: string - example: 'expand reference for this' - is_deleted: - description: Indicates whether the subscription has been deleted - type: boolean - example: 'false' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - type: object - BulkAction: - type: array - items: - type: integer - example: '[0,1,2,3,]' - BTRules: - properties: - data_key: - description: 'The key to search' - type: string - example: 'description,amount' - operator: - description: 'The operator flag of the search' - type: string - example: '>' - value: - description: 'The value to search for' - type: string - example: bob - type: object - Company: - properties: - id: - description: "The unique hashed identifier for the company" - type: string - example: WJxbojagwO - size_id: - description: "The unique identifier representing the company's size category" - type: string - example: '2' - industry_id: - description: "The unique identifier representing the company's industry category" - type: string - example: '5' - slack_webhook_url: - description: "The URL for the company's Slack webhook notifications" - type: string - example: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' - google_analytics_key: - description: "The company's Google Analytics tracking ID" - type: string - example: 'UA-123456789-1' - portal_mode: - description: "The mode determining how client-facing URLs are structured (e.g., subdomain, domain, or iframe)" - type: string - example: subdomain - subdomain: - description: "The subdomain prefix for the company's domain (e.g., 'acme' in acme.domain.com)" - type: string - example: acme - portal_domain: - description: "The fully qualified domain used for client-facing URLs" - type: string - example: 'https://subdomain.invoicing.co' - enabled_tax_rates: - description: "The number of tax rates used per entity" - type: integer - example: '2' - fill_products: - description: "A flag determining whether to auto-fill product descriptions based on the product key" - type: boolean - example: true - convert_products: - description: "A flag determining whether to convert products between different types or units" - type: boolean - example: true - update_products: - description: "A flag determining whether to update product descriptions when the description changes" - type: boolean - example: true - show_product_details: - description: "A flag determining whether to display product details in the user interface" - type: boolean - example: true - show_product_cost: - description: "A flag determining whether to display product cost is shown in the user interface" - type: boolean - example: true - custom_fields: - description: "A mapping of custom fields for various objects within the company" - type: object - enable_product_cost: - description: "A flag determining whether to show or hide the product cost field in the user interface" - type: boolean - example: true - enable_product_quantity: - description: "A flag determining whether to show or hide the product quantity field in the user interface" - type: boolean - example: true - default_quantity: - description: "A flag determining whether to use a default quantity for products" - type: boolean - example: true - custom_surcharge_taxes1: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the first custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes2: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the second custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes3: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the third custom surcharge field" - type: boolean - example: true - custom_surcharge_taxes4: - description: "A flag determining whether to apply taxes on custom surcharge amounts for the fourth custom" - logo: - description: "The company logo file in binary format" - type: string - format: binary - example: logo.png - company_key: - description: "The static company key hash used to identify the Company" - readOnly: true - type: string - example: "Vnb14bRlwiFjc5ckte6cfbygTRkn5IMQ" - client_can_register: - description: "A flag determining whether clients can register for the client portal" - type: boolean - example: true - enabled_modules: - type: integer - description: | - Bitmask representation of the modules that are enabled in the application - - ``` - self::ENTITY_RECURRING_INVOICE => 1, - self::ENTITY_CREDIT => 2, - self::ENTITY_QUOTE => 4, - self::ENTITY_TASK => 8, - self::ENTITY_EXPENSE => 16, - self::ENTITY_PROJECT => 32, - self::ENTITY_VENDOR => 64, - self::ENTITY_TICKET => 128, - self::ENTITY_PROPOSAL => 256, - self::ENTITY_RECURRING_EXPENSE => 512, - self::ENTITY_RECURRING_TASK => 1024, - self::ENTITY_RECURRING_QUOTE => 2048, - ``` - - The default per_page value is 20. - - example: 2048 - db: - readOnly: true - type: string - example: 'db-ninja-01' - first_day_of_week: - description: "The first day of the week for the company" - type: string - example: '1' - first_month_of_year: - description: "The first month for the company financial year" - type: string - example: '1' - enabled_item_tax_rates: - description: "The number of tax rates used per item" - type: integer - example: 2 - is_large: - description: "A flag determining whether the company is considered large" - type: boolean - example: true - default_auto_bill: - type: enum - example: 'always' - description: | - A flag determining whether to auto-bill clients by default - - values: - - - always - Always auto bill - - disabled - Never auto bill - - optin - Allow the client to select their auto bill status with the default being disabled - - optout -Allow the client to select their auto bill status with the default being enabled - mark_expenses_invoiceable: - description: "A flag determining whether to mark expenses as invoiceable by default" - type: boolean - example: true - mark_expenses_paid: - description: "A flag determining whether to mark expenses as paid by default" - type: boolean - example: true - invoice_expense_documents: - description: "A flag determining whether to include expense documents on invoices by default" - type: boolean - example: true - auto_start_tasks: - description: "A flag determining whether to auto-start tasks by default" - type: boolean - example: true - invoice_task_timelog: - description: "A flag determining whether to include task time logs on invoices by default" - type: boolean - example: true - invoice_task_documents: - description: "A flag determining whether to include task documents on invoices by default" - type: boolean - example: true - show_tasks_table: - description: "A flag determining whether to show the tasks table on invoices by default" - type: boolean - example: true - is_disabled: - description: "A flag determining whether the company is disabled" - type: boolean - example: true - default_task_is_date_based: - description: "A flag determining whether to default tasks to be date-based" - type: boolean - example: true - enable_product_discount: - description: "A flag determining whether to show or hide the product discount field in the user interface" - type: boolean - example: true - calculate_expense_tax_by_amount: - description: "A flag determining whether to calculate expense taxes by amount" - type: boolean - example: true - expense_inclusive_taxes: - description: "A flag determining whether to include taxes in the expense amount" - type: boolean - example: true - session_timeout: - description: "The session timeout for the company" - type: integer - example: 60 - oauth_password_required: - description: "A flag determining whether to require a password for `dangerous` actions when using OAuth" - type: boolean - example: true - invoice_task_datelog: - description: "A flag determining whether to include task date logs on invoices by default" - type: boolean - example: true - default_password_timeout: - description: "The default password timeout for the company" - type: integer - example: 60 - show_task_end_date: - description: "A flag determining whether to show the task end date on invoices by default" - type: boolean - example: true - markdown_enabled: - description: "A flag determining whether markdown is enabled for the company" - type: boolean - example: true - report_include_drafts: - description: "A flag determining whether to include draft invoices in reports" - type: boolean - example: true - client_registration_fields: - description: "The client registration fields for the company" - type: object - stop_on_unpaid_recurring: - description: "A flag determining whether to stop recurring invoices when they are unpaid" - type: boolean - example: true - use_quote_terms_on_conversion: - description: "A flag determining whether to use quote terms on conversion to an invoice" - type: boolean - example: true - enable_applying_payments: - description: "A flag determining whether to enable applying payments to invoices" - type: boolean - example: true - track_inventory: - description: "A flag determining whether to track inventory for the company" - type: boolean - example: true - inventory_notification_threshold: - description: "The inventory notification threshold for the company" - type: integer - example: 60 - stock_notification: - description: "A flag determining whether to send stock notifications for the company" - type: boolean - example: true - matomo_url: - description: "The Matomo URL for the company" - type: string - example: 'https://matomo.example.com' - matomo_id: - description: "The Matomo ID for the company" - type: string - example: '1' - enabled_expense_tax_rates: - description: "The number of tax rates used per expense" - type: integer - example: 2 - invoice_task_project: - description: "A flag determining whether to include the project on invoices by default" - type: boolean - example: true - report_include_deleted: - description: "A flag determining whether to include deleted invoices in reports" - type: boolean - example: true - invoice_task_lock: - description: "A flag determining whether to lock tasks when invoiced" - type: boolean - example: true - convert_payment_currency: - description: "A flag determining whether to convert the payment currency" - type: boolean - example: true - convert_expense_currency: - description: "A flag determining whether to convert the expense currency" - type: boolean - example: true - notify_vendor_when_paid: - description: "A flag determining whether to notify the vendor when an expense is paid" - type: boolean - example: true - invoice_task_hours: - description: "A flag determining whether to include the task hours on invoices by default" - type: boolean - example: true - calculate_taxes: - description: "A flag determining whether to calculate taxes for the company" - type: boolean - example: true - tax_data: - description: "The tax data for the company" - type: object - e_invoice_certificate: - description: "The e-invoice certificate for the company" - type: string - example: '-----BEGIN CERTIFICATE-----' - e_invoice_certificate_passphrase: - description: "The e-invoice certificate passphrase for the company" - type: string - example: 'secret' - origin_tax_data: - description: "The origin tax data for the company" - type: object - invoice_task_project_header: - description: "A flag determining whether to include the project header on invoices by default" - type: boolean - example: true - invoice_task_item_description: - description: "A flag determining whether to include the item description on invoices by default" - type: boolean - example: true - - settings: - $ref: '#/components/schemas/CompanySettings' - type: object - RecurringInvoice: - properties: - id: - description: 'The hashed id of the recurring invoice' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - frequency_id: - description: 'The recurring invoice frequency' - type: number - example: '4' - remaining_cycles: - description: 'The number of invoices left to be generated' - type: number - example: '4' - number: - description: 'The recurringinvoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this recurring invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - line_items: - description: 'An array of objects which define the line items of the invoice' - type: object - example: '' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - type: object - - Quote: - properties: - id: - description: 'The unique hashed identifier for the quote' - type: string - example: Opnel5aKBz - user_id: - description: 'The unique hashed identifier for the user who created the quote' - type: string - example: '' - assigned_user_id: - description: 'The unique hashed identifier for the user assigned to the quote' - type: string - example: '' - company_id: - description: 'The unique hashed identifier for the company associated with the quote' - type: string - example: '' - client_id: - description: 'The unique hashed identifier for the client associated with the quote' - type: string - example: '' - status_id: - description: 'The status of the quote represented by a unique identifier' - type: string - example: '' - number: - description: 'The unique alpha-numeric quote number for the quote per company' - type: string - example: QUOTE_101 - po_number: - description: 'The purchase order number associated with the quote' - type: string - example: PO-1234 - terms: - description: 'The terms and conditions for the quote' - type: string - example: 'These are some quote terms. Valid for 14 days.' - public_notes: - description: 'Publicly visible notes associated with the quote' - type: string - example: 'These are public notes which the client may see' - private_notes: - description: 'Privately visible notes associated with the quote, not disclosed to the client' - type: string - example: 'These are private notes, not to be disclosed to the client' - footer: - description: 'The footer text of the quote' - type: string - example: 'The text goes in the footer of the quote' - custom_value1: - description: 'First custom value field for additional information' - type: string - example: 'A custom value' - custom_value2: - description: 'Second custom value field for additional information' - type: string - example: 'A custom value' - custom_value3: - description: 'Third custom value field for additional information' - type: string - example: 'A custom value' - custom_value4: - description: 'Fourth custom value field for additional information' - type: string - example: 'A custom value' - tax_name1: - description: 'The name of the first tax applied to the quote' - type: string - example: GST - tax_name2: - description: 'The name of the second tax applied to the quote' - type: string - example: VAT - tax_rate1: - description: 'The rate of the first tax applied to the quote' - type: number - format: float - example: 10.00 - tax_rate2: - description: 'The rate of the second tax applied to the quote' - type: number - format: float - example: 10.00 - tax_name3: - description: 'The name of the third tax applied to the quote' - type: string - example: '' - tax_rate3: - description: 'The rate of the third tax applied to the quote' - type: number - format: float - example: 10.00 - total_taxes: - description: 'The total amount of taxes for the quote' - type: number - format: float - example: 10.00 - line_items: - type: array - description: 'An array of objects which define the line items of the quote' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: 'The total amount of the quote before taxes and discounts' - type: number - format: float - example: 10.00 - balance: - description: 'The balance due for the quote after accounting for payments' - type: number - format: float - example: 10.00 - paid_to_date: - description: 'The total amount paid on the quote so far' - type: number - format: float - example: 10.00 - discount: - description: 'The discount amount or percentage applied to the quote' - type: number - format: float - example: 10.00 - partial: - description: 'The partial or deposit amount for the quote' - type: number - format: float - example: 10.00 - is_amount_discount: - description: 'Boolean flag indicating if the discount is a fixed amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Boolean flag indicating if the quote has been deleted' - type: boolean - example: false - uses_inclusive_taxes: - description: 'Boolean flag indicating if the taxes used are inclusive or exclusive' - type: boolean - example: true - date: - description: 'The date the quote was created' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the quote was sent to the client' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next scheduled date for sending a reminder for the quote' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the partial or deposit amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date for the total amount of the quote' - type: string - format: date - example: '1994-07-30' - settings: - $ref: '#/components/schemas/CompanySettings' - last_viewed: - description: 'The timestamp of the last time the quote was viewed' - type: number - format: integer - example: 1434342123 - updated_at: - description: 'The timestamp of the last update to the quote' - type: number - format: integer - example: 1434342123 - archived_at: - description: 'The timestamp of when the quote was archived' - type: number - format: integer - example: 1434342123 - custom_surcharge1: - description: 'First custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge3: - description: 'Third custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge4: - description: 'Fourth custom surcharge amount for the quote' - type: number - format: float - example: 10.00 - custom_surcharge_tax1: - description: 'Boolean flag indicating if taxes are charged on the first custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Boolean flag indicating if taxes are charged on the second custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Boolean flag indicating if taxes are charged on the third custom surcharge amount' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Boolean flag indicating if taxes are charged on the fourth custom surcharge amount' - type: boolean - example: true - type: object - Invoice: - properties: - id: - description: 'The invoice hashed id' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - readOnly: true - assigned_user_id: - description: 'The assigned user hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - readOnly: true - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - status_id: - description: 'The invoice status variable' - type: string - example: '4' - number: - description: 'The invoice number - is a unique alpha numeric number per invoice per company' - type: string - example: INV_101 - po_number: - description: 'The purchase order associated with this invoice' - type: string - example: PO-1234 - terms: - description: 'The invoice terms' - type: string - example: 'These are invoice terms' - public_notes: - description: 'The public notes of the invoice' - type: string - example: 'These are some public notes' - private_notes: - description: 'The private notes of the invoice' - type: string - example: 'These are some private notes' - footer: - description: 'The invoice footer notes' - type: string - example: '' - custom_value1: - description: 'A custom field value' - type: string - example: '2022-10-01' - custom_value2: - description: 'A custom field value' - type: string - example: 'Something custom' - custom_value3: - description: 'A custom field value' - type: string - example: '' - custom_value4: - description: 'A custom field value' - type: string - example: '' - tax_name1: - description: 'The tax name' - type: string - example: '' - tax_name2: - description: 'The tax name' - type: string - example: '' - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - total_taxes: - description: 'The total taxes for the invoice' - type: number - format: float - example: '10.00' - line_items: - type: array - description: 'An array of objects which define the line items of the invoice' - items: - $ref: '#/components/schemas/InvoiceItem' - invitations: - type: array - description: 'An array of objects which define the invitations of the invoice' - items: - $ref: '#/components/schemas/InvoiceInvitation' - amount: - description: 'The invoice amount' - type: number - format: float - example: '10.00' - balance: - description: 'The invoice balance' - type: number - format: float - example: '10.00' - paid_to_date: - description: 'The amount paid on the invoice to date' - type: number - format: float - example: '10.00' - discount: - description: 'The invoice discount, can be an amount or a percentage' - type: number - format: float - example: '10.00' - partial: - description: 'The deposit/partial amount' - type: number - format: float - example: '10.00' - is_amount_discount: - description: 'Flag determining if the discount is an amount or a percentage' - type: boolean - example: true - is_deleted: - description: 'Defines if the invoice has been deleted' - type: boolean - example: true - uses_inclusive_taxes: - description: 'Defines the type of taxes used as either inclusive or exclusive' - type: boolean - example: true - date: - description: 'The Invoice Date' - type: string - format: date - example: '1994-07-30' - last_sent_date: - description: 'The last date the invoice was sent out' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The Next date for a reminder to be sent' - type: string - format: date - example: '1994-07-30' - partial_due_date: - description: 'The due date for the deposit/partial amount' - type: string - format: date - example: '1994-07-30' - due_date: - description: 'The due date of the invoice' - type: string - format: date - example: '1994-07-30' - last_viewed: - description: Timestamp - type: number - format: integer - example: '1434342123' - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - custom_surcharge1: - description: 'First Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - project_id: - description: 'The project associated with this invoice' - type: string - example: Opnel5aKBz - auto_bill_tries: - description: 'The number of times the invoice has attempted to be auto billed' - type: integer - example: '1' - readOnly: true - auto_bill_enabled: - description: 'Boolean flag determining if the invoice is set to auto bill' - type: boolean - example: true - subscription_id: - description: 'The subscription associated with this invoice' - type: string - example: Opnel5aKBz - - type: object - ClientRequest: - required: - - contacts - - country_id - properties: - id: - description: 'The unique identifier of the client' - type: string - example: Opnel5aKBz - readOnly: true - contacts: - type: array - description: 'A list of contacts associated with the client' - items: - $ref: '#/components/schemas/ClientContactRequest' name: - description: 'The name of the client company or organization' + description: 'The token name' type: string - example: "Jim's Housekeeping" - website: - description: 'The website URL of the client company or organization' + example: 'Token Name' + token: + description: 'The token value' type: string - example: 'https://www.jims-housekeeping.com' - private_notes: - description: 'Notes that are only visible to the user who created the client' - type: string - example: 'Client prefers email communication over phone calls' - industry_id: - description: 'The unique identifier of the industry the client operates in' - type: number - example: '5' - size_id: - description: 'The unique identifier for the size category of the client company or organization' - type: number - example: '2' - address1: - description: "First line of the client's address" - type: string - example: '123 Main St' - address2: - description: "Second line of the client's address, if needed" - type: string - example: 'Apt 4B' - city: - description: 'The city the client is located in' - type: string - example: 'Beverly Hills' - state: - description: 'The state, province, or locality the client is located in' - type: string - example: 'California' - postal_code: - description: 'The postal code or ZIP code of the client' - type: string - example: '90210' - phone: - description: "The client's phone number" - type: string - example: '555-3434-3434' - country_id: - description: "The unique identifier of the client's country" - type: number - format: integer - example: '1' - custom_value1: - description: 'A custom field for storing additional information' - type: string - example: 'Preferred contact: Email' - custom_value2: - description: 'A custom field for storing additional information' - type: string - example: 'Account manager: John Doe' - custom_value3: - description: 'A custom field for storing additional information' - type: string - example: 'VIP client: Yes' - custom_value4: - description: 'A custom field for storing additional information' - type: string - example: 'Annual contract value: $50,000' - vat_number: - description: "The client's VAT (Value Added Tax) number, if applicable" - type: string - example: 'VAT123456' - id_number: - description: 'A unique identification number for the client, such as a tax ID or business registration number' - type: string - number: - description: 'A system-assigned unique number for the client, typically used for invoicing purposes' - type: string - example: 'CL-0001' - shipping_address1: - description: "First line of the client's shipping address" - type: string - example: '5 Wallaby Way' - shipping_address2: - description: "Second line of the client's shipping address, if needed" - type: string - example: 'Suite 5' - shipping_city: - description: "The city of the client's shipping address" - type: string - example: 'Perth' - shipping_state: - description: "The state, province, or locality of the client's shipping address" - type: string - example: 'Western Australia' - shipping_postal_code: - description: "The postal code or ZIP code of the client's shipping address" - type: string - example: '6110' - shipping_country_id: - description: "The unique identifier of the country for the client's shipping address" - type: number - format: integer - example: '4' - is_deleted: - description: 'A boolean value indicating whether the client has been deleted or not' - type: boolean - example: false - readOnly: true - group_settings_id: - description: 'The group settings assigned to the client' - type: string - example: Opnel5aKBz - routing_id: - description: 'The routing address id for e-invoicing for this client' - type: string - example: Opnel5aKBz3489-dfkiu-2239-sdsd - is_tax_exempt: - description: 'Flag which defines if the client is exempt from taxes' - type: boolean - example: false - has_valid_vat_number: - description: 'Flag which defines if the client has a valid VAT number' - type: boolean - example: false - readOnly: true - classification: - description: 'The classification of the client' - type: string - example: 'individual' - settings: - $ref: '#/components/schemas/ClientSettings' - type: object - Vendor: - properties: - id: - description: 'The hashed id of the vendor. This is a unique identifier for the vendor.' - type: string - example: Opnel5aKBz - readOnly: true - user_id: - description: 'The hashed id of the user who created the vendor. This is a unique identifier for the user.' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The hashed id of the assigned user to this vendor. This is a unique identifier for the user.' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company. This is a unique identifier for the company.' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed id of the client. This is a unique identifier for the client.' - type: string - example: Opnel5aKBz - contacts: - type: array - items: - $ref: '#/components/schemas/VendorContact' - description: 'An array of contacts associated with the vendor.' - name: - description: 'The name of the vendor.' - type: string - example: 'Harry cafe de wheels' - classification: - description: 'The classification of the vendor.' - type: string - example: 'individual' - website: - description: 'The website of the vendor.' - type: string - example: www.harry.com - private_notes: - description: 'The private notes of the vendor. These notes are only visible to users with appropriate permissions.' - type: string - example: 'Shhh, do not tell the vendor' - industry_id: - description: 'The industry id of the vendor. This is a unique identifier for the industry.' - type: string - example: '1' - size_id: - description: 'The size id of the vendor. This is a unique identifier for the size of the vendor.' - type: string - example: '' - address1: - description: 'The first line of the vendor''s address.' - type: string - example: '' - address2: - description: 'The second line of the vendor''s address.' - type: string - example: '' - city: - description: 'The city of the vendor''s address.' - type: string - example: '' - state: - description: 'The state of the vendor''s address.' - type: string - example: '' - postal_code: - description: 'The postal code of the vendor''s address.' - type: string - example: '' - phone: - description: 'The phone number of the vendor.' - type: string - example: 555-3434-3434 - country_id: - description: 'The country id of the vendor. This is a unique identifier for the country.' - type: string - example: '' - currency_id: - description: 'The currency id of the vendor. This is a unique identifier for the currency.' - type: string - example: '4' - custom_value1: - description: 'The value of the first custom field for the vendor.' - type: string - example: '' - custom_value2: - description: 'The value of the second custom field for the vendor.' - type: string - example: '' - custom_value3: - description: 'The value of the third custom field for the vendor.' - type: string - example: '' - custom_value4: - description: 'The value of the fourth custom field for the vendor.' - type: string - example: '' - vat_number: - description: 'The VAT number of the vendor.' - type: string - example: '' - id_number: - description: 'The ID number of the vendor.' - type: string - example: '' - number: - description: 'The number of the vendor' - type: string - example: '11234' - is_deleted: - description: 'Boolean flag determining if the vendor has been deleted' - type: boolean - example: true - language_id: - description: 'The language id of the vendor. This is a unique identifier for the language.' - type: string - example: '1' - vendor_hash: - description: 'The vendor hash of the vendor. This is a unique identifier for the vendor.' - type: string - example: 'aaa-sss-www' - readOnly: true - transaction_name: - description: 'The transaction name of the vendor.' - type: string - example: 'aaa-sss-www' - last_login: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - created_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - updated_at: - description: Timestamp - type: number - format: integer - example: '134341234234' - readOnly: true - settings: - $ref: '#/components/schemas/CompanySettings' - type: object - ClientSettings: - required: - - currency_id - properties: - currency_id: - description: 'The default currency id' - type: string - example: true - timezone_id: - description: 'The timezone id' - type: string - example: '15' - date_format_id: - description: 'The date format id' - type: string - example: '15' - military_time: - description: 'Toggles 12/24 hour time' - type: boolean - example: true - language_id: - description: 'The language id' - type: string - example: '1' - show_currency_code: - description: 'Toggles whether the currency symbol or code is shown' - type: boolean - example: true - payment_terms: - description: '-1 sets no payment term, 0 sets payment due immediately, positive integers indicates payment terms in days' - type: integer - example: '1' - company_gateway_ids: - description: 'A commad separate list of available gateways' - type: string - example: '1,2,3,4' - custom_value1: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value2: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value3: - description: 'A Custom Label' - type: string - example: 'Custom Label' - custom_value4: - description: 'A Custom Label' - type: string - example: 'Custom Label' - default_task_rate: - description: 'The default task rate' - type: number - format: float - example: '10.00' - send_reminders: - description: 'Toggles whether reminders are sent' - type: boolean - example: true - enable_client_portal_tasks: - description: 'Show/hide the tasks panel in the client portal' - type: boolean - example: true - email_style: - description: 'options include plain,light,dark,custom' - type: string - example: light - reply_to_email: - description: 'The reply to email address' - type: string - example: email@gmail.com - bcc_email: - description: 'A comma separate list of BCC emails' - type: string - example: 'email@gmail.com, contact@gmail.com' - pdf_email_attachment: - description: 'Toggles whether to attach PDF as attachment' - type: boolean - example: true - ubl_email_attachment: - description: 'Toggles whether to attach UBL as attachment' - type: boolean - example: true - email_style_custom: - description: 'The custom template' - type: string - example: '' - counter_number_applied: - description: 'enum when the invoice number counter is set, ie when_saved, when_sent, when_paid' - type: string - example: when_sent - quote_number_applied: - description: 'enum when the quote number counter is set, ie when_saved, when_sent' - type: string - example: when_sent - custom_message_dashboard: - description: 'A custom message which is displayed on the dashboard' - type: string - example: 'Please pay invoices immediately' - custom_message_unpaid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a unpaid invoice.' - type: string - example: 'Please pay invoices immediately' - custom_message_paid_invoice: - description: 'A custom message which is displayed in the client portal when a client is viewing a paid invoice.' - type: string - example: 'Thanks for paying this invoice!' - custom_message_unapproved_quote: - description: 'A custom message which is displayed in the client portal when a client is viewing a unapproved quote.' - type: string - example: 'Please approve quote' - lock_invoices: - description: 'Toggles whether invoices are locked once sent and cannot be modified further' - type: boolean - example: true - auto_archive_invoice: - description: 'Toggles whether a invoice is archived immediately following payment' - type: boolean - example: true - auto_archive_quote: - description: 'Toggles whether a quote is archived after being converted to a invoice' - type: boolean - example: true - auto_convert_quote: - description: 'Toggles whether a quote is converted to a invoice when approved' - type: boolean - example: true - inclusive_taxes: - description: 'Boolean flag determining whether inclusive or exclusive taxes are used' - type: boolean - example: true - task_number_pattern: - description: 'Allows customisation of the task number pattern' - type: string - example: '{$year}-{$counter}' - task_number_counter: - description: 'The incrementing counter for tasks' - type: integer - example: '1' - reminder_send_time: - description: 'Time from UTC +0 when the email will be sent to the client' - type: integer - example: '32400' - expense_number_pattern: - description: 'Allows customisation of the expense number pattern' - type: string - example: '{$year}-{$counter}' - expense_number_counter: - description: 'The incrementing counter for expenses' - type: integer - example: '1' - vendor_number_pattern: - description: 'Allows customisation of the vendor number pattern' - type: string - example: '{$year}-{$counter}' - vendor_number_counter: - description: 'The incrementing counter for vendors' - type: integer - example: '1' - ticket_number_pattern: - description: 'Allows customisation of the ticket number pattern' - type: string - example: '{$year}-{$counter}' - ticket_number_counter: - description: 'The incrementing counter for tickets' - type: integer - example: '1' - payment_number_pattern: - description: 'Allows customisation of the payment number pattern' - type: string - example: '{$year}-{$counter}' - payment_number_counter: - description: 'The incrementing counter for payments' - type: integer - example: '1' - invoice_number_pattern: - description: 'Allows customisation of the invoice number pattern' - type: string - example: '{$year}-{$counter}' - invoice_number_counter: - description: 'The incrementing counter for invoices' - type: integer - example: '1' - quote_number_pattern: - description: 'Allows customisation of the quote number pattern' - type: string - example: '{$year}-{$counter}' - quote_number_counter: - description: 'The incrementing counter for quotes' - type: integer - example: '1' - client_number_pattern: - description: 'Allows customisation of the client number pattern' - type: string - example: '{$year}-{$counter}' - client_number_counter: - description: 'The incrementing counter for clients' - type: integer - example: '1' - credit_number_pattern: - description: 'Allows customisation of the credit number pattern' - type: string - example: '{$year}-{$counter}' - credit_number_counter: - description: 'The incrementing counter for credits' - type: integer - example: '1' - recurring_invoice_number_prefix: - description: 'This string is prepended to the recurring invoice number' - type: string - example: R - reset_counter_frequency_id: - description: 'CONSTANT which is used to apply the frequency which the counters are reset' - type: integer - example: '1' - reset_counter_date: - description: 'The explicit date which is used to reset counters' - type: string - example: '2019-01-01' - counter_padding: - description: 'Pads the counter with leading zeros' - type: integer - example: '1' - shared_invoice_quote_counter: - description: 'Flags whether to share the counter for invoices and quotes' - type: boolean - example: true - update_products: - description: 'Determines if client fields are updated from third party APIs' - type: boolean - example: true - convert_products: - description: '' - type: boolean - example: true - fill_products: - description: 'Automatically fill products based on product_key' - type: boolean - example: true - invoice_terms: - description: 'The default invoice terms' - type: string - example: 'Invoice Terms are...' - quote_terms: - description: 'The default quote terms' - type: string - example: 'Quote Terms are...' - invoice_taxes: - description: 'Taxes can be applied to the invoice' - type: number - example: '1' - invoice_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' - quote_design_id: - description: 'The default design id (invoice, quote etc)' - type: string - example: '1' - invoice_footer: - description: 'The default invoice footer' - type: string - example: '1' - invoice_labels: - description: 'JSON string of invoice labels' - type: string - example: '1' - tax_rate1: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name1: - description: 'The tax name' - type: string - example: GST - tax_rate2: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name2: - description: 'The tax name' - type: string - example: GST - tax_rate3: - description: 'The tax rate (float)' - type: number - example: '10' - tax_name3: - description: 'The tax name' - type: string - example: GST - payment_type_id: - description: 'The default payment type id' - type: string - example: '1' - custom_fields: - description: 'JSON string of custom fields' - type: string - example: '{}' - email_footer: - description: 'The default email footer' - type: string - example: 'A default email footer' - email_sending_method: - description: 'The email driver to use to send email, options include default, gmail' - type: string - example: default - gmail_sending_user_id: - description: 'The hashed_id of the user account to send email from' - type: string - example: F76sd34D - email_subject_invoice: - description: '' - type: string - example: 'Your Invoice Subject' - email_subject_quote: - description: '' - type: string - example: 'Your Quote Subject' - email_subject_payment: - description: '' - type: string - example: 'Your Payment Subject' - email_template_invoice: - description: 'The full template for invoice emails' - type: string - example: '' - email_template_quote: - description: 'The full template for quote emails' - type: string - example: '' - email_template_payment: - description: 'The full template for payment emails' - type: string - example: '' - email_subject_reminder1: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder2: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder3: - description: 'Email subject for Reminder' - type: string - example: '' - email_subject_reminder_endless: - description: 'Email subject for endless reminders' - type: string - example: '' - email_template_reminder1: - description: 'The full template for Reminder 1' - type: string - example: '' - email_template_reminder2: - description: 'The full template for Reminder 2' - type: string - example: '' - email_template_reminder3: - description: 'The full template for Reminder 3' - type: string - example: '' - email_template_reminder_endless: - description: 'The full template for enless reminders' - type: string - example: '' - enable_portal_password: - description: 'Toggles whether a password is required to log into the client portal' - type: boolean - example: true - show_accept_invoice_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true - show_accept_quote_terms: - description: 'Toggles whether the terms dialogue is shown to the client' - type: boolean - example: true - require_invoice_signature: - description: 'Toggles whether a invoice signature is required' - type: boolean - example: true - require_quote_signature: - description: 'Toggles whether a quote signature is required' - type: boolean - example: true - name: - description: 'The company name' - type: string - example: 'Acme Co' - company_logo: - description: 'The company logo file' - type: object - example: logo.png - website: - description: 'The company website URL' - type: string - example: www.acme.com - address1: - description: 'The company address line 1' - type: string - example: 'Suite 888' - address2: - description: 'The company address line 2' - type: string - example: '5 Jimbo Way' - city: - description: 'The company city' - type: string - example: Sydney - state: - description: 'The company state' - type: string - example: Florisa - postal_code: - description: 'The company zip/postal code' - type: string - example: '90210' - phone: - description: 'The company phone' - type: string - example: 555-213-3948 - email: - description: 'The company email' - type: string - example: joe@acme.co - country_id: - description: 'The country ID' - type: string - example: '1' - vat_number: - description: 'The company VAT/TAX ID number' - type: string - example: '32 120 377 720' - page_size: - description: 'The default page size' - type: string - example: A4 - font_size: - description: 'The font size' - type: number - example: '9' - primary_font: - description: 'The primary font' - type: string - example: roboto - secondary_font: - description: 'The secondary font' - type: string - example: roboto - hide_paid_to_date: - description: 'Flags whether to hide the paid to date field' - type: boolean - example: false - embed_documents: - description: 'Toggled whether to embed documents in the PDF' - type: boolean - example: false - all_pages_header: - description: 'The header for the PDF' - type: boolean - example: false - all_pages_footer: - description: 'The footer for the PDF' - type: boolean - example: false - document_email_attachment: - description: 'Toggles whether to attach documents in the email' - type: boolean - example: false - enable_client_portal_password: - description: 'Toggles password protection of the client portal' - type: boolean - example: false - enable_email_markup: - description: 'Toggles the use of markdown in emails' - type: boolean - example: false - enable_client_portal_dashboard: - description: 'Toggles whether the client dashboard is shown in the client portal' - type: boolean - example: false - enable_client_portal: - description: 'Toggles whether the entire client portal is displayed to the client, or only the context' - type: boolean - example: false - email_template_statement: - description: 'The body of the email for statements' - type: string - example: 'template matter' - email_subject_statement: - description: 'The subject of the email for statements' - type: string - example: 'subject matter' - signature_on_pdf: - description: 'Toggles whether the signature (if available) is displayed on the PDF' - type: boolean - example: false - quote_footer: - description: 'The default quote footer' - type: string - example: 'the quote footer' - email_subject_custom1: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 1' - email_subject_custom2: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 2' - email_subject_custom3: - description: 'Custom reminder template subject' - type: string - example: 'Custom Subject 3' - email_template_custom1: - description: 'Custom reminder template body' - type: string - example: '' - email_template_custom2: - description: 'Custom reminder template body' - type: string - example: '' - email_template_custom3: - description: 'Custom reminder template body' - type: string - example: '' - enable_reminder1: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - enable_reminder2: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - enable_reminder3: - description: 'Toggles whether this reminder is enabled' - type: boolean - example: false - num_days_reminder1: - description: 'The Reminder interval' - type: number - example: '9' - num_days_reminder2: - description: 'The Reminder interval' - type: number - example: '9' - num_days_reminder3: - description: 'The Reminder interval' - type: number - example: '9' - schedule_reminder1: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - schedule_reminder2: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - schedule_reminder3: - description: '(enum: after_invoice_date, before_due_date, after_due_date)' - type: string - example: after_invoice_date - late_fee_amount1: - description: 'The late fee amount for reminder 1' - type: number - example: 10 - late_fee_amount2: - description: 'The late fee amount for reminder 2' - type: number - example: 20 - late_fee_amount3: - description: 'The late fee amount for reminder 2' - type: number - example: 100 - endless_reminder_frequency_id: - description: 'The frequency id of the endless reminder' - type: string - example: '1' - client_online_payment_notification: - description: 'Determines if a client should receive the notification for a online payment' - type: boolean - example: false - client_manual_payment_notification: - description: 'Determines if a client should receive the notification for a manually entered payment' - type: boolean - example: false - enable_e_invoice: - description: 'Determines if e-invoicing is enabled' - type: boolean - example: false - default_expense_payment_type_id: - description: 'The default payment type for expenses' - type: string - example: '0' - e_invoice_type: - description: 'The e-invoice type' - type: string - example: 'EN16931' - mailgun_endpoint: - description: 'The mailgun endpoint - used to determine whether US or EU endpoints are used' - type: string - example: 'api.mailgun.net or api.eu.mailgun.net' - client_initiated_payments: - description: 'Determines if clients can initiate payments directly from the client portal' - type: boolean - example: false - client_initiated_payments_minimum: - description: 'The minimum amount a client can pay' - type: number - example: 10 - sync_invoice_quote_columns: - description: 'Determines if invoice and quote columns are synced for the PDF rendering, or if they use their own columns' - type: boolean - example: false - show_task_item_description: - description: 'Determines if the task item description is shown on the invoice' - type: boolean - example: false - allow_billable_task_items: - description: 'Determines if task items can be marked as billable' - type: boolean - example: false - accept_client_input_quote_approval: - description: 'Determines if clients can approve quotes and also pass through a PO Number reference' - type: boolean - example: false - custom_sending_email: - description: 'When using Mailgun or Postmark, the FROM email address can be customized using this setting.' - type: string - example: 'bob@gmail.com' - show_paid_stamp: - description: 'Determines if the PAID stamp is shown on the invoice' - type: boolean - example: false - show_shipping_address: - description: 'Determines if the shipping address is shown on the invoice' - type: boolean - example: false - company_logo_size: - description: 'The size of the company logo on the PDF - percentage value between 0 and 100' - type: number - example: 100 - show_email_footer: - description: 'Determines if the email footer is shown on emails' - type: boolean - example: false - email_alignment: - description: 'The alignment of the email body text, options include left / center / right' - type: string - example: 'left' - auto_bill_standard_invoices: - description: 'Determines if standard invoices are automatically billed when they are created or due' - type: boolean - example: false - postmark_secret: - description: 'The Postmark secret API key' - type: string - example: '123456' - mailgun_secret: - description: 'The Mailgun secret API key' - type: string - example: '123456' - mailgun_domain: - description: 'The Mailgun domain' - type: string - example: 'sandbox123456.mailgun.org' - send_email_on_mark_paid: - description: 'Determines if an email is sent when an invoice is marked as paid' - type: boolean - example: false - vendor_portal_enable_uploads: - description: 'Determines if vendors can upload files to the portal' - type: boolean - example: false - besr_id: - description: 'The BESR ID' - type: string - example: '123456' - qr_iban: - description: 'The IBAN for the QR code' - type: string - example: 'CH123456' - email_subject_purchase_order: - description: 'The email subject for purchase orders' - type: string - example: 'Purchase Order' - email_template_purchase_order: - description: 'The email template for purchase orders' - type: string - example: 'Please see attached your purchase order.' - require_purchase_order_signature: - description: 'Determines if a signature is required on purchase orders' - type: boolean - example: false - purchase_order_public_notes: - description: 'The public notes for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_terms: - description: 'The terms for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_footer: - description: 'The footer for purchase orders' - type: string - example: 'Please see attached your purchase order.' - purchase_order_design_id: - description: 'The design id for purchase orders' - type: string - example: 'hd677df' - purchase_order_number_pattern: - description: 'The pattern for purchase order numbers' - type: string - example: 'PO-000000' - purchase_order_number_counter: - description: 'The counter for purchase order numbers' - type: number - example: 1 - page_numbering_alignment: - description: 'The alignment for page numbering: options include left / center / right' - type: string - example: 'left' - page_numbering: - description: 'Determines if page numbering is enabled on Document PDFs' - type: boolean - example: false - auto_archive_invoice_cancelled: - description: 'Determines if invoices are automatically archived when they are cancelled' - type: boolean - example: false - email_from_name: - description: 'The FROM name for emails when using Custom emailers' - type: string - example: 'Bob Smith' - show_all_tasks_client_portal: - description: 'Determines if all tasks are shown on the client portal' - type: boolean - example: false - entity_send_time: - description: 'The time that emails are sent. The time is localized to the clients locale, integer values from 1 - 24' - type: integer - example: 9 - shared_invoice_credit_counter: - description: 'Determines if the invoice and credit counter are shared' - type: boolean - example: false - reply_to_name: - description: 'The reply to name for emails' - type: string - example: 'Bob Smith' - hide_empty_columns_on_pdf: - description: 'Determines if empty columns are hidden on PDFs' - type: boolean - example: false - enable_reminder_endless: - description: 'Determines if endless reminders are enabled' - type: boolean - example: false - use_credits_payment: - description: 'Determines if credits can be used as a payment method' - type: boolean - example: false - recurring_invoice_number_pattern: - description: 'The pattern for recurring invoice numbers' - type: string - example: 'R-000000' - recurring_invoice_number_counter: - description: 'The counter for recurring invoice numbers' - type: number - example: 1 - client_portal_under_payment_minimum: - description: 'The minimum payment payment' - type: number - example: 10 - auto_bill_date: - description: 'Determines when the invoices are auto billed, options are on_send_date (when the invoice is sent) or on_due_date (when the invoice is due))' - type: string - example: 'on_send_date' - primary_color: - description: 'The primary color for the client portal / document highlights' - type: string - example: '#ffffff' - secondary_color: - description: 'The secondary color for the client portal / document highlights' - type: string - example: '#ffffff' - client_portal_allow_under_payment: - description: 'Determines if clients can pay invoices under the invoice amount due' - type: boolean - example: false - client_portal_allow_over_payment: - description: 'Determines if clients can pay invoices over the invoice amount' - type: boolean - example: false - auto_bill: - description: 'Determines how autobilling is applied for recurring invoices. off (no auto billed), always (always auto bill), optin (The user must opt in to auto billing), optout (The user must opt out of auto billing' - type: string - example: 'off' - client_portal_terms: - description: 'The terms which are displayed on the client portal' - type: string - example: 'Please see attached your invoice.' - client_portal_privacy_policy: - description: 'The privacy policy which is displayed on the client portal' - type: string - example: 'These are the terms of use for using the client portal.' - client_can_register: - description: 'Determines if clients can register on the client portal' - type: boolean - example: false - portal_design_id: - description: 'The design id for the client portal' - type: string - example: 'hd677df' - late_fee_endless_percent: - description: 'The late fee percentage for endless late fees' - type: number - example: 10 - late_fee_endless_amount: - description: 'The late fee amount for endless late fees' - type: number - example: 10 - auto_email_invoice: - description: 'Determines if invoices are automatically emailed when they are created' - type: boolean - example: false - email_signature: - description: 'The email signature for emails' - type: string - example: 'Bob Smith' - type: object - RecurringExpense: - properties: - id: - description: 'The hashed id of the recurring expense' - type: string - example: Opnel5aKBz - user_id: - description: 'The hashed id of the user who created the recurring expense' - type: string - example: Opnel5aKBz - assigned_user_id: - description: 'The hashed id of the user assigned to this recurring expense' - type: string - example: Opnel5aKBz - company_id: - description: 'The hashed id of the company' - type: string - example: Opnel5aKBz - client_id: - description: 'The hashed id of the client' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The hashed id of the invoice' - type: string - example: Opnel5aKBz - bank_id: - description: 'The id of the bank associated with this recurring expense' - type: string - example: '22' - invoice_currency_id: - description: 'The currency id of the invoice associated with this recurring expense' - type: string - example: '1' - expense_currency_id: - description: 'The currency id of the expense associated with this recurring expense' - type: string - example: '1' - invoice_category_id: - description: 'The category id of the invoice' - type: string - example: '1' - payment_type_id: - description: 'The payment type id' - type: string - example: '1' - private_notes: - description: 'The recurring expense private notes' - type: string - example: 'Private and confidential' - public_notes: - description: 'The recurring expense public notes' - type: string - example: 'This is the best client in the world' - transaction_reference: - description: 'The recurring expense transaction reference' - type: string - example: EXP-1223-2333 - transcation_id: - description: 'The transaction id of the recurring expense' - type: string - example: '1233312312' - custom_value1: - description: 'Custom value field' - type: string - example: $1000 - custom_value2: - description: 'Custom value field' - type: string - example: '2022-10-10' - custom_value3: - description: 'Custom value field' - type: string - example: 'short text' - custom_value4: - description: 'Custom value field' - type: string - example: 'very long text' - tax_name1: - description: 'The tax name' - type: string - example: GST - tax_name2: - description: 'The tax name' - type: string - example: VAT - tax_rate1: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'The tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'The tax name' - type: string - example: '' - tax_rate3: - description: 'The tax rate' - type: number - format: float - example: '10.00' - amount: - description: 'The total amount of the recurring expense' - type: number - format: float - example: '10.00' - frequency_id: - description: 'The frequency this recurring expense fires' - type: number - format: int - example: '1' - remaining_cycles: - description: 'The number of remaining cycles for this recurring expense' - type: number - format: int - example: '1' - foreign_amount: - description: 'The foreign currency amount of the recurring expense' - type: number - format: float - example: '10.00' - exchange_rate: - description: 'The exchange rate for the expernse' - type: number - format: float - example: '0.80' - date: - description: 'The date of the expense' - type: string - example: '' - payment_date: - description: 'The date the expense was paid' - type: string - example: '' - should_be_invoiced: - description: 'Boolean flag determining if the expense should be invoiced' - type: boolean - example: true - is_deleted: - description: 'Boolean flag determining if the recurring expense is deleted' - type: boolean - example: true - last_sent_date: - description: 'The Date it was sent last' - type: string - format: date - example: '1994-07-30' - next_send_date: - description: 'The next send date' - type: string - format: date - example: '1994-07-30' - invoice_documents: - description: 'Boolean flag determining if the documents associated with this expense should be passed onto the invoice if it is converted to an invoice' - type: boolean - example: true - updated_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - archived_at: - description: Timestamp - type: number - format: integer - example: '1434342123' - type: object - Activity: - properties: - id: - description: 'The id field of the activity' - type: string - example: Opnel5aKBz - activity_type_id: - description: 'The activity type id' - type: string - example: Opnel5aKBz - client_id: - description: 'The client hashed id' - type: string - example: Opnel5aKBz - company_id: - description: 'The company hashed id' - type: string - example: Opnel5aKBz - user_id: - description: 'The user hashed id' - type: string - example: Opnel5aKBz - invoice_id: - description: 'The invoice hashed id' - type: string - example: Opnel5aKBz - payment_id: - description: 'The payment hashed id' - type: string - example: Opnel5aKBz - credit_id: - description: 'The credit hashed id' - type: string - example: Opnel5aKBz - updated_at: - description: 'Unixtimestamp the last time the record was updated' - type: integer - example: '343421434' - expense_id: - description: 'The expense hashed id' - type: string - example: Opnel5aKBz + example: AS3df3jUUH765fhfd9KJuidj3JShjA is_system: - description: 'Defines is the activity was performed by the system' + description: 'Determines whether the token is created by the system rather than a user' + type: boolean + example: 'true' + type: object + CompanyUser: + properties: + permissions: + description: 'The company user permissions' + type: string + example: '[create_invoice]' + settings: + description: 'Settings that are used for the frontend applications to store user preferences / metadata' + type: object + example: 'json object' + react_settings: + description: 'Dedicated settings object for the react web application' + type: object' + example: 'json object' + is_owner: + description: 'Determines whether the user owns this company' type: boolean example: true - contact_id: - description: 'The contact hashed id' - type: string - example: Opnel5aKBz - task_id: - description: 'The task hashed id' - type: string - example: Opnel5aKBz - notes: - description: 'Activity Notes' - type: string - example: Opnel5aKBz - token_id: - description: 'The hashed ID of the token who performed the action' - type: string - example: Opnel5aKBz - ip: - description: 'The IP Address of the user who performed the action' - type: string - example: 192.168.1.252 + is_admin: + description: 'Determines whether the user is the admin of this company' + type: boolean + example: true + is_locked: + description: 'Determines whether the users access to this company has been locked' + type: boolean + example: true + updated_at: + description: 'The last time the record was modified, format Unix Timestamp' + type: integer + example: '1231232312321' + deleted_at: + description: 'Timestamp when the user was archived, format Unix Timestamp' + type: integer + example: '12312312321' + account: + $ref: '#/components/schemas/Account' + company: + $ref: '#/components/schemas/Company' user: $ref: '#/components/schemas/User' - client: - $ref: '#/components/schemas/Client' - contact: - $ref: '#/components/schemas/ClientContact' - recurring_invoice: - $ref: '#/components/schemas/RecurringInvoice' - invoice: - $ref: '#/components/schemas/Invoice' - credit: - $ref: '#/components/schemas/Credit' - quote: - $ref: '#/components/schemas/Quote' - payment: - $ref: '#/components/schemas/Payment' - expense: - $ref: '#/components/schemas/Expense' - task: - $ref: '#/components/schemas/Task' - purchase_order: - $ref: '#/components/schemas/PurchaseOrder' - vendor: - $ref: '#/components/schemas/Vendor' - vendor_contact: - $ref: '#/components/schemas/VendorContact' + token: + $ref: '#/components/schemas/CompanyToken' type: object - Expense: + InvoiceInvitationRequest: + required: + - client_contact_id properties: id: - description: 'The expense hashed id' + description: 'The entity invitation hashed id' type: string example: Opnel5aKBz - user_id: - description: 'The user hashed id' + readOnly: true + client_contact_id: + description: 'The client contact hashed id' type: string - example: '' - assigned_user_id: - description: 'The assigned user hashed id' + example: Opnel5aKBz + key: + description: 'The invitation key' type: string - example: '' - company_id: - description: 'The company hashed id' + example: Opnel5aKBz4343343566236gvbb + readOnly: true + link: + description: 'The invitation link' type: string - example: '' - client_id: - description: 'The client hashed id' + example: 'https://www.example.com/invitations/Opnel5aKBz4343343566236gvbb' + readOnly: true + sent_date: + description: 'The invitation sent date' type: string - example: '' - invoice_id: - description: 'The related invoice hashed id' + format: date-time + readOnly: true + viewed_date: + description: 'The invitation viewed date' type: string - example: '' - bank_id: - description: 'The bank id related to this expense' + format: date-time + readOnly: true + opened_date: + description: 'The invitation opened date' type: string - example: '' - invoice_currency_id: - description: 'The currency id of the related invoice' - type: string - example: '' - expense_currency_id: - description: 'The currency id of the expense' - type: string - example: '' - invoice_category_id: - description: 'The invoice category id' - type: string - example: '' - payment_type_id: - description: 'The payment type id' - type: string - example: '' - recurring_expense_id: - description: 'The related recurring expense this expense was created from' - type: string - example: '' - private_notes: - description: 'The private notes of the expense' - type: string - example: '' - public_notes: - description: 'The public notes of the expense' - type: string - example: '' - transaction_reference: - description: 'The transaction references of the expense' - type: string - example: '' - transcation_id: - description: 'The transaction id of the expense' - type: string - example: '' - custom_value1: - description: 'A custom value' - type: string - example: '' - custom_value2: - description: 'A custom value' - type: string - example: '' - custom_value3: - description: 'A custom value' - type: string - example: '' - custom_value4: - description: 'A custom value' - type: string - example: '' - tax_name1: - description: 'Tax name' - type: string - example: '' - tax_name2: - description: 'Tax name' - type: string - example: '' - tax_rate1: - description: 'Tax rate' - type: number - format: float - example: '10.00' - tax_rate2: - description: 'Tax rate' - type: number - format: float - example: '10.00' - tax_name3: - description: 'Tax name' - type: string - example: '' - tax_rate3: - description: 'Tax rate' - type: number - format: float - example: '10.00' - amount: - description: 'The total expense amont' - type: number - format: float - example: '10.00' - foreign_amount: - description: 'The total foreign amount of the expense' - type: number - format: float - example: '10.00' - exchange_rate: - description: 'The exchange rate at the time of the expense' - type: number - format: float - example: '0.80' - date: - description: 'The expense date formate Y-m-d' - type: string - example: '2022-12-01' - payment_date: - description: 'The date of payment for the expense, format Y-m-d' - type: string - example: '' - should_be_invoiced: - description: 'Flag whether the expense should be invoiced' - type: boolean - example: true - is_deleted: - description: 'Boolean determining whether the expense has been deleted' - type: boolean - example: true - invoice_documents: - description: 'Passing the expense documents over to the invoice' - type: boolean - example: true + format: date-time + readOnly: true updated_at: - description: Timestamp + description: 'Timestamp' type: number format: integer example: '1434342123' + readOnly: true archived_at: - description: Timestamp + description: 'Timestamp' type: number format: integer example: '1434342123' - type: object - Error: - properties: - message: - description: 'Something terrible went wrong' + readOnly: true + email_error: + description: 'The email error' type: string - example: 'Unexpected error' - code: - description: 'The HTTP error code, ie 5xx 4xx' - type: integer - example: '500' - type: object + example: 'The email error' + readOnly: true + email_status: + description: 'The email status' + type: string + readOnly: true + InvoiceInvitation: properties: id: @@ -20759,378 +21176,47 @@ components: type: string readOnly: true - - ProductBulkAction: - required: - - action - - ids - properties: - action: - type: string - example: archive - description: 'The action to perform ie. archive / restore / delete / set_tax_id' - ids: - type: array - items: - format: string - type: string - example: 2J234DFA,D2J234DFA,D2J234DFA - description: string array of client hashed ids - tax_id: - type: string - example: '1' - description: | - The tax rate id to set on the list of products - - The following constants are available (default = '1') - - ``` - PRODUCT_TYPE_PHYSICAL = '1' - PRODUCT_TYPE_SERVICE = '2' - PRODUCT_TYPE_DIGITAL = '3' - PRODUCT_TYPE_SHIPPING = '4' - PRODUCT_TYPE_EXEMPT = '5' - PRODUCT_TYPE_REDUCED_TAX = '6' - PRODUCT_TYPE_OVERRIDE_TAX = '7' - PRODUCT_TYPE_ZERO_RATED = '8' - PRODUCT_TYPE_REVERSE_TAX = '9' - ``` - type: object - User: + Design: properties: id: - description: 'The hashed id of the user' - type: string - example: Opnel5aKBz - readOnly: true - first_name: - description: 'The first name of the user' - type: string - example: Brad - last_name: - description: 'The last name of the user' - type: string - example: Pitt - email: - description: 'The users email address' - type: string - example: brad@pitt.com - phone: - description: 'The users phone number' - type: string - example: 555-1233-23232 - signature: - description: 'The users sign off signature' - type: string - example: 'Have a nice day!' - avatar: - description: 'The users avatar' - type: string - example: 'https://url.to.your/avatar.png' - accepted_terms_version: - description: 'The version of the invoice ninja terms that has been accepted by the user' - type: string - example: 1.0.1 - readOnly: true - oauth_user_id: - description: 'The provider id of the oauth entity' - type: string - example: jkhasdf789as6f675sdf768sdfs - readOnly: true - oauth_provider_id: - description: 'The oauth entity id' - type: string - example: google - readOnly: true - language_id: - description: 'The language id of the user' - type: string - example: 1 - verified_phone_number: - description: 'Boolean flag if the user has their phone verified. Required to settings up 2FA' - type: boolean - example: true - readOnly: true - sms_verification_code: - description: 'The sms verification code for the user. Required to settings up 2FA' - type: string - example: '123456' - readOnly: true - oauth_user_token_expiry: - description: 'The expiry date of the oauth token' - type: string - example: '2022-10-10' - readOnly: true - has_password: - description: 'Boolean flag determining if the user has a password' - type: boolean - example: true - readOnly: true - last_confirmed_email_address: - description: 'The last confirmed email address of the user' - type: string - example: 'bob@gmail.com' - readOnly: true - custom_value1: - description: 'A custom value' - type: string - example: 'Custom value 1' - custom_value2: - description: 'A custom value' - type: string - example: '$1000' - custom_value3: - description: 'A custom value' - type: string - example: 'Custom value 3' - custom_value4: - description: 'A custom value' - type: string - example: 'Custom value 4' - is_deleted: - description: 'Boolean flag determining if the user has been deleted' - type: boolean - example: true - readOnly: true - google_2fa_secret: - description: 'The google 2fa secret for the user' - type: string - example: '123456' - readOnly: true - type: object - Account: - properties: - id: - description: 'The account hashed id' + description: 'The design hashed id' type: string example: AS3df3A - account_sms_verified: - description: 'Boolean flag if the account has been verified by sms' + name: + description: 'The design name' type: string + example: Beauty + design: + description: 'The design HTML' + type: string + example: '' + is_custom: + description: 'Flag to determine if the design is a custom user design' + type: boolean example: true - type: object - Credit: - properties: - id: - description: "The unique hashed ID of the credit" - type: string - example: Opnel5aKBz - user_id: - description: "The unique hashed ID of the user associated with the credit" - type: string - example: 1a2b3c4d5e - assigned_user_id: - description: "The unique hashed ID of the assigned user responsible for the credit" - type: string - example: 6f7g8h9i0j - company_id: - description: "The unique hashed ID of the company associated with the credit" - type: string - example: k1l2m3n4o5 - client_id: - description: "The unique hashed ID of the client associated with the credit" - type: string - example: p1q2r3s4t5 - status_id: - description: "The ID representing the current status of the credit" - type: string - example: 3 - invoice_id: - description: "The unique hashed ID of the linked invoice to which the credit is applied" - type: string - example: u1v2w3x4y5 - number: - description: "The unique alphanumeric credit number per company" - type: string - example: QUOTE_101 - po_number: - description: "The purchase order number referred to by the credit" - type: string - example: PO_12345 - terms: - description: "The terms associated with the credit" - type: string - example: "Net 30" - public_notes: - description: "Public notes for the credit" - type: string - example: "Thank you for your business." - private_notes: - description: "Private notes for internal use, not visible to the client" - type: string - example: "Client is requesting a discount." - footer: - description: "The footer text for the credit" - type: string - example: "Footer text goes here." - custom_value1: - description: "Custom value 1 for additional credit information" - type: string - example: "Custom data 1" - custom_value2: - description: "Custom value 2 for additional credit information" - type: string - example: "Custom data 2" - custom_value3: - description: "Custom value 3 for additional credit information" - type: string - example: "Custom data 3" - custom_value4: - description: "Custom value 4 for additional credit information" - type: string - example: "Custom data 4" - tax_name1: - description: "The name of the first tax applied to the credit" - type: string - example: "VAT" - tax_name2: - description: "The name of the second tax applied to the credit" - type: string - example: "GST" - tax_rate1: - description: "The rate of the first tax applied to the credit" - type: number - format: float - example: 10.00 - tax_rate2: - description: "The rate of the second tax applied to the credit" - type: number - format: float - example: 5.00 - tax_name3: - description: "The name of the third tax applied to the credit" - type: string - example: "PST" - tax_rate3: - description: "The rate of the third tax applied to the credit" - type: number - format: float - example: 8.00 - total_taxes: - description: "The total amount of taxes for the credit" - type: number - format: float - example: 23.00 - line_items: - type: array - description: 'An array of objects which define the line items of the credit' - items: - $ref: '#/components/schemas/InvoiceItem' - amount: - description: "The total amount of the credit" - type: number - format: float - example: 100.00 - balance: - description: "The outstanding balance of the credit" - type: number - format: float - example: 50.00 - paid_to_date: - description: "The total amount paid to date for the credit" - type: number - format: float - example: 50.00 - discount: - description: "The discount applied to the credit" - type: number - format: float - example: 10.00 - partial: - description: "The partial amount applied to the credit" - type: number - format: float - example: 20.00 - is_amount_discount: - description: "Indicates whether the discount applied is a fixed amount or a percentage" + is_active: + description: 'Flag to determine if the design is available for use' type: boolean example: true is_deleted: - description: "Indicates whether the credit has been deleted" - type: boolean - example: false - uses_inclusive_taxes: - description: "Indicates whether the tax rates applied to the credit are inclusive or exclusive" + description: 'Flag to determine if the design is deleted' type: boolean example: true - date: - description: "The date the credit was issued" - type: string - format: date - example: "1994-07-30" - last_sent_date: - description: "The date the credit was last sent out" - type: string - format: date - example: "1994-07-30" - next_send_date: - description: "The next scheduled date for sending a credit reminder" - type: string - format: date - example: "1994-07-30" - partial_due_date: - description: "The due date for the partial amount of the credit" - type: string - format: date - example: "1994-07-30" - due_date: - description: "The due date for the total amount of the credit" - type: string - format: date - example: "1994-07-30" - settings: - $ref: "#/components/schemas/CompanySettings" - last_viewed: - description: "The timestamp of the last time the credit was viewed" + created_at: + description: Timestamp type: number format: integer - example: 1434342123 + example: '134341234234' updated_at: - description: "The timestamp of the last time the credit was updated" + description: Timestamp type: number format: integer - example: 1434342123 - archived_at: - description: "The timestamp of the last time the credit was archived" + example: '134341234234' + deleted_at: + description: Timestamp type: number format: integer - example: 1434342123 - custom_surcharge1: - description: "First custom surcharge amount" - type: number - format: float - example: 10.00 - custom_surcharge2: - description: 'Second Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge3: - description: 'Third Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge4: - description: 'Fourth Custom Surcharge' - type: number - format: float - example: '10.00' - custom_surcharge_tax1: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax2: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax3: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true - custom_surcharge_tax4: - description: 'Toggles charging taxes on custom surcharge amounts' - type: boolean - example: true + example: '134341234234' type: object tags: - name: login diff --git a/openapi/paths/clients.yaml b/openapi/paths/clients.yaml index 90b9afd093aa..b89c8517f68c 100644 --- a/openapi/paths/clients.yaml +++ b/openapi/paths/clients.yaml @@ -653,5 +653,91 @@ $ref: '#/components/responses/429' 5XX: description: 'Server error' + default: + $ref: '#/components/responses/default' + /api/v1/reactivate_email/{bounce_id}: + post: + tags: + - clients + summary: 'Removes email suppression of a user in the system' + description: 'Emails are suppressed by PostMark, when they receive a Hard bounce / Spam Complaint. This endpoint allows you to remove the suppression and send emails to the user again.' + operationId: reactivateEmail + parameters: + - $ref: '#/components/parameters/X-API-TOKEN' + - $ref: '#/components/parameters/X-Requested-With' + - $ref: '#/components/parameters/include' + - name: bounce_id + in: path + description: 'The postmark Bounce ID reference' + required: true + schema: + type: string + format: string + example: 123243 + responses: + 200: + description: 'Success' + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: '#/components/headers/X-MINIMUM-CLIENT-VERSION' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + 400: + description: 'Postmark exception - generated if the suppression cannot be removed for any reason' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' + default: + $ref: '#/components/responses/default' + /api/v1/clients/{client}/updateTaxData: + post: + tags: + - clients + summary: 'Update tax data' + description: 'Updates the clients tax data - if their address has changed' + operationId: updateClientTaxData + parameters: + - $ref: '#/components/parameters/X-API-TOKEN' + - $ref: '#/components/parameters/X-Requested-With' + - $ref: '#/components/parameters/include' + - name: client + in: path + description: 'The Client Hashed ID reference' + required: true + schema: + type: string + format: string + example: V2J234DFA + responses: + 200: + description: 'Success' + headers: + X-MINIMUM-CLIENT-VERSION: + $ref: '#/components/headers/X-MINIMUM-CLIENT-VERSION' + X-RateLimit-Remaining: + $ref: '#/components/headers/X-RateLimit-Remaining' + X-RateLimit-Limit: + $ref: '#/components/headers/X-RateLimit-Limit' + 400: + description: 'Postmark exception - generated if the suppression cannot be removed for any reason' + 401: + $ref: '#/components/responses/401' + 403: + $ref: '#/components/responses/403' + 422: + $ref: '#/components/responses/422' + 429: + $ref: '#/components/responses/429' + 5XX: + description: 'Server error' default: $ref: '#/components/responses/default' \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 61e44f833902..fa22de99e55d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -164,6 +164,8 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('clients/{client}/{mergeable_client}/merge', [ClientController::class, 'merge'])->name('clients.merge')->middleware('password_protected'); Route::post('clients/bulk', [ClientController::class, 'bulk'])->name('clients.bulk'); + Route::post('reactivate_email/{bounce_id}', [ClientController::class, 'reactivateEmail'])->name('clients.reactivate_email'); + Route::post('filters/{entity}', [FilterController::class, 'index'])->name('filters'); Route::resource('client_gateway_tokens', ClientGatewayTokenController::class); From 17e4962435446ae1373f6596a4b335e8cab1df31 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Mon, 23 Oct 2023 09:48:16 +1100 Subject: [PATCH 43/51] Add reactivate email paths for PostMark --- .../Client/ReactivateClientEmailRequest.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 app/Http/Requests/Client/ReactivateClientEmailRequest.php diff --git a/app/Http/Requests/Client/ReactivateClientEmailRequest.php b/app/Http/Requests/Client/ReactivateClientEmailRequest.php new file mode 100644 index 000000000000..413747aeb1d2 --- /dev/null +++ b/app/Http/Requests/Client/ReactivateClientEmailRequest.php @@ -0,0 +1,28 @@ + Date: Mon, 23 Oct 2023 14:02:53 +1100 Subject: [PATCH 44/51] Updates for removing suppressions from emails --- app/Http/Controllers/ClientController.php | 39 +++++++++++++++++-- app/Jobs/PostMark/ProcessPostmarkWebhook.php | 26 +++++++++++++ app/Models/Client.php | 5 +-- app/Models/Company.php | 20 ++++++++++ .../CreditInvitationTransformer.php | 1 + .../InvoiceInvitationTransformer.php | 1 + .../PurchaseOrderInvitationTransformer.php | 1 + .../QuoteInvitationTransformer.php | 1 + .../RecurringInvoiceInvitationTransformer.php | 1 + 9 files changed, 88 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/ClientController.php b/app/Http/Controllers/ClientController.php index db90d3ca05c0..1dfff875d34d 100644 --- a/app/Http/Controllers/ClientController.php +++ b/app/Http/Controllers/ClientController.php @@ -14,6 +14,8 @@ namespace App\Http\Controllers; use App\Utils\Ninja; use App\Models\Client; use App\Models\Account; +use App\Models\Company; +use App\Models\SystemLog; use Postmark\PostmarkClient; use Illuminate\Http\Response; use App\Factory\ClientFactory; @@ -38,6 +40,7 @@ use App\Http\Requests\Client\UpdateClientRequest; use App\Http\Requests\Client\UploadClientRequest; use App\Http\Requests\Client\DestroyClientRequest; use App\Http\Requests\Client\ReactivateClientEmailRequest; +use App\Jobs\PostMark\ProcessPostmarkWebhook; /** * Class ClientController. @@ -221,7 +224,7 @@ class ClientController extends BaseController } }); - return $this->listResponse(Client::withTrashed()->company()->whereIn('id', $request->ids)); + return $this->listResponse(Client::query()->withTrashed()->company()->whereIn('id', $request->ids)); } /** @@ -320,12 +323,42 @@ class ClientController extends BaseController * Reactivate a client email * * @param ReactivateClientEmailRequest $request - * @param string $bounce_id + * @param string $bounce_id //could also be the invitationId * @return \Illuminate\Http\JsonResponse */ public function reactivateEmail(ReactivateClientEmailRequest $request, string $bounce_id) { - + /** @var \App\Models\User $user */ + $user = auth()->user(); + + if(stripos($bounce_id, '-') !== false){ + $log = + SystemLog::query() + ->where('company_id', $user->company()->id) + ->where('type_id', SystemLog::TYPE_WEBHOOK_RESPONSE) + ->where('category_id', SystemLog::CATEGORY_MAIL) + ->whereJsonContains('log', ['MessageID' => $bounce_id]) + ->orderBy('id', 'desc') + ->first(); + + $resolved_bounce_id = false; + + if($log && ($log?->log['ID'] ?? false)){ + $resolved_bounce_id = $log->log['ID'] ?? false; + } + + if(!$resolved_bounce_id){ + $ppwebhook = new ProcessPostmarkWebhook([]); + $resolved_bounce_id = $ppwebhook->getBounceId($bounce_id); + } + + if(!$resolved_bounce_id){ + return response()->json(['message' => 'Bounce ID not found'], 400); + } + + $bounce_id = $resolved_bounce_id; + } + $postmark = new PostmarkClient(config('services.postmark.token')); try { diff --git a/app/Jobs/PostMark/ProcessPostmarkWebhook.php b/app/Jobs/PostMark/ProcessPostmarkWebhook.php index 35a90f79b340..25f2d57c8cda 100644 --- a/app/Jobs/PostMark/ProcessPostmarkWebhook.php +++ b/app/Jobs/PostMark/ProcessPostmarkWebhook.php @@ -339,6 +339,32 @@ class ProcessPostmarkWebhook implements ShouldQueue } } + public function getRawMessage(string $message_id) + { + + $postmark = new PostmarkClient(config('services.postmark.token')); + $messageDetail = $postmark->getOutboundMessageDetails($message_id); + return $messageDetail; + + } + + + public function getBounceId(string $message_id): ?int + { + + $messageDetail = $this->getRawMessage($message_id); + + + $event = collect($messageDetail->messageevents)->first(function ($event) { + + return $event?->Details?->BounceID ?? false; + + }); + + return $event?->Details?->BounceID ?? null; + + } + private function fetchMessage(): array { if(strlen($this->request['MessageID']) < 1){ diff --git a/app/Models/Client.php b/app/Models/Client.php index 2c06040cc671..a056a103493f 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -306,10 +306,7 @@ class Client extends BaseModel implements HasLocalePreference return $this->hasMany(ClientContact::class)->where('is_primary', true); } - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function company() + public function company(): BelongsTo { return $this->belongsTo(Company::class); } diff --git a/app/Models/Company.php b/app/Models/Company.php index 1de20b8d694b..8d097d3b8baa 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -786,6 +786,26 @@ class Company extends BaseModel return $this->hasMany(CompanyUser::class)->withTrashed(); } + public function invoice_invitations(): HasMany + { + return $this->hasMany(InvoiceInvitation::class); + } + + public function quote_invitations(): HasMany + { + return $this->hasMany(QuoteInvitation::class); + } + + public function credit_invitations(): HasMany + { + return $this->hasMany(CreditInvitation::class); + } + + public function purchase_order_invitations(): HasMany + { + return $this->hasMany(PurchaseOrderInvitation::class); + } + /** * @return \App\Models\User|null */ diff --git a/app/Transformers/CreditInvitationTransformer.php b/app/Transformers/CreditInvitationTransformer.php index 3be2395ae37f..ce53e3829d9c 100644 --- a/app/Transformers/CreditInvitationTransformer.php +++ b/app/Transformers/CreditInvitationTransformer.php @@ -33,6 +33,7 @@ class CreditInvitationTransformer extends EntityTransformer 'created_at' => (int) $invitation->created_at, 'email_status' => $invitation->email_status ?: '', 'email_error' => (string) $invitation->email_error, + 'message_id' => (string) $invitation->message_id ?: '', ]; } } diff --git a/app/Transformers/InvoiceInvitationTransformer.php b/app/Transformers/InvoiceInvitationTransformer.php index 9f7aaa582b0b..0caa6bb9990b 100644 --- a/app/Transformers/InvoiceInvitationTransformer.php +++ b/app/Transformers/InvoiceInvitationTransformer.php @@ -33,6 +33,7 @@ class InvoiceInvitationTransformer extends EntityTransformer 'created_at' => (int) $invitation->created_at, 'email_status' => $invitation->email_status ?: '', 'email_error' => (string) $invitation->email_error, + 'message_id' => (string) $invitation->message_id ?: '', ]; } } diff --git a/app/Transformers/PurchaseOrderInvitationTransformer.php b/app/Transformers/PurchaseOrderInvitationTransformer.php index 5a4474fbc0ca..0c3ad649af76 100644 --- a/app/Transformers/PurchaseOrderInvitationTransformer.php +++ b/app/Transformers/PurchaseOrderInvitationTransformer.php @@ -24,6 +24,7 @@ class PurchaseOrderInvitationTransformer extends EntityTransformer 'created_at' => (int) $invitation->created_at, 'email_status' => $invitation->email_status ?: '', 'email_error' => (string) $invitation->email_error, + 'message_id' => (string) $invitation->message_id ?: '', ]; } } diff --git a/app/Transformers/QuoteInvitationTransformer.php b/app/Transformers/QuoteInvitationTransformer.php index 02c84d841dc0..394e9339c90e 100644 --- a/app/Transformers/QuoteInvitationTransformer.php +++ b/app/Transformers/QuoteInvitationTransformer.php @@ -33,6 +33,7 @@ class QuoteInvitationTransformer extends EntityTransformer 'created_at' => (int) $invitation->created_at, 'email_status' => $invitation->email_status ?: '', 'email_error' => (string) $invitation->email_error, + 'message_id' => (string) $invitation->message_id ?: '', ]; } } diff --git a/app/Transformers/RecurringInvoiceInvitationTransformer.php b/app/Transformers/RecurringInvoiceInvitationTransformer.php index 7c7b31e8584d..6dc852e37e50 100644 --- a/app/Transformers/RecurringInvoiceInvitationTransformer.php +++ b/app/Transformers/RecurringInvoiceInvitationTransformer.php @@ -33,6 +33,7 @@ class RecurringInvoiceInvitationTransformer extends EntityTransformer 'created_at' => (int) $invitation->created_at, 'email_status' => $invitation->email_status ?: '', 'email_error' => (string) $invitation->email_error, + 'message_id' => (string) $invitation->message_id ?: '', ]; } } From 30f349ae13db6cf40ba702b886d9aac80b5048b3 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Oct 2023 12:50:10 +1100 Subject: [PATCH 45/51] set auto sync to true --- app/Factory/BankIntegrationFactory.php | 1 + app/Http/Controllers/Bank/YodleeController.php | 3 ++- app/Http/Controllers/BankIntegrationController.php | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Factory/BankIntegrationFactory.php b/app/Factory/BankIntegrationFactory.php index e49934c5a6dd..dff55255e59b 100644 --- a/app/Factory/BankIntegrationFactory.php +++ b/app/Factory/BankIntegrationFactory.php @@ -30,6 +30,7 @@ class BankIntegrationFactory $bank_integration->bank_account_type = ''; $bank_integration->balance = 0; $bank_integration->currency = ''; + $bank_integration->auto_sync = 1; return $bank_integration; } diff --git a/app/Http/Controllers/Bank/YodleeController.php b/app/Http/Controllers/Bank/YodleeController.php index 8de11cf308a2..bf4f1fc368cf 100644 --- a/app/Http/Controllers/Bank/YodleeController.php +++ b/app/Http/Controllers/Bank/YodleeController.php @@ -87,7 +87,8 @@ class YodleeController extends BaseController $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; $bank_integration->from_date = now()->subYear(); - + $bank_integration->auto_sync = true; + $bank_integration->save(); } } diff --git a/app/Http/Controllers/BankIntegrationController.php b/app/Http/Controllers/BankIntegrationController.php index e80a9b31a537..3e404ff73f6b 100644 --- a/app/Http/Controllers/BankIntegrationController.php +++ b/app/Http/Controllers/BankIntegrationController.php @@ -230,6 +230,8 @@ class BankIntegrationController extends BaseController $bank_integration->nickname = $account['nickname']; $bank_integration->balance = $account['current_balance']; $bank_integration->currency = $account['account_currency']; + $bank_integration->auto_sync = true; + $bank_integration->save(); } } From e69869e06cd13f2e5b64052e8575f3274b9592ef Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Oct 2023 15:08:13 +1100 Subject: [PATCH 46/51] Add invitation to payment emails --- app/Jobs/Payment/EmailPayment.php | 14 ++++++++++++-- app/Jobs/Payment/EmailRefundPayment.php | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php index edcdf83a4c3f..46d4c3bad46f 100644 --- a/app/Jobs/Payment/EmailPayment.php +++ b/app/Jobs/Payment/EmailPayment.php @@ -83,11 +83,21 @@ class EmailPayment implements ShouldQueue $invitation = null; + $nmo = new NinjaMailerObject; + if ($this->payment->invoices && $this->payment->invoices->count() >= 1) { - $invitation = $this->payment->invoices->first()->invitations()->first(); + + if($this->contact){ + $invitation = $this->payment->invoices->first()->invitations()->where('contact_id', $this->contact->id)->first(); + } + else + $invitation = $this->payment->invoices->first()->invitations()->first(); + + if($invitation) + $nmo->invitation = $invitation; } - $nmo = new NinjaMailerObject; + $nmo->mailable = new TemplateEmail($email_builder, $this->contact, $invitation); $nmo->to_user = $this->contact; $nmo->settings = $this->settings; diff --git a/app/Jobs/Payment/EmailRefundPayment.php b/app/Jobs/Payment/EmailRefundPayment.php index a6e5de75b2a2..0da81a92533b 100644 --- a/app/Jobs/Payment/EmailRefundPayment.php +++ b/app/Jobs/Payment/EmailRefundPayment.php @@ -88,11 +88,22 @@ class EmailRefundPayment implements ShouldQueue $invitation = null; + $nmo = new NinjaMailerObject; + if ($this->payment->invoices && $this->payment->invoices->count() >= 1) { - $invitation = $this->payment->invoices->first()->invitations()->first(); + + if($this->contact) { + $invitation = $this->payment->invoices->first()->invitations()->where('contact_id', $this->contact->id)->first(); + } else { + $invitation = $this->payment->invoices->first()->invitations()->first(); + } + + if($invitation) + $nmo->invitation = $invitation; } - $nmo = new NinjaMailerObject; + + $nmo->mailable = new TemplateEmail($email_builder, $this->contact, $invitation); $nmo->to_user = $this->contact; $nmo->settings = $this->settings; From 867ad809aaa4712480f5c183cfc03b23009d02e9 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Oct 2023 15:16:48 +1100 Subject: [PATCH 47/51] set contact on invitation for payment emails --- app/Jobs/Payment/EmailPayment.php | 2 +- app/Jobs/Payment/EmailRefundPayment.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Jobs/Payment/EmailPayment.php b/app/Jobs/Payment/EmailPayment.php index 46d4c3bad46f..dd6a7f129a17 100644 --- a/app/Jobs/Payment/EmailPayment.php +++ b/app/Jobs/Payment/EmailPayment.php @@ -88,7 +88,7 @@ class EmailPayment implements ShouldQueue if ($this->payment->invoices && $this->payment->invoices->count() >= 1) { if($this->contact){ - $invitation = $this->payment->invoices->first()->invitations()->where('contact_id', $this->contact->id)->first(); + $invitation = $this->payment->invoices->first()->invitations()->where('client_contact_id', $this->contact->id)->first(); } else $invitation = $this->payment->invoices->first()->invitations()->first(); diff --git a/app/Jobs/Payment/EmailRefundPayment.php b/app/Jobs/Payment/EmailRefundPayment.php index 0da81a92533b..bbc062f79169 100644 --- a/app/Jobs/Payment/EmailRefundPayment.php +++ b/app/Jobs/Payment/EmailRefundPayment.php @@ -93,7 +93,7 @@ class EmailRefundPayment implements ShouldQueue if ($this->payment->invoices && $this->payment->invoices->count() >= 1) { if($this->contact) { - $invitation = $this->payment->invoices->first()->invitations()->where('contact_id', $this->contact->id)->first(); + $invitation = $this->payment->invoices->first()->invitations()->where('client_contact_id', $this->contact->id)->first(); } else { $invitation = $this->payment->invoices->first()->invitations()->first(); } From 56dd89ff8d999f185869efd4c9d2c5b59d27da91 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Oct 2023 10:08:36 +1100 Subject: [PATCH 48/51] Minor catches for live preview --- app/Http/Controllers/PreviewController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 8b5d499d8de8..4d4afc0fa68c 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -106,12 +106,14 @@ class PreviewController extends BaseController $invitation->{$request->entity} = $entity_obj; } + if(empty($entity_obj->design_id)) + $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); + /** Generate variables */ $html = new HtmlEngine($invitation); $html->settings = $settings; $variables = $html->generateLabelsAndValues(); - $design = \App\Models\Design::query()->withTrashed()->find($entity_obj->design_id ?? 2); /* Catch all in case migration doesn't pass back a valid design */ From e7a1f8ef74d142fb1756a1be31763d1384a69a15 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Oct 2023 10:47:54 +1100 Subject: [PATCH 49/51] Fixes for default terms/footers etc --- app/Http/Controllers/PreviewController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/PreviewController.php b/app/Http/Controllers/PreviewController.php index 4d4afc0fa68c..6c2d217fbe4d 100644 --- a/app/Http/Controllers/PreviewController.php +++ b/app/Http/Controllers/PreviewController.php @@ -100,9 +100,9 @@ class PreviewController extends BaseController /** Update necessary objecty props */ if(!$entity_obj->id) { $entity_obj->design_id = intval($this->decodePrimaryKey($settings->{$entity_prop."_design_id"})); - $entity_obj->footer = $settings->{$entity_prop."_footer"}; - $entity_obj->terms = $settings->{$entity_prop."_terms"}; - $entity_obj->public_notes = $request->getClient()->public_notes; + $entity_obj->footer = empty($entity_obj->footer) ? $settings->{$entity_prop."_footer"} : $entity_obj->footer; + $entity_obj->terms = empty($entity_obj->term) ? $settings->{$entity_prop."_terms"} : $entity_obj->terms; + $entity_obj->public_notes = empty($entity_obj->public_notes) ? $request->getClient()->public_notes : $entity_obj->public_notes; $invitation->{$request->entity} = $entity_obj; } From 43f904bdbe69e68e82a1ca6141817744ec234dba Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Oct 2023 12:04:36 +1100 Subject: [PATCH 50/51] Add send test email checks --- app/Console/Commands/SendTestEmails.php | 63 ++++++++----------- .../Controllers/StripeConnectController.php | 1 + app/Listeners/Mail/MailSentListener.php | 1 + app/Mail/TestMailServer.php | 1 - 4 files changed, 28 insertions(+), 38 deletions(-) diff --git a/app/Console/Commands/SendTestEmails.php b/app/Console/Commands/SendTestEmails.php index 0b0aef8bdfcb..5a113e8f2f58 100644 --- a/app/Console/Commands/SendTestEmails.php +++ b/app/Console/Commands/SendTestEmails.php @@ -11,16 +11,18 @@ namespace App\Console\Commands; -use App\DataMapper\CompanySettings; -use App\DataMapper\DefaultSettings; -use App\Jobs\Mail\NinjaMailerJob; -use App\Jobs\Mail\NinjaMailerObject; -use App\Mail\Migration\MaxCompanies; +use Faker\Factory; +use App\Models\User; use App\Models\Account; use App\Models\Company; -use App\Models\User; -use Faker\Factory; +use App\Mail\TestMailServer; use Illuminate\Console\Command; +use App\Jobs\Mail\NinjaMailerJob; +use App\DataMapper\CompanySettings; +use App\DataMapper\DefaultSettings; +use App\Jobs\Mail\NinjaMailerObject; +use App\Mail\Migration\MaxCompanies; +use Illuminate\Support\Facades\Mail; class SendTestEmails extends Command { @@ -55,39 +57,26 @@ class SendTestEmails extends Command */ public function handle() { - $faker = Factory::create(); - $account = Account::factory()->create(); - - $user = User::factory()->create([ - 'account_id' => $account->id, - 'confirmation_code' => '123', - 'email' => $faker->safeEmail(), - 'first_name' => 'John', - 'last_name' => 'Doe', - ]); - - $company = Company::factory()->create([ - 'account_id' => $account->id, - ]); - - $user->companies()->attach($company->id, [ - 'account_id' => $account->id, - 'is_owner' => 1, - 'is_admin' => 1, - 'is_locked' => 0, - 'permissions' => '', - 'notifications' => CompanySettings::notificationDefaults(), - //'settings' => DefaultSettings::userSettings(), - 'settings' => null, - ]); + $to_user = User::first(); $nmo = new NinjaMailerObject; - $nmo->mailable = new MaxCompanies($user->account->companies()->first()); - $nmo->company = $user->account->companies()->first(); - $nmo->settings = $user->account->companies()->first()->settings; - $nmo->to_user = $user; + $nmo->mailable = new TestMailServer('Email Server Works!', config('mail.from.address')); + $nmo->company = $to_user->account->companies()->first(); + $nmo->settings = $to_user->account->companies()->first()->settings; + $nmo->to_user = $to_user; - (new NinjaMailerJob($nmo))->handle(); + try { + + Mail::raw("Test Message", function ($message) { + $message->to(config('mail.from.address')) + ->from(config('mail.from.address'), config('mail.from.name')) + ->subject('Test Email'); + }); + + + } catch(\Exception $e) { + $this->info("Error sending email: " . $e->getMessage()); + } } } diff --git a/app/Http/Controllers/StripeConnectController.php b/app/Http/Controllers/StripeConnectController.php index 5f8900e6705f..edb97e486d14 100644 --- a/app/Http/Controllers/StripeConnectController.php +++ b/app/Http/Controllers/StripeConnectController.php @@ -117,6 +117,7 @@ class StripeConnectController extends BaseController 'refresh_token' => $response->refresh_token, 'access_token' => $response->access_token, 'appleDomainVerification' => '', + // "statementDescriptor" => "", ]; $company_gateway->setConfig($payload); diff --git a/app/Listeners/Mail/MailSentListener.php b/app/Listeners/Mail/MailSentListener.php index b6228b8bf1d8..34bd8a5300e2 100644 --- a/app/Listeners/Mail/MailSentListener.php +++ b/app/Listeners/Mail/MailSentListener.php @@ -41,6 +41,7 @@ class MailSentListener implements ShouldQueue */ public function handle(MessageSent $event) { + if (!Ninja::isHosted()) { return; } diff --git a/app/Mail/TestMailServer.php b/app/Mail/TestMailServer.php index a0c7f784f563..917a1fa9f039 100644 --- a/app/Mail/TestMailServer.php +++ b/app/Mail/TestMailServer.php @@ -17,7 +17,6 @@ use Illuminate\Queue\SerializesModels; class TestMailServer extends Mailable { - // use Queueable, SerializesModels; public $support_messages; From ab6f86f40458414c2e88c03ef03ad6d20d7693fb Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 25 Oct 2023 12:13:06 +1100 Subject: [PATCH 51/51] Minor fixes for task filters --- app/Filters/TaskFilters.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/Filters/TaskFilters.php b/app/Filters/TaskFilters.php index 0c564af5a4f6..481fb4429902 100644 --- a/app/Filters/TaskFilters.php +++ b/app/Filters/TaskFilters.php @@ -136,8 +136,13 @@ class TaskFilters extends QueryFilters $status_parameters = explode(',', $value); - if(count($status_parameters) >= 1) - $this->builder->whereIn('status_id', $this->transformKeys($status_parameters)); + if(count($status_parameters) >= 1){ + + $this->builder->where(function ($query) use ($status_parameters) { + $query->whereIn('status_id', $this->transformKeys($status_parameters))->whereNull('invoice_id'); + }); + + } return $this->builder; }