From 7a750f930a3a476bc252477f61d85b086716ceb0 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 12 Jul 2024 11:01:27 -0400 Subject: [PATCH 01/69] add client and invoice transformers --- .../Quickbooks/ClientTransformer.php | 91 ++++++++++++++++ .../Quickbooks/InvoiceTransformer.php | 103 ++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 app/Import/Transformer/Quickbooks/ClientTransformer.php create mode 100644 app/Import/Transformer/Quickbooks/InvoiceTransformer.php diff --git a/app/Import/Transformer/Quickbooks/ClientTransformer.php b/app/Import/Transformer/Quickbooks/ClientTransformer.php new file mode 100644 index 000000000000..c70ced06e922 --- /dev/null +++ b/app/Import/Transformer/Quickbooks/ClientTransformer.php @@ -0,0 +1,91 @@ + 'CompanyName', + 'phone' => 'PrimaryPhone.FreeFormNumber', + 'country_id' => 'BillAddr.Country', + 'state' => 'BillAddr.CountrySubDivisionCode', + 'address1' => 'BillAddr.Line1', + 'city' => 'BillAddr.City', + 'postal_code' => 'BillAddr.PostalCode', + 'shipping_country_id' => 'ShipAddr.Country', + 'shipping_state' => 'ShipAddr.CountrySubDivisionCode', + 'shipping_address1' => 'ShipAddr.Line1', + 'shipping_city' => 'ShipAddr.City', + 'shipping_postal_code' => 'ShipAddr.PostalCode', + 'public_notes' => 'Notes' + ]; + + /** + * Transforms a Customer array into a ClientContact model. + * + * @param array $data + * @return array|bool + */ + public function transform($data) + { + $transformed_data = []; + // Assuming 'customer_name' is equivalent to 'CompanyName' + if (isset($data['CompanyName']) && $this->hasClient($data['CompanyName'])) { + return false; + } + + foreach($this->fillable as $key => $field) { + $transformed_data[$key] = method_exists($this, $method = sprintf("get%s", str_replace(".","",$field)) )? call_user_func([$this, $method],$data,$field) : $this->getString($data, $field); + } + + $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data)->toArray() + $this->getContacts($data, $field); + + return $transformed_data; + } + + public function getString($data, $field) + { + return Arr::get($data, $field); + } + + protected function getContacts($data, $field = null) { + return [ 'contacts' => [ + (new ClientContact())->fill([ + 'first_name' => $this->getString($data, 'GivenName'), + 'last_name' => $this->getString($data, 'FamilyName'), + 'phone' => $this->getString($data, 'PrimaryPhone.FreeFormNumber'), + 'email' => $this->getString($data, 'PrimaryEmailAddr.Address'), + ]) ] + ]; + } + + + public function getShipAddrCountry($data,$field) { + return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c); + } + + public function getBillAddrCountry($data,$field) { + return is_null(($c = $this->getString($data,$field))) ? null : $this->getCountryId($c); + } + +} diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php new file mode 100644 index 000000000000..30bcf80bed1b --- /dev/null +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -0,0 +1,103 @@ + "TotalAmt", + 'line_items' => "Line", + 'due_date' => "DueDate", + 'partial' => "Deposit", + 'balance' => "Balance", + 'comments' => "CustomerMemo", + 'number' => "DocNumber", + 'created_at' => "CreateTime", + 'updated_at' => "LastUpdatedTime" + ]; + + public function transform($data) + { + $transformed = []; + + foreach ($this->fillable as $key => $field) { + $transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field)); + } + + return (new Model)->fillable(array_keys($this->fillable))->fill($transformed)->toArray(); + } + + public function getTotalAmt($data) + { + return (float) $this->getString($data,'TotalAmt'); + } + + public function getLine($data) + { + return array_map(function ($item) { + return [ + 'description' => $this->getString($item,'Description'), + 'quantity' => $this->getString($item,'SalesItemLineDetail.Qty'), + 'unit_price' =>$this->getString($item,'SalesItemLineDetail.UnitPrice'), + 'amount' => $this->getString($item,'Amount') + ]; + }, array_filter($this->getString($data,'Line'), function ($item) { + return $this->getString($item,'DetailType') === 'SalesItemLineDetail'; + })); + } + + public function getString($data,$field) { + return Arr::get($data,$field); + } + + public function getDueDate($data) + { + return $this->parseDateOrNull($data, 'DueDate'); + } + + public function getDeposit($data) + { + return (float) $this->getString($data,'Deposit'); + } + + public function getBalance($data) + { + return (float) $this->getString($data,'Balance'); + } + + public function getCustomerMemo($data) + { + return $this->getString($data,'CustomerMemo.value'); + } + + public function getCreateTime($data) + { + return $this->parseDateOrNull($this->getString($data,'MetaData.CreateTime')); + } + + public function getLastUpdatedTime($data) + { + return $this->parseDateOrNull($this->getString($data,'MetaData.LastUpdatedTime')); + } +} From 4531df27594121496cac034dd016e4548fbca56e Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 12 Jul 2024 11:01:55 -0400 Subject: [PATCH 02/69] add test data --- .../Response/Http/200-cutomer-response.txt | 51 ++++++ .../Response/Http/200-invoice-response.txt | 161 ++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 tests/Mock/Response/Http/200-cutomer-response.txt create mode 100644 tests/Mock/Response/Http/200-invoice-response.txt diff --git a/tests/Mock/Response/Http/200-cutomer-response.txt b/tests/Mock/Response/Http/200-cutomer-response.txt new file mode 100644 index 000000000000..c399f0f09438 --- /dev/null +++ b/tests/Mock/Response/Http/200-cutomer-response.txt @@ -0,0 +1,51 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sun, 05 May 2013 08:52:09 GMT +Content-Type: application/json;charset=utf-8 +Content-Length: 127 +Connection: keep-alive +Cache-Control: no-cache, no-store +Access-Control-Max-Age: 300 +Access-Control-Allow-Credentials: true + +{ + "Customer": { + "PrimaryEmailAddr": { + "Address": "Surf@Intuit.com" + }, + "SyncToken": "0", + "domain": "QBO", + "GivenName": "Bill", + "DisplayName": "Bill's Windsurf Shop", + "BillWithParent": false, + "FullyQualifiedName": "Bill's Windsurf Shop", + "CompanyName": "Bill's Windsurf Shop", + "FamilyName": "Lucchini", + "sparse": false, + "PrimaryPhone": { + "FreeFormNumber": "(415) 444-6538" + }, + "Active": true, + "Job": false, + "BalanceWithJobs": 85.0, + "BillAddr": { + "City": "Half Moon Bay", + "Line1": "12 Ocean Dr.", + "PostalCode": "94213", + "Lat": "37.4307072", + "Long": "-122.4295234", + "CountrySubDivisionCode": "CA", + "Id": "3" + }, + "PreferredDeliveryMethod": "Print", + "Taxable": false, + "PrintOnCheckName": "Bill's Windsurf Shop", + "Balance": 85.0, + "Id": "2", + "MetaData": { + "CreateTime": "2014-09-11T16:49:28-07:00", + "LastUpdatedTime": "2014-09-18T12:56:01-07:00" + } + }, + "time": "2015-07-23T11:04:15.496-07:00" +} \ No newline at end of file diff --git a/tests/Mock/Response/Http/200-invoice-response.txt b/tests/Mock/Response/Http/200-invoice-response.txt new file mode 100644 index 000000000000..8038934e64ea --- /dev/null +++ b/tests/Mock/Response/Http/200-invoice-response.txt @@ -0,0 +1,161 @@ +HTTP/1.1 200 OK +Server: nginx +Date: Sun, 05 May 2013 08:52:09 GMT +Content-Type: application/json;charset=utf-8 +Content-Length: 127 +Connection: keep-alive +Cache-Control: no-cache, no-store +Access-Control-Max-Age: 300 +Access-Control-Allow-Credentials: true + +{ + "Invoice": { + "TxnDate": "2014-09-19", + "domain": "QBO", + "PrintStatus": "NeedToPrint", + "SalesTermRef": { + "value": "3" + }, + "TotalAmt": 362.07, + "Line": [ + { + "Description": "Rock Fountain", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 1, + "UnitPrice": 275, + "ItemRef": { + "name": "Rock Fountain", + "value": "5" + } + }, + "LineNum": 1, + "Amount": 275.0, + "Id": "1" + }, + { + "Description": "Fountain Pump", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 1, + "UnitPrice": 12.75, + "ItemRef": { + "name": "Pump", + "value": "11" + } + }, + "LineNum": 2, + "Amount": 12.75, + "Id": "2" + }, + { + "Description": "Concrete for fountain installation", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 5, + "UnitPrice": 9.5, + "ItemRef": { + "name": "Concrete", + "value": "3" + } + }, + "LineNum": 3, + "Amount": 47.5, + "Id": "3" + }, + { + "DetailType": "SubTotalLineDetail", + "Amount": 335.25, + "SubTotalLineDetail": {} + } + ], + "DueDate": "2014-10-19", + "ApplyTaxAfterDiscount": false, + "DocNumber": "1037", + "sparse": false, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "ProjectRef": { + "value": "39298045" + }, + "Deposit": 0, + "Balance": 362.07, + "CustomerRef": { + "name": "Sonnenschein Family Store", + "value": "24" + }, + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 26.82, + "TaxLine": [ + { + "DetailType": "TaxLineDetail", + "Amount": 26.82, + "TaxLineDetail": { + "NetAmountTaxable": 335.25, + "TaxPercent": 8, + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true + } + } + ] + }, + "SyncToken": "0", + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "BillEmail": { + "Address": "Familiystore@intuit.com" + }, + "ShipAddr": { + "City": "Middlefield", + "Line1": "5647 Cypress Hill Ave.", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681", + "CountrySubDivisionCode": "CA", + "Id": "25" + }, + "EmailStatus": "NotSet", + "BillAddr": { + "Line4": "Middlefield, CA 94303", + "Line3": "5647 Cypress Hill Ave.", + "Line2": "Sonnenschein Family Store", + "Line1": "Russ Sonnenschein", + "Long": "-122.1141681", + "Lat": "37.4238562", + "Id": "95" + }, + "MetaData": { + "CreateTime": "2014-09-19T13:16:17-07:00", + "LastUpdatedTime": "2014-09-19T13:16:17-07:00" + }, + "CustomField": [ + { + "DefinitionId": "1", + "StringValue": "102", + "Type": "StringType", + "Name": "Crew #" + } + ], + "Id": "130" + }, + "time": "2015-07-24T10:48:27.082-07:00" +} \ No newline at end of file From d11cc1d010ca851a48e3a1834349638f251e1ad9 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 12 Jul 2024 11:02:22 -0400 Subject: [PATCH 03/69] add test sample data --- tests/Mock/Response/Quickbooks/customer.json | 42 +++++ tests/Mock/Response/Quickbooks/invoice.json | 152 +++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 tests/Mock/Response/Quickbooks/customer.json create mode 100644 tests/Mock/Response/Quickbooks/invoice.json diff --git a/tests/Mock/Response/Quickbooks/customer.json b/tests/Mock/Response/Quickbooks/customer.json new file mode 100644 index 000000000000..8dae12a9f307 --- /dev/null +++ b/tests/Mock/Response/Quickbooks/customer.json @@ -0,0 +1,42 @@ + +{ + "Customer": { + "PrimaryEmailAddr": { + "Address": "Surf@Intuit.com" + }, + "SyncToken": "0", + "domain": "QBO", + "GivenName": "Bill", + "DisplayName": "Bill's Windsurf Shop", + "BillWithParent": false, + "FullyQualifiedName": "Bill's Windsurf Shop", + "CompanyName": "Bill's Windsurf Shop", + "FamilyName": "Lucchini", + "sparse": false, + "PrimaryPhone": { + "FreeFormNumber": "(415) 444-6538" + }, + "Active": true, + "Job": false, + "BalanceWithJobs": 85.0, + "BillAddr": { + "City": "Half Moon Bay", + "Line1": "12 Ocean Dr.", + "PostalCode": "94213", + "Lat": "37.4307072", + "Long": "-122.4295234", + "CountrySubDivisionCode": "CA", + "Id": "3" + }, + "PreferredDeliveryMethod": "Print", + "Taxable": false, + "PrintOnCheckName": "Bill's Windsurf Shop", + "Balance": 85.0, + "Id": "2", + "MetaData": { + "CreateTime": "2014-09-11T16:49:28-07:00", + "LastUpdatedTime": "2014-09-18T12:56:01-07:00" + } + }, + "time": "2015-07-23T11:04:15.496-07:00" + } \ No newline at end of file diff --git a/tests/Mock/Response/Quickbooks/invoice.json b/tests/Mock/Response/Quickbooks/invoice.json new file mode 100644 index 000000000000..9c72504550c3 --- /dev/null +++ b/tests/Mock/Response/Quickbooks/invoice.json @@ -0,0 +1,152 @@ + +{ + "Invoice": { + "TxnDate": "2014-09-19", + "domain": "QBO", + "PrintStatus": "NeedToPrint", + "SalesTermRef": { + "value": "3" + }, + "TotalAmt": 362.07, + "Line": [ + { + "Description": "Rock Fountain", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 1, + "UnitPrice": 275, + "ItemRef": { + "name": "Rock Fountain", + "value": "5" + } + }, + "LineNum": 1, + "Amount": 275.0, + "Id": "1" + }, + { + "Description": "Fountain Pump", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 1, + "UnitPrice": 12.75, + "ItemRef": { + "name": "Pump", + "value": "11" + } + }, + "LineNum": 2, + "Amount": 12.75, + "Id": "2" + }, + { + "Description": "Concrete for fountain installation", + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "TaxCodeRef": { + "value": "TAX" + }, + "Qty": 5, + "UnitPrice": 9.5, + "ItemRef": { + "name": "Concrete", + "value": "3" + } + }, + "LineNum": 3, + "Amount": 47.5, + "Id": "3" + }, + { + "DetailType": "SubTotalLineDetail", + "Amount": 335.25, + "SubTotalLineDetail": {} + } + ], + "DueDate": "2014-10-19", + "ApplyTaxAfterDiscount": false, + "DocNumber": "1037", + "sparse": false, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "ProjectRef": { + "value": "39298045" + }, + "Deposit": 0, + "Balance": 362.07, + "CustomerRef": { + "name": "Sonnenschein Family Store", + "value": "24" + }, + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 26.82, + "TaxLine": [ + { + "DetailType": "TaxLineDetail", + "Amount": 26.82, + "TaxLineDetail": { + "NetAmountTaxable": 335.25, + "TaxPercent": 8, + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true + } + } + ] + }, + "SyncToken": "0", + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "BillEmail": { + "Address": "Familiystore@intuit.com" + }, + "ShipAddr": { + "City": "Middlefield", + "Line1": "5647 Cypress Hill Ave.", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681", + "CountrySubDivisionCode": "CA", + "Id": "25" + }, + "EmailStatus": "NotSet", + "BillAddr": { + "Line4": "Middlefield, CA 94303", + "Line3": "5647 Cypress Hill Ave.", + "Line2": "Sonnenschein Family Store", + "Line1": "Russ Sonnenschein", + "Long": "-122.1141681", + "Lat": "37.4238562", + "Id": "95" + }, + "MetaData": { + "CreateTime": "2014-09-19T13:16:17-07:00", + "LastUpdatedTime": "2014-09-19T13:16:17-07:00" + }, + "CustomField": [ + { + "DefinitionId": "1", + "StringValue": "102", + "Type": "StringType", + "Name": "Crew #" + } + ], + "Id": "130" + }, + "time": "2015-07-24T10:48:27.082-07:00" + } \ No newline at end of file From c2ef811faa7500e227ca7e8b8147d7a629dd18fb Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 12 Jul 2024 11:02:36 -0400 Subject: [PATCH 04/69] add transformer tests --- .../Quickbooks/ClientTransformerTest.php | 50 +++++++++++++++++ .../Quickbooks/InvoiceTransformerTest.php | 54 +++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php create mode 100644 tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php diff --git a/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php new file mode 100644 index 000000000000..f9fdcbabca7e --- /dev/null +++ b/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php @@ -0,0 +1,50 @@ +create(1234); + + // Read the JSON string from a file and decode into an associative array + $this->customer_data = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/customer.json') ), true); + $this->transformer = new ClientTransformer($company); + $this->transformed_data = $this->transformer->transform($this->customer_data['Customer']); + } + + public function testClassExists() + { + $this->assertInstanceOf(ClientTransformer::class, $this->transformer); + } + + public function testTransformReturnsArray() + { + $this->assertIsArray($this->transformed_data); + } + + public function testTransformHasNameProperty() + { + $this->assertArrayHasKey('name', $this->transformed_data); + $this->assertEquals($this->customer_data['Customer']['CompanyName'], $this->transformed_data['name']); + } + + public function testTransformHasContactsProperty() + { + $this->assertArrayHasKey('contacts', $this->transformed_data); + $this->assertIsArray($this->transformed_data['contacts']); + $this->assertArrayHasKey(0, $this->transformed_data['contacts']); + $this->assertArrayHasKey('email', $this->transformed_data['contacts'][0]); + $this->assertEquals($this->customer_data['Customer']['PrimaryEmailAddr']['Address'], $this->transformed_data['contacts'][0]['email']); + } +} diff --git a/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php new file mode 100644 index 000000000000..05dc0c7c5ba1 --- /dev/null +++ b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php @@ -0,0 +1,54 @@ +create(1234); + + // Read the JSON string from a file and decode into an associative array + $this->invoiceData = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/invoice.json') ), true); + $this->transformer = new InvoiceTransformer($company); + $this->transformedData = $this->transformer->transform($this->invoiceData['Invoice']); + } + + public function testIsInstanceOf() + { + $this->assertInstanceOf(InvoiceTransformer::class, $this->transformer); + } + + public function testTransformReturnsArray() + { + $this->assertIsArray($this->transformedData); + } + + public function testTransformContainsNumber() + { + $this->assertArrayHasKey('number', $this->transformedData); + $this->assertEquals($this->invoiceData['Invoice']['DocNumber'], $this->transformedData['number']); + } + + public function testTransformContainsDueDate() + { + $this->assertArrayHasKey('due_date', $this->transformedData); + $this->assertEquals(strtotime($this->invoiceData['Invoice']['DueDate']), strtotime($this->transformedData['due_date'])); + } + + public function testTransformContainsAmount() + { + $this->assertArrayHasKey('amount', $this->transformedData); + $this->assertIsFloat($this->transformedData['amount']); + $this->assertEquals($this->invoiceData['Invoice']['TotalAmt'], $this->transformedData['amount']); + } +} From 53b6f65add58aaec3c8d6096a8b1ba727c790c82 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 15:06:52 -0400 Subject: [PATCH 05/69] add import class for quickbooks --- app/Import/Providers/Quickbooks.php | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 app/Import/Providers/Quickbooks.php diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php new file mode 100644 index 000000000000..99cdc3490310 --- /dev/null +++ b/app/Import/Providers/Quickbooks.php @@ -0,0 +1,101 @@ +{$entity}(); + } + + //collate any errors + + // $this->finalizeImport(); + } + + public function client() + { + $entity_type = 'client'; + $data = $this->getData($entity_type); + if (empty($data)) { + $this->entity_count['clients'] = 0; + + return; + } + + $this->request_name = StoreClientRequest::class; + $this->repository_name = ClientRepository::class; + $this->factory_name = ClientFactory::class; + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + $this->transformer = new ClientTransformer($this->company); + $client_count = $this->ingest($data, $entity_type); + $this->entity_count['clients'] = $client_count; + } + + public function getData($type) { + + // get the data from cache? file? or api ? + return []; + } + + public function invoice() + { + //make sure we update and create products + $initial_update_products_value = $this->company->update_products; + $this->company->update_products = true; + + $this->company->save(); + + $entity_type = 'invoice'; + $data = $this->getData($entity_type); + + if (empty($data)) { + $this->entity_count['invoices'] = 0; + + return; + } + + $this->request_name = StoreInvoiceRequest::class; + $this->repository_name = InvoiceRepository::class; + $this->factory_name = InvoiceFactory::class; + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + $this->transformer = new InvoiceTransformer($this->company); + $invoice_count = $this->ingestInvoices($data, 'Invoice #'); + $this->entity_count['invoices'] = $invoice_count; + $this->company->update_products = $initial_update_products_value; + $this->company->save(); + } +} From 367d27258c501f32aa68b26108ac1b24639a4494 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 15:07:27 -0400 Subject: [PATCH 06/69] add company id to array --- .../Transformer/Quickbooks/ClientTransformer.php | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/app/Import/Transformer/Quickbooks/ClientTransformer.php b/app/Import/Transformer/Quickbooks/ClientTransformer.php index c70ced06e922..313476307847 100644 --- a/app/Import/Transformer/Quickbooks/ClientTransformer.php +++ b/app/Import/Transformer/Quickbooks/ClientTransformer.php @@ -58,9 +58,9 @@ class ClientTransformer extends BaseTransformer $transformed_data[$key] = method_exists($this, $method = sprintf("get%s", str_replace(".","",$field)) )? call_user_func([$this, $method],$data,$field) : $this->getString($data, $field); } - $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data)->toArray() + $this->getContacts($data, $field); - - return $transformed_data; + $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data); + $transformed_data->contacts[0] = $this->getContacts($data)->toArray()+['company_id' => $this->company->id ]; + return $transformed_data->toArray() + ['company_id' => $this->company->id ] ; } public function getString($data, $field) @@ -68,15 +68,14 @@ class ClientTransformer extends BaseTransformer return Arr::get($data, $field); } - protected function getContacts($data, $field = null) { - return [ 'contacts' => [ - (new ClientContact())->fill([ + protected function getContacts($data) { + return (new ClientContact())->fill([ 'first_name' => $this->getString($data, 'GivenName'), 'last_name' => $this->getString($data, 'FamilyName'), 'phone' => $this->getString($data, 'PrimaryPhone.FreeFormNumber'), 'email' => $this->getString($data, 'PrimaryEmailAddr.Address'), - ]) ] - ]; + 'company_id' => $this->company->id + ]); } From 4d431935e1678780c4cd21c230174280d9c432da Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 15:07:49 -0400 Subject: [PATCH 07/69] add test case for quickbooks import class --- .../Import/Quickbooks/QuickbooksTest.php | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 tests/Feature/Import/Quickbooks/QuickbooksTest.php diff --git a/tests/Feature/Import/Quickbooks/QuickbooksTest.php b/tests/Feature/Import/Quickbooks/QuickbooksTest.php new file mode 100644 index 000000000000..65bc1dab15ff --- /dev/null +++ b/tests/Feature/Import/Quickbooks/QuickbooksTest.php @@ -0,0 +1,81 @@ +withoutMiddleware(ThrottleRequests::class); + config(['database.default' => config('ninja.db.default')]); + $this->makeTestData(); + // $this->withoutExceptionHandling(); + Auth::setUser($this->user); + $this->data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer']; + $hash = Str::random(32); + Cache::put($hash.'-client', base64_encode(json_encode($this->data)), 360); + + $this->quickbooks = Mockery::mock(Quickbooks::class,[[ + 'hash' => $hash, + 'column_map' => ['client' => ['mapping' => []]], + 'skip_header' => true, + 'import_type' => 'invoicely', + ], $this->company ])->makePartial(); + } + + public function testImportCallsGetDataOnceForClient() + { + $this->quickbooks->shouldReceive('getData') + ->once() + ->with('client') + ->andReturn($this->data); + + // Mocking the dependencies used within the client method + + $this->quickbooks->import('client'); + + $this->assertArrayHasKey('clients', $this->quickbooks->entity_count); + $this->assertGreaterThan(0, $this->quickbooks->entity_count['clients']); + + $base_transformer = new BaseTransformer($this->company); + $this->assertTrue($base_transformer->hasClient('Sonnenschein Family Store')); + $contact = $base_transformer->getClient('Amy\'s Bird Sanctuary',''); + $contact = Client::where('name','Amy\'s Bird Sanctuary')->first(); + $this->assertEquals('(650) 555-3311',$contact->phone); + $this->assertEquals('Birds@Intuit.com',$contact->contacts()->first()->email); + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } +} From 0941d1ae3230a23b0d4199ad4ed80a585aa236cb Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 15:08:04 -0400 Subject: [PATCH 08/69] test data --- tests/Feature/Import/customers.json | 1602 ++++++++++++++++ .../Response/Http/200-cutomer-response.txt | 1638 ++++++++++++++++- tests/Mock/Response/Quickbooks/customer.json | 78 +- 3 files changed, 3246 insertions(+), 72 deletions(-) create mode 100644 tests/Feature/Import/customers.json diff --git a/tests/Feature/Import/customers.json b/tests/Feature/Import/customers.json new file mode 100644 index 000000000000..a009db9f1259 --- /dev/null +++ b/tests/Feature/Import/customers.json @@ -0,0 +1,1602 @@ +{ + "Customer" : [ + { + "Taxable": true, + "BillAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "Job": false, + "BillWithParent": false, + "Balance": 239, + "BalanceWithJobs": 239, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "1", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:48:43-07:00", + "LastUpdatedTime": "2024-06-18T13:39:32-07:00" + }, + "GivenName": "Amy", + "FamilyName": "Lauterbach", + "FullyQualifiedName": "Amy's Bird Sanctuary", + "CompanyName": "Amy's Bird Sanctuary", + "DisplayName": "Amy's Bird Sanctuary", + "PrintOnCheckName": "Amy's Bird Sanctuary", + "Active": true, + "V4IDPseudonym": "00209899a3070295964fbf970b06db6f84a1cc", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-3311" + }, + "PrimaryEmailAddr": { + "Address": "Birds@Intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "3", + "Line1": "12 Ocean Dr.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4307072", + "Long": "-122.4295234" + }, + "Job": false, + "BillWithParent": false, + "Balance": 85, + "BalanceWithJobs": 85, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "2", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:49:28-07:00", + "LastUpdatedTime": "2024-06-18T12:56:01-07:00" + }, + "GivenName": "Bill", + "FamilyName": "Lucchini", + "FullyQualifiedName": "Bill's Windsurf Shop", + "CompanyName": "Bill's Windsurf Shop", + "DisplayName": "Bill's Windsurf Shop", + "PrintOnCheckName": "Bill's Windsurf Shop", + "Active": true, + "V4IDPseudonym": "00209823598a40e12748f6913eeca078bc98fa", + "PrimaryPhone": { + "FreeFormNumber": "(415) 444-6538" + }, + "PrimaryEmailAddr": { + "Address": "Surf@Intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "4", + "Line1": "65 Ocean Dr.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4300318", + "Long": "-122.4336537" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "3", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:51:22-07:00", + "LastUpdatedTime": "2024-06-19T12:59:21-07:00" + }, + "GivenName": "Grace", + "FamilyName": "Pariente", + "FullyQualifiedName": "Cool Cars", + "CompanyName": "Cool Cars", + "DisplayName": "Cool Cars", + "PrintOnCheckName": "Cool Cars", + "Active": true, + "V4IDPseudonym": "002098b664cfcba7ac42889139cc9b06d57333", + "PrimaryPhone": { + "FreeFormNumber": "(415) 555-9933" + }, + "PrimaryEmailAddr": { + "Address": "Cool_Cars@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "5", + "Line1": "321 Channing", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.443231", + "Long": "-122.1561846" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "4", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:52:08-07:00", + "LastUpdatedTime": "2024-06-11T16:52:08-07:00" + }, + "GivenName": "Diego", + "FamilyName": "Rodriguez", + "FullyQualifiedName": "Diego Rodriguez", + "DisplayName": "Diego Rodriguez", + "PrintOnCheckName": "Diego Rodriguez", + "Active": true, + "V4IDPseudonym": "00209827fb8566559a42dd87f6bcff1cf3ce54", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4477" + }, + "PrimaryEmailAddr": { + "Address": "Diego@Rodriguez.com" + } + }, + { + "Taxable": true, + "BillAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "ShipAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "5", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-11T16:54:59-07:00", + "LastUpdatedTime": "2024-06-18T13:28:29-07:00" + }, + "GivenName": "Peter", + "FamilyName": "Dukes", + "FullyQualifiedName": "Dukes Basketball Camp", + "CompanyName": "Dukes Basketball Camp", + "DisplayName": "Dukes Basketball Camp", + "PrintOnCheckName": "Dukes Basketball Camp", + "Active": true, + "V4IDPseudonym": "002098da72467ced2a4043b5a9920bf20d3328", + "PrimaryPhone": { + "FreeFormNumber": "(520) 420-5638" + }, + "PrimaryEmailAddr": { + "Address": "Dukes_bball@intuit.com" + } + }, + { + "Taxable": false, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "6", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:55:21-07:00", + "LastUpdatedTime": "2024-06-11T16:55:21-07:00" + }, + "GivenName": "Dylan", + "FamilyName": "Sollfrank", + "FullyQualifiedName": "Dylan Sollfrank", + "DisplayName": "Dylan Sollfrank", + "PrintOnCheckName": "Dylan Sollfrank", + "Active": true, + "V4IDPseudonym": "00209821a0919a92a048d8b0fc3bab3c89475a" + }, + { + "Taxable": false, + "BillAddr": { + "Id": "7", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "7", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 562.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "7", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:57:10-07:00", + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "GivenName": "Kirby", + "FamilyName": "Freeman", + "FullyQualifiedName": "Freeman Sporting Goods", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "Freeman Sporting Goods", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "002098cbf3c6869e8941a9a514acfdb6e7cf85", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0987" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "7" + }, + "Level": 1, + "Balance": 477.5, + "BalanceWithJobs": 477.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "8", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:00:01-07:00", + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "GivenName": "Sasha", + "FamilyName": "Tillou", + "FullyQualifiedName": "Freeman Sporting Goods:0969 Ocean View Road", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "0969 Ocean View Road", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "00209876302803c00243cbad0d677ef769e8b8", + "PrimaryPhone": { + "FreeFormNumber": "(415) 555-9933" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "7" + }, + "Level": 1, + "Balance": 85, + "BalanceWithJobs": 85, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "9", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:01:00-07:00", + "LastUpdatedTime": "2024-06-18T13:23:52-07:00" + }, + "GivenName": "Amelia", + "FullyQualifiedName": "Freeman Sporting Goods:55 Twin Lane", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "55 Twin Lane", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "002098b4f80760af17465abec41c07b5b74e77", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0987" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "Job": false, + "BillWithParent": false, + "Balance": 629.1, + "BalanceWithJobs": 629.1, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "10", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:02:22-07:00", + "LastUpdatedTime": "2024-06-18T13:41:59-07:00" + }, + "GivenName": "Geeta", + "FamilyName": "Kalapatapu", + "FullyQualifiedName": "Geeta Kalapatapu", + "DisplayName": "Geeta Kalapatapu", + "PrintOnCheckName": "Geeta Kalapatapu", + "Active": true, + "V4IDPseudonym": "002098f803e91bfe1b47a4a2abe22384d1703f", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0022" + }, + "PrimaryEmailAddr": { + "Address": "Geeta@Kalapatapu.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "11", + "Line1": "1045 Main St.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4559621", + "Long": "-122.429939" + }, + "ShipAddr": { + "Id": "11", + "Line1": "1045 Main St.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4559621", + "Long": "-122.429939" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "11", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:04:02-07:00", + "LastUpdatedTime": "2024-06-11T17:04:02-07:00" + }, + "GivenName": "Lisa", + "FamilyName": "Gevelber", + "FullyQualifiedName": "Gevelber Photography", + "CompanyName": "Gevelber Photography", + "DisplayName": "Gevelber Photography", + "PrintOnCheckName": "Gevelber Photography", + "Active": true, + "V4IDPseudonym": "0020987afc0e2408724dc89b9f4e156beb3a71", + "PrimaryPhone": { + "FreeFormNumber": "(415) 222-4345" + }, + "PrimaryEmailAddr": { + "Address": "Photography@intuit.com" + }, + "WebAddr": { + "URI": "http://gevelberphotography.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "ShipAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "Job": false, + "BillWithParent": false, + "Balance": 81, + "BalanceWithJobs": 81, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "12", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:05:09-07:00", + "LastUpdatedTime": "2024-06-18T13:25:43-07:00" + }, + "GivenName": "Jeff", + "FamilyName": "Chin", + "FullyQualifiedName": "Jeff's Jalopies", + "CompanyName": "Jeff's Jalopies", + "DisplayName": "Jeff's Jalopies", + "PrintOnCheckName": "Jeff's Jalopies", + "Active": true, + "V4IDPseudonym": "00209855ab5447413f4b09bcd216103e96648b", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-8989" + }, + "PrimaryEmailAddr": { + "Address": "Jalopies@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "ShipAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "Job": false, + "BillWithParent": false, + "Balance": 450, + "BalanceWithJobs": 450, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "13", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:06:42-07:00", + "LastUpdatedTime": "2024-06-17T11:06:49-07:00" + }, + "GivenName": "John", + "FamilyName": "Melton", + "FullyQualifiedName": "John Melton", + "DisplayName": "John Melton", + "PrintOnCheckName": "John Melton", + "Active": true, + "V4IDPseudonym": "002098b2de3f0dd58b421b88d5d9629e468dcc", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-5879" + }, + "PrimaryEmailAddr": { + "Address": "John@Melton.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "14", + "Line1": "45 First St.", + "City": "Menlo Park", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "ShipAddr": { + "Id": "14", + "Line1": "45 First St.", + "City": "Menlo Park", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "14", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:08:02-07:00", + "LastUpdatedTime": "2024-06-11T17:08:02-07:00" + }, + "GivenName": "Kate", + "FamilyName": "Whelan", + "FullyQualifiedName": "Kate Whelan", + "DisplayName": "Kate Whelan", + "PrintOnCheckName": "Kate Whelan", + "Active": true, + "V4IDPseudonym": "0020986254ccc2492e4ad3bb920370ad62fb0a", + "PrimaryPhone": { + "FreeFormNumber": "(650) 554-8822" + }, + "PrimaryEmailAddr": { + "Address": "Kate@Whelan.com" + } + }, + { + "Taxable": true, + "BillAddr": { + "Id": "96", + "Line1": "123 Main Street", + "City": "Mountain View", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94042" + }, + "Notes": "Here are other details.", + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "58", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-07-15T08:42:10-07:00", + "LastUpdatedTime": "2024-07-15T08:42:10-07:00" + }, + "Title": "Mr", + "GivenName": "James", + "MiddleName": "B", + "FamilyName": "King", + "Suffix": "Jr", + "FullyQualifiedName": "King's Groceries", + "CompanyName": "King Groceries", + "DisplayName": "King's Groceries", + "PrintOnCheckName": "King Groceries", + "Active": true, + "V4IDPseudonym": "00209846b1d33d9019492aac784d0eedb77aed", + "PrimaryPhone": { + "FreeFormNumber": "(555) 555-5555" + }, + "PrimaryEmailAddr": { + "Address": "jdrew@myemail.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "16", + "Line1": "789 Sugar Lane", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "Job": false, + "BillWithParent": false, + "Balance": 75, + "BalanceWithJobs": 75, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "16", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:11:27-07:00", + "LastUpdatedTime": "2024-06-17T15:18:18-07:00" + }, + "GivenName": "Kathy", + "FamilyName": "Kuplis", + "FullyQualifiedName": "Kookies by Kathy", + "CompanyName": "Kookies by Kathy", + "DisplayName": "Kookies by Kathy", + "PrintOnCheckName": "Kookies by Kathy", + "Active": true, + "V4IDPseudonym": "0020984a4693d41b404518b85508d743552dd1", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-7896" + }, + "PrimaryEmailAddr": { + "Address": "qbwebsamplecompany@yahoo.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "ShipAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "Job": false, + "BillWithParent": false, + "Balance": 314.28, + "BalanceWithJobs": 314.28, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "17", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:12:16-07:00", + "LastUpdatedTime": "2024-06-19T12:57:24-07:00" + }, + "GivenName": "Mark", + "FamilyName": "Cho", + "FullyQualifiedName": "Mark Cho", + "DisplayName": "Mark Cho", + "PrintOnCheckName": "Mark Cho", + "Active": true, + "V4IDPseudonym": "00209825541e1b61474594a1c62c9839d920e8", + "PrimaryPhone": { + "FreeFormNumber": "(650) 554-1479" + }, + "PrimaryEmailAddr": { + "Address": "Mark@Cho.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "18", + "Line1": "900 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "19", + "Line1": "38921 S. Boise Ave", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.3989376", + "Long": "-122.1443935" + }, + "Job": false, + "BillWithParent": false, + "Balance": 954.75, + "BalanceWithJobs": 954.75, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "18", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:13:37-07:00", + "LastUpdatedTime": "2024-06-17T11:43:20-07:00" + }, + "GivenName": "Kathy", + "FamilyName": "Paulsen", + "FullyQualifiedName": "Paulsen Medical Supplies", + "CompanyName": "Paulsen Medical Supplies", + "DisplayName": "Paulsen Medical Supplies", + "PrintOnCheckName": "Paulsen Medical Supplies", + "Active": true, + "V4IDPseudonym": "00209831a461015bf14e34b746fd75cac87725", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-4569" + }, + "PrimaryEmailAddr": { + "Address": "Medical@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "15", + "Line1": "350 Mountain View Dr.", + "City": "South Orange", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07079", + "Lat": "40.7633073", + "Long": "-74.2426072" + }, + "ShipAddr": { + "Id": "15", + "Line1": "350 Mountain View Dr.", + "City": "South Orange", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07079", + "Lat": "40.7633073", + "Long": "-74.2426072" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "15", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-11T17:10:24-07:00", + "LastUpdatedTime": "2024-06-17T15:30:27-07:00" + }, + "GivenName": "Karen", + "FamilyName": "Pye", + "FullyQualifiedName": "Pye's Cakes", + "CompanyName": "Pye's Cakes", + "DisplayName": "Pye's Cakes", + "PrintOnCheckName": "Pye's Cakes", + "Active": true, + "V4IDPseudonym": "00209838541496d15d4be692d2f42b5d6dc7cb", + "PrimaryPhone": { + "FreeFormNumber": "(973) 555-4652" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-2234" + }, + "PrimaryEmailAddr": { + "Address": "pyescakes@intuit.com" + }, + "WebAddr": { + "URI": "http://www.pyescakes.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "20", + "Line1": "753 Cedar St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "37.7077016", + "Long": "-122.4273232" + }, + "ShipAddr": { + "Id": "20", + "Line1": "753 Cedar St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "37.7077016", + "Long": "-122.4273232" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "19", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:14:24-07:00", + "LastUpdatedTime": "2024-06-11T17:14:24-07:00" + }, + "GivenName": "Jeff", + "FamilyName": "Rago", + "FullyQualifiedName": "Rago Travel Agency", + "CompanyName": "Rago Travel Agency", + "DisplayName": "Rago Travel Agency", + "PrintOnCheckName": "Rago Travel Agency", + "Active": true, + "V4IDPseudonym": "0020984ad706a287e34b9cae15a26c53765c8b", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-1596" + }, + "PrimaryEmailAddr": { + "Address": "Rago_Travel@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "Job": false, + "BillWithParent": false, + "Balance": 226, + "BalanceWithJobs": 226, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "20", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:15:14-07:00", + "LastUpdatedTime": "2024-06-18T12:44:45-07:00" + }, + "GivenName": "Stephanie", + "FamilyName": "Martini", + "FullyQualifiedName": "Red Rock Diner", + "CompanyName": "Red Rock Diner", + "DisplayName": "Red Rock Diner", + "PrintOnCheckName": "Red Rock Diner", + "Active": true, + "V4IDPseudonym": "0020981f7515e864254570b6677c93fd43f7f0", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4973" + }, + "PrimaryEmailAddr": { + "Address": "qbwebsamplecompany@yahoo.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "ShipAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "Job": false, + "BillWithParent": false, + "Balance": 78.6, + "BalanceWithJobs": 78.6, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "21", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:17:21-07:00", + "LastUpdatedTime": "2024-06-18T13:45:12-07:00" + }, + "GivenName": "Rondonuwu", + "MiddleName": "Fruit", + "FamilyName": "and Vegi", + "FullyQualifiedName": "Rondonuwu Fruit and Vegi", + "DisplayName": "Rondonuwu Fruit and Vegi", + "PrintOnCheckName": "Rondonuwu Fruit and Vegi", + "Active": true, + "V4IDPseudonym": "00209805dabf0e38b14f80b7907e1e7de3a267", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-2645" + }, + "PrimaryEmailAddr": { + "Address": "Tony@Rondonuwu.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "23", + "Line1": "77 University", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4431445", + "Long": "-122.164443" + }, + "ShipAddr": { + "Id": "23", + "Line1": "77 University", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4431445", + "Long": "-122.164443" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 274.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "22", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:18:34-07:00", + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "GivenName": "Shara", + "FamilyName": "Barnett", + "FullyQualifiedName": "Shara Barnett", + "DisplayName": "Shara Barnett", + "PrintOnCheckName": "Shara Barnett", + "Active": true, + "V4IDPseudonym": "0020980a9b0bf278924520a48aae29958713bd", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4563" + }, + "PrimaryEmailAddr": { + "Address": "Shara@Barnett.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "22" + }, + "Level": 1, + "Balance": 274.5, + "BalanceWithJobs": 274.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "23", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:22:41-07:00", + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "GivenName": "Shara", + "FamilyName": "Barnett", + "FullyQualifiedName": "Shara Barnett:Barnett Design", + "CompanyName": "Barnett Design", + "DisplayName": "Barnett Design", + "PrintOnCheckName": "Barnett Design", + "Active": true, + "V4IDPseudonym": "0020986db0d9f0b0e54e91956ff6f914074f59", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-1289" + }, + "PrimaryEmailAddr": { + "Address": "Design@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "ShipAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "Job": false, + "BillWithParent": false, + "Balance": 362.07, + "BalanceWithJobs": 362.07, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "24", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:23:24-07:00", + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + }, + "GivenName": "Russ", + "FamilyName": "Sonnenschein", + "FullyQualifiedName": "Sonnenschein Family Store", + "CompanyName": "Sonnenschein Family Store", + "DisplayName": "Sonnenschein Family Store", + "PrintOnCheckName": "Sonnenschein Family Store", + "Active": true, + "V4IDPseudonym": "002098961fc251829b4f60b43cbcb7f73ca615", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-8463" + }, + "PrimaryEmailAddr": { + "Address": "Familiystore@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "Job": false, + "BillWithParent": false, + "Balance": 160, + "BalanceWithJobs": 160, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "25", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-11T17:24:26-07:00", + "LastUpdatedTime": "2024-06-19T12:52:44-07:00" + }, + "GivenName": "Katsuyuki", + "FamilyName": "Yanagawa", + "FullyQualifiedName": "Sushi by Katsuyuki", + "CompanyName": "Sushi by Katsuyuki", + "DisplayName": "Sushi by Katsuyuki", + "PrintOnCheckName": "Sushi by Katsuyuki", + "Active": true, + "V4IDPseudonym": "002098cf586235e42b4a92b5bfff179f7df063", + "PrimaryPhone": { + "FreeFormNumber": "(505) 570-0147" + }, + "PrimaryEmailAddr": { + "Address": "Sushi@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "Job": false, + "BillWithParent": false, + "Balance": 414.72, + "BalanceWithJobs": 414.72, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "26", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:25:08-07:00", + "LastUpdatedTime": "2024-06-18T13:36:31-07:00" + }, + "GivenName": "Travis", + "FamilyName": "Waldron", + "FullyQualifiedName": "Travis Waldron", + "DisplayName": "Travis Waldron", + "PrintOnCheckName": "Travis Waldron", + "Active": true, + "V4IDPseudonym": "002098f8684dee1bd64a01a19506e21c69bad5", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-9977" + }, + "PrimaryEmailAddr": { + "Address": "Travis@Waldron.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "28", + "Line1": "202 Main St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85704", + "Lat": "32.2242351", + "Long": "-110.9758242" + }, + "ShipAddr": { + "Id": "28", + "Line1": "202 Main St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85704", + "Lat": "32.2242351", + "Long": "-110.9758242" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "27", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:26:07-07:00", + "LastUpdatedTime": "2024-06-11T17:26:07-07:00" + }, + "GivenName": "Dan", + "FamilyName": "Wilks", + "FullyQualifiedName": "Video Games by Dan", + "CompanyName": "Video Games by Dan", + "DisplayName": "Video Games by Dan", + "PrintOnCheckName": "Video Games by Dan", + "Active": true, + "V4IDPseudonym": "002098567a1f1e04774cbfa5068603e226b935", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-3456" + }, + "PrimaryEmailAddr": { + "Address": "Videogames@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "29", + "Line1": "135 Broadway", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4846734", + "Long": "-122.1989488" + }, + "ShipAddr": { + "Id": "29", + "Line1": "135 Broadway", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4846734", + "Long": "-122.1989488" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "28", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:28:21-07:00", + "LastUpdatedTime": "2024-06-11T17:28:21-07:00" + }, + "GivenName": "Whitney", + "FamilyName": "Brewer", + "FullyQualifiedName": "Wedding Planning by Whitney", + "CompanyName": "Wedding Planning by Whitney", + "DisplayName": "Wedding Planning by Whitney", + "PrintOnCheckName": "Wedding Planning by Whitney", + "Active": true, + "V4IDPseudonym": "0020985f7b009aa05649a39b07782c91f430a2", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-2473" + }, + "PrimaryEmailAddr": { + "Address": "Dream_Wedding@intuit.com" + }, + "WebAddr": { + "URI": "http://www.dreamwedding.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "ShipAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "Job": false, + "BillWithParent": false, + "Balance": 375, + "BalanceWithJobs": 375, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "29", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:29:04-07:00", + "LastUpdatedTime": "2024-06-17T11:09:08-07:00" + }, + "GivenName": "Nicola", + "FamilyName": "Weiskopf", + "FullyQualifiedName": "Weiskopf Consulting", + "CompanyName": "Weiskopf Consulting", + "DisplayName": "Weiskopf Consulting", + "PrintOnCheckName": "Weiskopf Consulting", + "Active": true, + "V4IDPseudonym": "002098ed8b94bb7f83434c8e68f1cffec7604d", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-1423" + }, + "PrimaryEmailAddr": { + "Address": "Consulting@intuit.com" + } + } + ] +} \ No newline at end of file diff --git a/tests/Mock/Response/Http/200-cutomer-response.txt b/tests/Mock/Response/Http/200-cutomer-response.txt index c399f0f09438..eea22e37daf5 100644 --- a/tests/Mock/Response/Http/200-cutomer-response.txt +++ b/tests/Mock/Response/Http/200-cutomer-response.txt @@ -9,43 +9,1609 @@ Access-Control-Max-Age: 300 Access-Control-Allow-Credentials: true { - "Customer": { - "PrimaryEmailAddr": { - "Address": "Surf@Intuit.com" - }, - "SyncToken": "0", - "domain": "QBO", - "GivenName": "Bill", - "DisplayName": "Bill's Windsurf Shop", - "BillWithParent": false, - "FullyQualifiedName": "Bill's Windsurf Shop", - "CompanyName": "Bill's Windsurf Shop", - "FamilyName": "Lucchini", - "sparse": false, - "PrimaryPhone": { - "FreeFormNumber": "(415) 444-6538" - }, - "Active": true, - "Job": false, - "BalanceWithJobs": 85.0, + "QueryResponse": { + "Customer": [ + { + "Taxable": true, "BillAddr": { - "City": "Half Moon Bay", - "Line1": "12 Ocean Dr.", - "PostalCode": "94213", - "Lat": "37.4307072", - "Long": "-122.4295234", - "CountrySubDivisionCode": "CA", - "Id": "3" - }, - "PreferredDeliveryMethod": "Print", - "Taxable": false, - "PrintOnCheckName": "Bill's Windsurf Shop", - "Balance": 85.0, - "Id": "2", + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "Job": false, + "BillWithParent": false, + "Balance": 239, + "BalanceWithJobs": 239, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "1", + "SyncToken": "0", "MetaData": { - "CreateTime": "2014-09-11T16:49:28-07:00", - "LastUpdatedTime": "2014-09-18T12:56:01-07:00" + "CreateTime": "2024-06-11T16:48:43-07:00", + "LastUpdatedTime": "2024-06-18T13:39:32-07:00" + }, + "GivenName": "Amy", + "FamilyName": "Lauterbach", + "FullyQualifiedName": "Amy's Bird Sanctuary", + "CompanyName": "Amy's Bird Sanctuary", + "DisplayName": "Amy's Bird Sanctuary", + "PrintOnCheckName": "Amy's Bird Sanctuary", + "Active": true, + "V4IDPseudonym": "00209899a3070295964fbf970b06db6f84a1cc", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-3311" + }, + "PrimaryEmailAddr": { + "Address": "Birds@Intuit.com" } - }, - "time": "2015-07-23T11:04:15.496-07:00" + }, + { + "Taxable": false, + "BillAddr": { + "Id": "3", + "Line1": "12 Ocean Dr.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4307072", + "Long": "-122.4295234" + }, + "Job": false, + "BillWithParent": false, + "Balance": 85, + "BalanceWithJobs": 85, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "2", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:49:28-07:00", + "LastUpdatedTime": "2024-06-18T12:56:01-07:00" + }, + "GivenName": "Bill", + "FamilyName": "Lucchini", + "FullyQualifiedName": "Bill's Windsurf Shop", + "CompanyName": "Bill's Windsurf Shop", + "DisplayName": "Bill's Windsurf Shop", + "PrintOnCheckName": "Bill's Windsurf Shop", + "Active": true, + "V4IDPseudonym": "00209823598a40e12748f6913eeca078bc98fa", + "PrimaryPhone": { + "FreeFormNumber": "(415) 444-6538" + }, + "PrimaryEmailAddr": { + "Address": "Surf@Intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "4", + "Line1": "65 Ocean Dr.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4300318", + "Long": "-122.4336537" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "3", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:51:22-07:00", + "LastUpdatedTime": "2024-06-19T12:59:21-07:00" + }, + "GivenName": "Grace", + "FamilyName": "Pariente", + "FullyQualifiedName": "Cool Cars", + "CompanyName": "Cool Cars", + "DisplayName": "Cool Cars", + "PrintOnCheckName": "Cool Cars", + "Active": true, + "V4IDPseudonym": "002098b664cfcba7ac42889139cc9b06d57333", + "PrimaryPhone": { + "FreeFormNumber": "(415) 555-9933" + }, + "PrimaryEmailAddr": { + "Address": "Cool_Cars@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "5", + "Line1": "321 Channing", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.443231", + "Long": "-122.1561846" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "4", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:52:08-07:00", + "LastUpdatedTime": "2024-06-11T16:52:08-07:00" + }, + "GivenName": "Diego", + "FamilyName": "Rodriguez", + "FullyQualifiedName": "Diego Rodriguez", + "DisplayName": "Diego Rodriguez", + "PrintOnCheckName": "Diego Rodriguez", + "Active": true, + "V4IDPseudonym": "00209827fb8566559a42dd87f6bcff1cf3ce54", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4477" + }, + "PrimaryEmailAddr": { + "Address": "Diego@Rodriguez.com" + } + }, + { + "Taxable": true, + "BillAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "ShipAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "5", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-11T16:54:59-07:00", + "LastUpdatedTime": "2024-06-18T13:28:29-07:00" + }, + "GivenName": "Peter", + "FamilyName": "Dukes", + "FullyQualifiedName": "Dukes Basketball Camp", + "CompanyName": "Dukes Basketball Camp", + "DisplayName": "Dukes Basketball Camp", + "PrintOnCheckName": "Dukes Basketball Camp", + "Active": true, + "V4IDPseudonym": "002098da72467ced2a4043b5a9920bf20d3328", + "PrimaryPhone": { + "FreeFormNumber": "(520) 420-5638" + }, + "PrimaryEmailAddr": { + "Address": "Dukes_bball@intuit.com" + } + }, + { + "Taxable": false, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "6", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:55:21-07:00", + "LastUpdatedTime": "2024-06-11T16:55:21-07:00" + }, + "GivenName": "Dylan", + "FamilyName": "Sollfrank", + "FullyQualifiedName": "Dylan Sollfrank", + "DisplayName": "Dylan Sollfrank", + "PrintOnCheckName": "Dylan Sollfrank", + "Active": true, + "V4IDPseudonym": "00209821a0919a92a048d8b0fc3bab3c89475a" + }, + { + "Taxable": false, + "BillAddr": { + "Id": "7", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "7", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 562.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "7", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T16:57:10-07:00", + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "GivenName": "Kirby", + "FamilyName": "Freeman", + "FullyQualifiedName": "Freeman Sporting Goods", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "Freeman Sporting Goods", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "002098cbf3c6869e8941a9a514acfdb6e7cf85", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0987" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "7" + }, + "Level": 1, + "Balance": 477.5, + "BalanceWithJobs": 477.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "8", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:00:01-07:00", + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "GivenName": "Sasha", + "FamilyName": "Tillou", + "FullyQualifiedName": "Freeman Sporting Goods:0969 Ocean View Road", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "0969 Ocean View Road", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "00209876302803c00243cbad0d677ef769e8b8", + "PrimaryPhone": { + "FreeFormNumber": "(415) 555-9933" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "7" + }, + "Level": 1, + "Balance": 85, + "BalanceWithJobs": 85, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "9", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:01:00-07:00", + "LastUpdatedTime": "2024-06-18T13:23:52-07:00" + }, + "GivenName": "Amelia", + "FullyQualifiedName": "Freeman Sporting Goods:55 Twin Lane", + "CompanyName": "Freeman Sporting Goods", + "DisplayName": "55 Twin Lane", + "PrintOnCheckName": "Freeman Sporting Goods", + "Active": true, + "V4IDPseudonym": "002098b4f80760af17465abec41c07b5b74e77", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0987" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-8849" + }, + "Fax": { + "FreeFormNumber": "(520) 555-7894" + }, + "PrimaryEmailAddr": { + "Address": "Sporting_goods@intuit.com" + }, + "WebAddr": { + "URI": "http://sportinggoods.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "Job": false, + "BillWithParent": false, + "Balance": 629.1, + "BalanceWithJobs": 629.1, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "10", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:02:22-07:00", + "LastUpdatedTime": "2024-06-18T13:41:59-07:00" + }, + "GivenName": "Geeta", + "FamilyName": "Kalapatapu", + "FullyQualifiedName": "Geeta Kalapatapu", + "DisplayName": "Geeta Kalapatapu", + "PrintOnCheckName": "Geeta Kalapatapu", + "Active": true, + "V4IDPseudonym": "002098f803e91bfe1b47a4a2abe22384d1703f", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-0022" + }, + "PrimaryEmailAddr": { + "Address": "Geeta@Kalapatapu.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "11", + "Line1": "1045 Main St.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4559621", + "Long": "-122.429939" + }, + "ShipAddr": { + "Id": "11", + "Line1": "1045 Main St.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4559621", + "Long": "-122.429939" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "11", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:04:02-07:00", + "LastUpdatedTime": "2024-06-11T17:04:02-07:00" + }, + "GivenName": "Lisa", + "FamilyName": "Gevelber", + "FullyQualifiedName": "Gevelber Photography", + "CompanyName": "Gevelber Photography", + "DisplayName": "Gevelber Photography", + "PrintOnCheckName": "Gevelber Photography", + "Active": true, + "V4IDPseudonym": "0020987afc0e2408724dc89b9f4e156beb3a71", + "PrimaryPhone": { + "FreeFormNumber": "(415) 222-4345" + }, + "PrimaryEmailAddr": { + "Address": "Photography@intuit.com" + }, + "WebAddr": { + "URI": "http://gevelberphotography.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "ShipAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "Job": false, + "BillWithParent": false, + "Balance": 81, + "BalanceWithJobs": 81, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "12", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:05:09-07:00", + "LastUpdatedTime": "2024-06-18T13:25:43-07:00" + }, + "GivenName": "Jeff", + "FamilyName": "Chin", + "FullyQualifiedName": "Jeff's Jalopies", + "CompanyName": "Jeff's Jalopies", + "DisplayName": "Jeff's Jalopies", + "PrintOnCheckName": "Jeff's Jalopies", + "Active": true, + "V4IDPseudonym": "00209855ab5447413f4b09bcd216103e96648b", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-8989" + }, + "PrimaryEmailAddr": { + "Address": "Jalopies@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "ShipAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "Job": false, + "BillWithParent": false, + "Balance": 450, + "BalanceWithJobs": 450, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "13", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:06:42-07:00", + "LastUpdatedTime": "2024-06-17T11:06:49-07:00" + }, + "GivenName": "John", + "FamilyName": "Melton", + "FullyQualifiedName": "John Melton", + "DisplayName": "John Melton", + "PrintOnCheckName": "John Melton", + "Active": true, + "V4IDPseudonym": "002098b2de3f0dd58b421b88d5d9629e468dcc", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-5879" + }, + "PrimaryEmailAddr": { + "Address": "John@Melton.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "14", + "Line1": "45 First St.", + "City": "Menlo Park", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "ShipAddr": { + "Id": "14", + "Line1": "45 First St.", + "City": "Menlo Park", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "14", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:08:02-07:00", + "LastUpdatedTime": "2024-06-11T17:08:02-07:00" + }, + "GivenName": "Kate", + "FamilyName": "Whelan", + "FullyQualifiedName": "Kate Whelan", + "DisplayName": "Kate Whelan", + "PrintOnCheckName": "Kate Whelan", + "Active": true, + "V4IDPseudonym": "0020986254ccc2492e4ad3bb920370ad62fb0a", + "PrimaryPhone": { + "FreeFormNumber": "(650) 554-8822" + }, + "PrimaryEmailAddr": { + "Address": "Kate@Whelan.com" + } + }, + { + "Taxable": true, + "BillAddr": { + "Id": "96", + "Line1": "123 Main Street", + "City": "Mountain View", + "Country": "USA", + "CountrySubDivisionCode": "CA", + "PostalCode": "94042" + }, + "Notes": "Here are other details.", + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "58", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-07-15T08:42:10-07:00", + "LastUpdatedTime": "2024-07-15T08:42:10-07:00" + }, + "Title": "Mr", + "GivenName": "James", + "MiddleName": "B", + "FamilyName": "King", + "Suffix": "Jr", + "FullyQualifiedName": "King's Groceries", + "CompanyName": "King Groceries", + "DisplayName": "King's Groceries", + "PrintOnCheckName": "King Groceries", + "Active": true, + "V4IDPseudonym": "00209846b1d33d9019492aac784d0eedb77aed", + "PrimaryPhone": { + "FreeFormNumber": "(555) 555-5555" + }, + "PrimaryEmailAddr": { + "Address": "jdrew@myemail.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "16", + "Line1": "789 Sugar Lane", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "Job": false, + "BillWithParent": false, + "Balance": 75, + "BalanceWithJobs": 75, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "16", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:11:27-07:00", + "LastUpdatedTime": "2024-06-17T15:18:18-07:00" + }, + "GivenName": "Kathy", + "FamilyName": "Kuplis", + "FullyQualifiedName": "Kookies by Kathy", + "CompanyName": "Kookies by Kathy", + "DisplayName": "Kookies by Kathy", + "PrintOnCheckName": "Kookies by Kathy", + "Active": true, + "V4IDPseudonym": "0020984a4693d41b404518b85508d743552dd1", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-7896" + }, + "PrimaryEmailAddr": { + "Address": "qbwebsamplecompany@yahoo.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "ShipAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "Job": false, + "BillWithParent": false, + "Balance": 314.28, + "BalanceWithJobs": 314.28, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "17", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:12:16-07:00", + "LastUpdatedTime": "2024-06-19T12:57:24-07:00" + }, + "GivenName": "Mark", + "FamilyName": "Cho", + "FullyQualifiedName": "Mark Cho", + "DisplayName": "Mark Cho", + "PrintOnCheckName": "Mark Cho", + "Active": true, + "V4IDPseudonym": "00209825541e1b61474594a1c62c9839d920e8", + "PrimaryPhone": { + "FreeFormNumber": "(650) 554-1479" + }, + "PrimaryEmailAddr": { + "Address": "Mark@Cho.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "18", + "Line1": "900 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "19", + "Line1": "38921 S. Boise Ave", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.3989376", + "Long": "-122.1443935" + }, + "Job": false, + "BillWithParent": false, + "Balance": 954.75, + "BalanceWithJobs": 954.75, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "18", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:13:37-07:00", + "LastUpdatedTime": "2024-06-17T11:43:20-07:00" + }, + "GivenName": "Kathy", + "FamilyName": "Paulsen", + "FullyQualifiedName": "Paulsen Medical Supplies", + "CompanyName": "Paulsen Medical Supplies", + "DisplayName": "Paulsen Medical Supplies", + "PrintOnCheckName": "Paulsen Medical Supplies", + "Active": true, + "V4IDPseudonym": "00209831a461015bf14e34b746fd75cac87725", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-4569" + }, + "PrimaryEmailAddr": { + "Address": "Medical@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "15", + "Line1": "350 Mountain View Dr.", + "City": "South Orange", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07079", + "Lat": "40.7633073", + "Long": "-74.2426072" + }, + "ShipAddr": { + "Id": "15", + "Line1": "350 Mountain View Dr.", + "City": "South Orange", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07079", + "Lat": "40.7633073", + "Long": "-74.2426072" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "15", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-11T17:10:24-07:00", + "LastUpdatedTime": "2024-06-17T15:30:27-07:00" + }, + "GivenName": "Karen", + "FamilyName": "Pye", + "FullyQualifiedName": "Pye's Cakes", + "CompanyName": "Pye's Cakes", + "DisplayName": "Pye's Cakes", + "PrintOnCheckName": "Pye's Cakes", + "Active": true, + "V4IDPseudonym": "00209838541496d15d4be692d2f42b5d6dc7cb", + "PrimaryPhone": { + "FreeFormNumber": "(973) 555-4652" + }, + "Mobile": { + "FreeFormNumber": "(973) 555-2234" + }, + "PrimaryEmailAddr": { + "Address": "pyescakes@intuit.com" + }, + "WebAddr": { + "URI": "http://www.pyescakes.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "20", + "Line1": "753 Cedar St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "37.7077016", + "Long": "-122.4273232" + }, + "ShipAddr": { + "Id": "20", + "Line1": "753 Cedar St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "37.7077016", + "Long": "-122.4273232" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "19", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:14:24-07:00", + "LastUpdatedTime": "2024-06-11T17:14:24-07:00" + }, + "GivenName": "Jeff", + "FamilyName": "Rago", + "FullyQualifiedName": "Rago Travel Agency", + "CompanyName": "Rago Travel Agency", + "DisplayName": "Rago Travel Agency", + "PrintOnCheckName": "Rago Travel Agency", + "Active": true, + "V4IDPseudonym": "0020984ad706a287e34b9cae15a26c53765c8b", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-1596" + }, + "PrimaryEmailAddr": { + "Address": "Rago_Travel@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "Job": false, + "BillWithParent": false, + "Balance": 226, + "BalanceWithJobs": 226, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "20", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:15:14-07:00", + "LastUpdatedTime": "2024-06-18T12:44:45-07:00" + }, + "GivenName": "Stephanie", + "FamilyName": "Martini", + "FullyQualifiedName": "Red Rock Diner", + "CompanyName": "Red Rock Diner", + "DisplayName": "Red Rock Diner", + "PrintOnCheckName": "Red Rock Diner", + "Active": true, + "V4IDPseudonym": "0020981f7515e864254570b6677c93fd43f7f0", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4973" + }, + "PrimaryEmailAddr": { + "Address": "qbwebsamplecompany@yahoo.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "ShipAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "Job": false, + "BillWithParent": false, + "Balance": 78.6, + "BalanceWithJobs": 78.6, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "21", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:17:21-07:00", + "LastUpdatedTime": "2024-06-18T13:45:12-07:00" + }, + "GivenName": "Rondonuwu", + "MiddleName": "Fruit", + "FamilyName": "and Vegi", + "FullyQualifiedName": "Rondonuwu Fruit and Vegi", + "DisplayName": "Rondonuwu Fruit and Vegi", + "PrintOnCheckName": "Rondonuwu Fruit and Vegi", + "Active": true, + "V4IDPseudonym": "00209805dabf0e38b14f80b7907e1e7de3a267", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-2645" + }, + "PrimaryEmailAddr": { + "Address": "Tony@Rondonuwu.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "23", + "Line1": "77 University", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4431445", + "Long": "-122.164443" + }, + "ShipAddr": { + "Id": "23", + "Line1": "77 University", + "City": "Palo Alto", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4431445", + "Long": "-122.164443" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 274.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "22", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:18:34-07:00", + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "GivenName": "Shara", + "FamilyName": "Barnett", + "FullyQualifiedName": "Shara Barnett", + "DisplayName": "Shara Barnett", + "PrintOnCheckName": "Shara Barnett", + "Active": true, + "V4IDPseudonym": "0020980a9b0bf278924520a48aae29958713bd", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-4563" + }, + "PrimaryEmailAddr": { + "Address": "Shara@Barnett.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "ShipAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "Job": true, + "BillWithParent": false, + "ParentRef": { + "value": "22" + }, + "Level": 1, + "Balance": 274.5, + "BalanceWithJobs": 274.5, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "23", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:22:41-07:00", + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "GivenName": "Shara", + "FamilyName": "Barnett", + "FullyQualifiedName": "Shara Barnett:Barnett Design", + "CompanyName": "Barnett Design", + "DisplayName": "Barnett Design", + "PrintOnCheckName": "Barnett Design", + "Active": true, + "V4IDPseudonym": "0020986db0d9f0b0e54e91956ff6f914074f59", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-1289" + }, + "PrimaryEmailAddr": { + "Address": "Design@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "ShipAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "Job": false, + "BillWithParent": false, + "Balance": 362.07, + "BalanceWithJobs": 362.07, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "24", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:23:24-07:00", + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + }, + "GivenName": "Russ", + "FamilyName": "Sonnenschein", + "FullyQualifiedName": "Sonnenschein Family Store", + "CompanyName": "Sonnenschein Family Store", + "DisplayName": "Sonnenschein Family Store", + "PrintOnCheckName": "Sonnenschein Family Store", + "Active": true, + "V4IDPseudonym": "002098961fc251829b4f60b43cbcb7f73ca615", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-8463" + }, + "PrimaryEmailAddr": { + "Address": "Familiystore@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "Job": false, + "BillWithParent": false, + "Balance": 160, + "BalanceWithJobs": 160, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "25", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-11T17:24:26-07:00", + "LastUpdatedTime": "2024-06-19T12:52:44-07:00" + }, + "GivenName": "Katsuyuki", + "FamilyName": "Yanagawa", + "FullyQualifiedName": "Sushi by Katsuyuki", + "CompanyName": "Sushi by Katsuyuki", + "DisplayName": "Sushi by Katsuyuki", + "PrintOnCheckName": "Sushi by Katsuyuki", + "Active": true, + "V4IDPseudonym": "002098cf586235e42b4a92b5bfff179f7df063", + "PrimaryPhone": { + "FreeFormNumber": "(505) 570-0147" + }, + "PrimaryEmailAddr": { + "Address": "Sushi@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "Job": false, + "BillWithParent": false, + "Balance": 414.72, + "BalanceWithJobs": 414.72, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "26", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:25:08-07:00", + "LastUpdatedTime": "2024-06-18T13:36:31-07:00" + }, + "GivenName": "Travis", + "FamilyName": "Waldron", + "FullyQualifiedName": "Travis Waldron", + "DisplayName": "Travis Waldron", + "PrintOnCheckName": "Travis Waldron", + "Active": true, + "V4IDPseudonym": "002098f8684dee1bd64a01a19506e21c69bad5", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-9977" + }, + "PrimaryEmailAddr": { + "Address": "Travis@Waldron.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "28", + "Line1": "202 Main St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85704", + "Lat": "32.2242351", + "Long": "-110.9758242" + }, + "ShipAddr": { + "Id": "28", + "Line1": "202 Main St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85704", + "Lat": "32.2242351", + "Long": "-110.9758242" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "27", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:26:07-07:00", + "LastUpdatedTime": "2024-06-11T17:26:07-07:00" + }, + "GivenName": "Dan", + "FamilyName": "Wilks", + "FullyQualifiedName": "Video Games by Dan", + "CompanyName": "Video Games by Dan", + "DisplayName": "Video Games by Dan", + "PrintOnCheckName": "Video Games by Dan", + "Active": true, + "V4IDPseudonym": "002098567a1f1e04774cbfa5068603e226b935", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-3456" + }, + "PrimaryEmailAddr": { + "Address": "Videogames@intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "29", + "Line1": "135 Broadway", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4846734", + "Long": "-122.1989488" + }, + "ShipAddr": { + "Id": "29", + "Line1": "135 Broadway", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4846734", + "Long": "-122.1989488" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "28", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:28:21-07:00", + "LastUpdatedTime": "2024-06-11T17:28:21-07:00" + }, + "GivenName": "Whitney", + "FamilyName": "Brewer", + "FullyQualifiedName": "Wedding Planning by Whitney", + "CompanyName": "Wedding Planning by Whitney", + "DisplayName": "Wedding Planning by Whitney", + "PrintOnCheckName": "Wedding Planning by Whitney", + "Active": true, + "V4IDPseudonym": "0020985f7b009aa05649a39b07782c91f430a2", + "PrimaryPhone": { + "FreeFormNumber": "(650) 557-2473" + }, + "PrimaryEmailAddr": { + "Address": "Dream_Wedding@intuit.com" + }, + "WebAddr": { + "URI": "http://www.dreamwedding.intuit.com" + } + }, + { + "Taxable": false, + "BillAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "ShipAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "Job": false, + "BillWithParent": false, + "Balance": 375, + "BalanceWithJobs": 375, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "29", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-11T17:29:04-07:00", + "LastUpdatedTime": "2024-06-17T11:09:08-07:00" + }, + "GivenName": "Nicola", + "FamilyName": "Weiskopf", + "FullyQualifiedName": "Weiskopf Consulting", + "CompanyName": "Weiskopf Consulting", + "DisplayName": "Weiskopf Consulting", + "PrintOnCheckName": "Weiskopf Consulting", + "Active": true, + "V4IDPseudonym": "002098ed8b94bb7f83434c8e68f1cffec7604d", + "PrimaryPhone": { + "FreeFormNumber": "(650) 555-1423" + }, + "PrimaryEmailAddr": { + "Address": "Consulting@intuit.com" + } + } + ], + "startPosition": 1, + "maxResults": 30 + }, + "time": "2024-07-15T08:42:58.291-07:00" } \ No newline at end of file diff --git a/tests/Mock/Response/Quickbooks/customer.json b/tests/Mock/Response/Quickbooks/customer.json index 8dae12a9f307..b4a17b495ce6 100644 --- a/tests/Mock/Response/Quickbooks/customer.json +++ b/tests/Mock/Response/Quickbooks/customer.json @@ -1,42 +1,48 @@ { - "Customer": { - "PrimaryEmailAddr": { - "Address": "Surf@Intuit.com" - }, - "SyncToken": "0", - "domain": "QBO", - "GivenName": "Bill", - "DisplayName": "Bill's Windsurf Shop", - "BillWithParent": false, - "FullyQualifiedName": "Bill's Windsurf Shop", - "CompanyName": "Bill's Windsurf Shop", - "FamilyName": "Lucchini", - "sparse": false, - "PrimaryPhone": { - "FreeFormNumber": "(415) 444-6538" - }, - "Active": true, - "Job": false, - "BalanceWithJobs": 85.0, + "Customer":{ + "Taxable": false, "BillAddr": { - "City": "Half Moon Bay", - "Line1": "12 Ocean Dr.", - "PostalCode": "94213", - "Lat": "37.4307072", - "Long": "-122.4295234", - "CountrySubDivisionCode": "CA", - "Id": "3" - }, - "PreferredDeliveryMethod": "Print", - "Taxable": false, - "PrintOnCheckName": "Bill's Windsurf Shop", - "Balance": 85.0, - "Id": "2", + "Id": "4", + "Line1": "65 Ocean Dr.", + "City": "Half Moon Bay", + "CountrySubDivisionCode": "CA", + "PostalCode": "94213", + "Lat": "37.4300318", + "Long": "-122.4336537" + }, + "Job": false, + "BillWithParent": false, + "Balance": 0, + "BalanceWithJobs": 0, + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PreferredDeliveryMethod": "Print", + "IsProject": false, + "ClientEntityId": "0", + "domain": "QBO", + "sparse": false, + "Id": "3", + "SyncToken": "0", "MetaData": { - "CreateTime": "2014-09-11T16:49:28-07:00", - "LastUpdatedTime": "2014-09-18T12:56:01-07:00" + "CreateTime": "2024-06-11T16:51:22-07:00", + "LastUpdatedTime": "2024-06-19T12:59:21-07:00" + }, + "GivenName": "Grace", + "FamilyName": "Pariente", + "FullyQualifiedName": "Cool Cars", + "CompanyName": "Cool Cars", + "DisplayName": "Cool Cars", + "PrintOnCheckName": "Cool Cars", + "Active": true, + "V4IDPseudonym": "002098b664cfcba7ac42889139cc9b06d57333", + "PrimaryPhone": { + "FreeFormNumber": "(415) 555-9933" + }, + "PrimaryEmailAddr": { + "Address": "Cool_Cars@intuit.com" } - }, - "time": "2015-07-23T11:04:15.496-07:00" + } } \ No newline at end of file From 57a62a054b268281936b5552e37fce0607984b70 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:28:22 -0400 Subject: [PATCH 09/69] add product import method --- app/Import/Providers/Quickbooks.php | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php index 99cdc3490310..60af270f7547 100644 --- a/app/Import/Providers/Quickbooks.php +++ b/app/Import/Providers/Quickbooks.php @@ -11,14 +11,18 @@ namespace App\Import\Providers; +use App\Factory\ProductFactory; use App\Factory\ClientFactory; use App\Factory\InvoiceFactory; use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Product\StoreProductRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; use App\Import\Transformer\Quickbooks\ClientTransformer; use App\Import\Transformer\Quickbooks\InvoiceTransformer; +use App\Import\Transformer\Quickbooks\ProductTransformer; use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; +use App\Repositories\ProductRepository; class Quickbooks extends BaseImport { @@ -64,6 +68,26 @@ class Quickbooks extends BaseImport $this->entity_count['clients'] = $client_count; } + public function product() + { + $entity_type = 'product'; + $data = $this->getData($entity_type); + if (empty($data)) { + $this->entity_count['products'] = 0; + + return; + } + + $this->request_name = StoreProductRequest::class; + $this->repository_name = ProductRepository::class; + $this->factory_name = ProductFactory::class; + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + $this->transformer = new ProductTransformer($this->company); + $count = $this->ingest($data, $entity_type); + $this->entity_count['products'] = $count; + } + public function getData($type) { // get the data from cache? file? or api ? From 282fe44c85456139dcce60a6cd6ef2fe33a1772f Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:29:04 -0400 Subject: [PATCH 10/69] fixed error missing 2nd param --- app/Import/Transformer/Quickbooks/InvoiceTransformer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php index 30bcf80bed1b..a49efaedf576 100644 --- a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -93,11 +93,11 @@ class InvoiceTransformer extends BaseTransformer public function getCreateTime($data) { - return $this->parseDateOrNull($this->getString($data,'MetaData.CreateTime')); + return $this->parseDateOrNull($data['MetaData'], 'CreateTime'); } public function getLastUpdatedTime($data) { - return $this->parseDateOrNull($this->getString($data,'MetaData.LastUpdatedTime')); + return $this->parseDateOrNull($data['MetaData'],'LastUpdatedTime'); } } From a1d3247d52547fd44d5bd538a757b791eca22196 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:29:17 -0400 Subject: [PATCH 11/69] add product transformer --- .../Quickbooks/ProductTransformer.php | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 app/Import/Transformer/Quickbooks/ProductTransformer.php diff --git a/app/Import/Transformer/Quickbooks/ProductTransformer.php b/app/Import/Transformer/Quickbooks/ProductTransformer.php new file mode 100644 index 000000000000..f86a49eca1ed --- /dev/null +++ b/app/Import/Transformer/Quickbooks/ProductTransformer.php @@ -0,0 +1,88 @@ + 'Name', + 'notes' => 'Description', + 'cost' => 'PurchaseCost', + 'price' => 'UnitPrice', + 'quantity' => 'QtyOnHand', + 'in_stock_quantity' => 'QtyOnHand', + 'created_at' => 'CreateTime', + 'updated_at' => 'LastUpdatedTime', + ]; + /** + * Transforms the JSON data into a ProductModel object. + * + * @param array $data + * @return ProductModel + */ + /** + * Transforms a Customer array into a Product model. + * + * @param array $data + * @return array|bool + */ + public function transform($data) + { + $transformed_data = []; + foreach($this->fillable as $key => $field) { + $transformed_data[$key] = method_exists($this, $method = sprintf("get%s", str_replace(".","",$field)) )? call_user_func([$this, $method],$data,$field) : $this->getString($data, $field); + } + + $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data); + + return $transformed_data->toArray() + ['company_id' => $this->company->id ] ; + } + + public function getString($data, $field) + { + return Arr::get($data, $field); + } + + public function getQtyOnHand($data, $field = null) { + return (int) $this->getString($data, $field); + } + + public function getPurchaseCost($data, $field = null) { + return (float) $this->getString($data, $field); + } + + + public function getUnitPrice($data, $field = null) { + return (float) $this->getString($data, $field); + } + + public function getCreateTime($data, $field = null) + { + return $this->parseDateOrNull($data['MetaData'], 'CreateTime'); + } + + public function getLastUpdatedTime($data, $field = null) + { + return $this->parseDateOrNull($data['MetaData'],'LastUpdatedTime'); + } +} From 4cde94f2032a43ab5de423aacdbe0064d958908e Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:29:32 -0400 Subject: [PATCH 12/69] add item sample data --- tests/Feature/Import/items.json | 204 +++++++++++++++++++++++ tests/Mock/Response/Quickbooks/item.json | 15 ++ 2 files changed, 219 insertions(+) create mode 100644 tests/Feature/Import/items.json create mode 100644 tests/Mock/Response/Quickbooks/item.json diff --git a/tests/Feature/Import/items.json b/tests/Feature/Import/items.json new file mode 100644 index 000000000000..a167db65d8f5 --- /dev/null +++ b/tests/Feature/Import/items.json @@ -0,0 +1,204 @@ +{ + "Item": [ + { + "Name": "Concrete", + "Description": "Concrete for fountain installation", + "UnitPrice": 0, + "sparse": true, + "Id": "3", + "MetaData": { + "CreateTime": "2024-06-16T10:36:03-07:00", + "LastUpdatedTime": "2024-06-19T12:47:47-07:00" + } + }, + { + "Name": "Design", + "Description": "Custom Design", + "UnitPrice": 75, + "sparse": true, + "Id": "4", + "MetaData": { + "CreateTime": "2024-06-16T10:41:38-07:00", + "LastUpdatedTime": "2024-06-16T10:41:38-07:00" + } + }, + { + "Name": "Gardening", + "Description": "Weekly Gardening Service", + "UnitPrice": 0, + "sparse": true, + "Id": "6", + "MetaData": { + "CreateTime": "2024-06-16T10:43:14-07:00", + "LastUpdatedTime": "2024-06-16T10:43:14-07:00" + } + }, + { + "Name": "Hours", + "UnitPrice": 0, + "sparse": true, + "Id": "2", + "MetaData": { + "CreateTime": "2024-06-11T14:42:05-07:00", + "LastUpdatedTime": "2024-06-11T14:42:05-07:00" + } + }, + { + "Name": "Installation", + "Description": "Installation of landscape design", + "UnitPrice": 50, + "sparse": true, + "Id": "7", + "MetaData": { + "CreateTime": "2024-06-16T10:43:54-07:00", + "LastUpdatedTime": "2024-06-16T10:43:54-07:00" + } + }, + { + "Name": "Lighting", + "Description": "Garden Lighting", + "UnitPrice": 0, + "sparse": true, + "Id": "8", + "MetaData": { + "CreateTime": "2024-06-16T10:44:40-07:00", + "LastUpdatedTime": "2024-06-19T12:47:38-07:00" + } + }, + { + "Name": "Maintenance & Repair", + "Description": "Maintenance & Repair", + "UnitPrice": 0, + "sparse": true, + "Id": "9", + "MetaData": { + "CreateTime": "2024-06-16T10:45:18-07:00", + "LastUpdatedTime": "2024-06-16T10:45:18-07:00" + } + }, + { + "Name": "Pest Control", + "Description": "Pest Control Services", + "UnitPrice": 35, + "sparse": true, + "Id": "10", + "MetaData": { + "CreateTime": "2024-06-16T10:45:49-07:00", + "LastUpdatedTime": "2024-06-16T10:45:49-07:00" + } + }, + { + "Name": "Pump", + "Description": "Fountain Pump", + "UnitPrice": 15, + "QtyOnHand": 25, + "sparse": true, + "Id": "11", + "MetaData": { + "CreateTime": "2024-06-16T10:46:45-07:00", + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + } + }, + { + "Name": "Refunds & Allowances", + "Description": "Income due to refunds or allowances", + "UnitPrice": 0, + "sparse": true, + "Id": "12", + "MetaData": { + "CreateTime": "2024-06-16T10:49:18-07:00", + "LastUpdatedTime": "2024-06-16T10:49:18-07:00" + } + }, + { + "Name": "Rock Fountain", + "Description": "Rock Fountain", + "UnitPrice": 275, + "QtyOnHand": 2, + "sparse": true, + "Id": "5", + "MetaData": { + "CreateTime": "2024-06-16T10:42:19-07:00", + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + } + }, + { + "Name": "Rocks", + "Description": "Garden Rocks", + "UnitPrice": 0, + "sparse": true, + "Id": "13", + "MetaData": { + "CreateTime": "2024-06-16T10:50:11-07:00", + "LastUpdatedTime": "2024-06-19T12:47:31-07:00" + } + }, + { + "Name": "Services", + "UnitPrice": 0, + "sparse": true, + "Id": "1", + "MetaData": { + "CreateTime": "2024-06-11T14:42:05-07:00", + "LastUpdatedTime": "2024-06-11T14:42:05-07:00" + } + }, + { + "Name": "Sod", + "Description": "Sod", + "UnitPrice": 0, + "sparse": true, + "Id": "14", + "MetaData": { + "CreateTime": "2024-06-16T10:50:45-07:00", + "LastUpdatedTime": "2024-06-19T12:47:22-07:00" + } + }, + { + "Name": "Soil", + "Description": "2 cubic ft. bag", + "UnitPrice": 10, + "sparse": true, + "Id": "15", + "MetaData": { + "CreateTime": "2024-06-16T10:51:28-07:00", + "LastUpdatedTime": "2024-06-19T12:47:25-07:00" + } + }, + { + "Name": "Sprinkler Heads", + "Description": "Sprinkler Heads", + "UnitPrice": 2, + "QtyOnHand": 25, + "sparse": true, + "Id": "16", + "MetaData": { + "CreateTime": "2024-06-16T10:51:50-07:00", + "LastUpdatedTime": "2024-06-19T12:51:47-07:00" + } + }, + { + "Name": "Sprinkler Pipes", + "Description": "Sprinkler Pipes", + "UnitPrice": 4, + "QtyOnHand": 31, + "sparse": true, + "Id": "17", + "MetaData": { + "CreateTime": "2024-06-16T10:52:07-07:00", + "LastUpdatedTime": "2024-06-19T12:57:24-07:00" + } + }, + { + "Name": "Trimming", + "Description": "Tree and Shrub Trimming", + "UnitPrice": 35, + "sparse": true, + "Id": "18", + "MetaData": { + "CreateTime": "2024-06-16T10:52:42-07:00", + "LastUpdatedTime": "2024-06-16T10:52:42-07:00" + } + } + ] +} \ No newline at end of file diff --git a/tests/Mock/Response/Quickbooks/item.json b/tests/Mock/Response/Quickbooks/item.json new file mode 100644 index 000000000000..816cc4720fb9 --- /dev/null +++ b/tests/Mock/Response/Quickbooks/item.json @@ -0,0 +1,15 @@ + +{ + "Item":{ + "Name": "Pump", + "Description": "Fountain Pump", + "UnitPrice": 15, + "QtyOnHand": 25, + "sparse": true, + "Id": "11", + "MetaData": { + "CreateTime": "2024-06-16T10:46:45-07:00", + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + } + } +} \ No newline at end of file From 2df99a312e577a49e5868f0c9e23c168287cacc9 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:29:51 -0400 Subject: [PATCH 13/69] update quickbook test for product import --- .../Import/Quickbooks/QuickbooksTest.php | 69 +++++++++++++------ 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/tests/Feature/Import/Quickbooks/QuickbooksTest.php b/tests/Feature/Import/Quickbooks/QuickbooksTest.php index 65bc1dab15ff..29a894baa774 100644 --- a/tests/Feature/Import/Quickbooks/QuickbooksTest.php +++ b/tests/Feature/Import/Quickbooks/QuickbooksTest.php @@ -5,17 +5,14 @@ namespace Tests\Feature\Import\Quickbooks; use Tests\TestCase; use App\Import\Providers\Quickbooks; use App\Import\Transformer\BaseTransformer; -use App\Import\Transformer\Quickbooks\ClientTransformer; -use App\Repositories\ClientRepository; -use App\Factory\ClientFactory; use App\Utils\Traits\MakesHash; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Routing\Middleware\ThrottleRequests; use Tests\MockAccountData; use Illuminate\Support\Facades\Cache; -use App\Http\Requests\Client\StoreClientRequest; use Mockery; use App\Models\Client; +use App\Models\Product; use Illuminate\Support\Str; use ReflectionClass; use Illuminate\Support\Facades\Auth; @@ -37,33 +34,34 @@ class QuickbooksTest extends TestCase $this->withoutMiddleware(ThrottleRequests::class); config(['database.default' => config('ninja.db.default')]); $this->makeTestData(); - // $this->withoutExceptionHandling(); + $this->withoutExceptionHandling(); Auth::setUser($this->user); - $this->data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer']; - $hash = Str::random(32); - Cache::put($hash.'-client', base64_encode(json_encode($this->data)), 360); - - $this->quickbooks = Mockery::mock(Quickbooks::class,[[ - 'hash' => $hash, - 'column_map' => ['client' => ['mapping' => []]], - 'skip_header' => true, - 'import_type' => 'invoicely', - ], $this->company ])->makePartial(); + } public function testImportCallsGetDataOnceForClient() { - $this->quickbooks->shouldReceive('getData') + $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer']; + $hash = Str::random(32); + Cache::put($hash.'-client', base64_encode(json_encode($data)), 360); + + $quickbooks = Mockery::mock(Quickbooks::class,[[ + 'hash' => $hash, + 'column_map' => ['client' => ['mapping' => []]], + 'skip_header' => true, + 'import_type' => 'quickbooks', + ], $this->company ])->makePartial(); + $quickbooks->shouldReceive('getData') ->once() ->with('client') - ->andReturn($this->data); + ->andReturn($data); // Mocking the dependencies used within the client method - $this->quickbooks->import('client'); + $quickbooks->import('client'); - $this->assertArrayHasKey('clients', $this->quickbooks->entity_count); - $this->assertGreaterThan(0, $this->quickbooks->entity_count['clients']); + $this->assertArrayHasKey('clients', $quickbooks->entity_count); + $this->assertGreaterThan(0, $quickbooks->entity_count['clients']); $base_transformer = new BaseTransformer($this->company); $this->assertTrue($base_transformer->hasClient('Sonnenschein Family Store')); @@ -73,6 +71,37 @@ class QuickbooksTest extends TestCase $this->assertEquals('Birds@Intuit.com',$contact->contacts()->first()->email); } + public function testImportCallsGetDataOnceForProducts() + { + $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/items.json') ), true))['Item']; + $hash = Str::random(32); + Cache::put($hash.'-item', base64_encode(json_encode($data)), 360); + + $quickbooks = Mockery::mock(Quickbooks::class,[[ + 'hash' => $hash, + 'column_map' => ['item' => ['mapping' => []]], + 'skip_header' => true, + 'import_type' => 'quickbooks', + ], $this->company ])->makePartial(); + $quickbooks->shouldReceive('getData') + ->once() + ->with('product') + ->andReturn($data); + + // Mocking the dependencies used within the client method + + $quickbooks->import('product'); + + $this->assertArrayHasKey('products', $quickbooks->entity_count); + $this->assertGreaterThan(0, $quickbooks->entity_count['products']); + + $base_transformer = new BaseTransformer($this->company); + $this->assertTrue($base_transformer->hasProduct('Gardening')); + $product = Product::where('product_key','Pest Control')->first(); + $this->assertGreaterThanOrEqual( 35, $product->price); + $this->assertLessThanOrEqual(0, $product->quantity); + } + protected function tearDown(): void { Mockery::close(); From 1a4cd7d3f6f1fda223a721996bd9281d89e25859 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 15 Jul 2024 21:30:09 -0400 Subject: [PATCH 14/69] add test case for product transformer --- .../Quickbooks/ProductTransformerTest.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php diff --git a/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php new file mode 100644 index 000000000000..04dbd6ff7312 --- /dev/null +++ b/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php @@ -0,0 +1,44 @@ +create(1234); + + // Read the JSON string from a file and decode into an associative array + $this->product_data = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/item.json') ), true); + $this->transformer = new ProductTransformer($company); + $this->transformed_data = $this->transformer->transform($this->product_data['Item']); + } + + public function testClassExists() + { + $this->assertInstanceOf(ProductTransformer::class, $this->transformer); + } + + public function testTransformReturnsArray() + { + $this->assertIsArray($this->transformed_data); + } + + public function testTransformHasProperties() + { + $this->assertArrayHasKey('product_key', $this->transformed_data); + $this->assertArrayHasKey('price', $this->transformed_data); + $this->assertTrue(is_numeric($this->transformed_data['price'])); + $this->assertEquals(15, (int) $this->transformed_data['price'] ); + $this->assertEquals((int) $this->product_data['Item']['QtyOnHand'], $this->transformed_data['quantity']); + } +} From e3135a8e9990ee65747a53387c1055a08530ce30 Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 16 Jul 2024 13:02:59 -0400 Subject: [PATCH 15/69] added get client info --- .../Quickbooks/InvoiceTransformer.php | 67 ++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php index a49efaedf576..37edb38e151e 100644 --- a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -17,6 +17,7 @@ use App\Import\ImportException; use App\DataMapper\InvoiceItem; use App\Models\Invoice as Model; use App\Import\Transformer\BaseTransformer; +use App\Import\Transformer\Quickbooks\ClientTransformer; /** * Class InvoiceTransformer. @@ -45,7 +46,7 @@ class InvoiceTransformer extends BaseTransformer $transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field)); } - return (new Model)->fillable(array_keys($this->fillable))->fill($transformed)->toArray(); + return (new Model)->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + $this->getInvoiceClient($data); } public function getTotalAmt($data) @@ -63,10 +64,72 @@ class InvoiceTransformer extends BaseTransformer 'amount' => $this->getString($item,'Amount') ]; }, array_filter($this->getString($data,'Line'), function ($item) { - return $this->getString($item,'DetailType') === 'SalesItemLineDetail'; + return $this->getString($item,'DetailType') !== 'SubTotalLineDetail'; })); } + public function getInvoiceClient($data, $field = null) { + /** + * "CustomerRef": { + "value": "23", + "name": ""Barnett Design + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "58", + "Line1": "Shara Barnett", + "Line2": "Barnett Design", + "Line3": "19 Main St.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + },"BillEmail": { + "Address": "Design@intuit.com" + }, + [ + 'name' => 'CompanyName', + 'phone' => 'PrimaryPhone.FreeFormNumber', + 'country_id' => 'BillAddr.Country', + 'state' => 'BillAddr.CountrySubDivisionCode', + 'address1' => 'BillAddr.Line1', + 'city' => 'BillAddr.City', + 'postal_code' => 'BillAddr.PostalCode', + 'shipping_country_id' => 'ShipAddr.Country', + 'shipping_state' => 'ShipAddr.CountrySubDivisionCode', + 'shipping_address1' => 'ShipAddr.Line1', + 'shipping_city' => 'ShipAddr.City', + 'shipping_postal_code' => 'ShipAddr.PostalCode', + 'public_notes' => 'Notes' + ]; + + */ + $bill_address = (object) $this->getString($data, 'BillAddr'); + $ship_address = $this->getString($data, 'ShipAddr'); + $customer = explode(" ", $this->getString($data, 'CustomerRef.name')); + $customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]]; + $has_company = property_exists($bill_address, 'Line4'); + $client = + [ + "CompanyName" => $has_company? $bill_address->Line2 : $bill_address->Line1, + "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_filter(explode(" ", $has_company? $bill_address->Line4 : $bill_address->Line3 ))) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ], + "ShipAddr" => $ship_address + ] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]]; + $client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address')); + + return ['client'=> (new ClientTransformer($this->company))->transform($client), 'client_id'=> $client_id ]; + } + public function getString($data,$field) { return Arr::get($data,$field); } From 519bb573687d50ea56f8eebbdab49ee530251e4d Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 16 Jul 2024 13:03:17 -0400 Subject: [PATCH 16/69] update sample data --- tests/Feature/Import/invoices.json | 4101 ++++++++++++++++ .../Response/Http/200-invoice-response.txt | 4247 ++++++++++++++++- tests/Mock/Response/Quickbooks/invoice.json | 241 +- 3 files changed, 8297 insertions(+), 292 deletions(-) create mode 100644 tests/Feature/Import/invoices.json diff --git a/tests/Feature/Import/invoices.json b/tests/Feature/Import/invoices.json new file mode 100644 index 000000000000..49bab4f6ebe3 --- /dev/null +++ b/tests/Feature/Import/invoices.json @@ -0,0 +1,4101 @@ +{ + "Invoice": [ + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "130", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-19T13:16:17-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + }, + "CustomField": [], + "DocNumber": "1037", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Fountain Pump", + "Amount": 12.75, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 12.75, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Concrete for fountain installation", + "Amount": 47.5, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "3", + "name": "Concrete" + }, + "UnitPrice": 9.5, + "Qty": 5, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 335.25, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 26.82, + "TaxLine": [ + { + "Amount": 26.82, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 335.25 + } + } + ] + }, + "CustomerRef": { + "value": "24", + "name": "Sonnenschein Family Store" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "95", + "Line1": "Russ Sonnenschein", + "Line2": "Sonnenschein Family Store", + "Line3": "5647 Cypress Hill Ave.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "ShipAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 362.07, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Familiystore@intuit.com" + }, + "Balance": 362.07 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "129", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-19T13:15:36-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "CustomField": [], + "DocNumber": "1036", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sod", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 10, + "Qty": 5, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "2 cubic ft. bag", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "15", + "name": "Soil" + }, + "UnitPrice": 10, + "Qty": 5, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Weekly Gardening Service", + "Amount": 87.5, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3.5, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "5", + "LineNum": 5, + "Description": "Fountain Pump", + "Amount": 15, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 15, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 477.5, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "94", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 477.5, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 477.5 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "96", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:30:49-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:13:33-07:00" + }, + "CustomField": [], + "DocNumber": "1031", + "TxnDate": "2024-04-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "128", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 90, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 30, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 365, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 22, + "TaxLine": [ + { + "Amount": 22, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 275 + } + } + ] + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "84", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-04", + "TotalAmt": 387, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "12", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T15:04:04-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:59:21-07:00" + }, + "CustomField": [], + "DocNumber": "1004", + "TxnDate": "2024-06-07", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "120", + "TxnType": "Payment" + }, + { + "TxnId": "61", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Heads", + "Amount": 20, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "16", + "name": "Sprinkler Heads" + }, + "UnitPrice": 2, + "Qty": 10, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 24, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 6, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Sod", + "Amount": 1750, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 35, + "Qty": 50, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Installation Hours", + "Amount": 400, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "1", + "name": "Services" + }, + "UnitPrice": 50, + "Qty": 8, + "ItemAccountRef": { + "value": "1", + "name": "Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 2194, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 175.52, + "TaxLine": [ + { + "Amount": 175.52, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 2194 + } + } + ] + }, + "CustomerRef": { + "value": "3", + "name": "Cool Cars" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "50", + "Line1": "Grace Pariente", + "Line2": "Cool Cars", + "Line3": "65 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-07", + "TotalAmt": 2369.52, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Cool_Cars@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "119", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-19T12:57:24-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:57:24-07:00" + }, + "CustomField": [], + "DocNumber": "1035", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 16, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 4, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 291, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 23.28, + "TaxLine": [ + { + "Amount": 23.28, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 291 + } + } + ] + }, + "CustomerRef": { + "value": "17", + "name": "Mark Cho" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "91", + "Line1": "Mark Cho", + "Line2": "36 Willow Rd", + "Line3": "Menlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 314.28, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Mark@Cho.com" + }, + "Balance": 314.28 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "63", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-17T15:28:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:52:44-07:00" + }, + "CustomField": [], + "DocNumber": "1017", + "TxnDate": "2024-06-03", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "116", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "70", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-03", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "106", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T13:45:12-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:45:12-07:00" + }, + "CustomField": [], + "DocNumber": "1034", + "TxnDate": "2024-06-18", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "4", + "TxnType": "TimeActivity" + }, + { + "TxnId": "5", + "TxnType": "TimeActivity" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Tree and Shrub Trimming", + "Amount": 30, + "LinkedTxn": [ + { + "TxnId": "4", + "TxnType": "TimeActivity" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-17", + "ItemRef": { + "value": "18", + "name": "Trimming" + }, + "UnitPrice": 15, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Lighting", + "Amount": 45, + "LinkedTxn": [ + { + "TxnId": "5", + "TxnType": "TimeActivity" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-17", + "ItemRef": { + "value": "8", + "name": "Lighting" + }, + "UnitPrice": 15, + "Qty": 3, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 3.6, + "TaxLine": [ + { + "Amount": 3.6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 45 + } + } + ] + }, + "CustomerRef": { + "value": "21", + "name": "Rondonuwu Fruit and Vegi" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "88", + "Line1": "Rondonuwu Fruit and Vegi", + "Line2": "847 California Ave.", + "Line3": "San Jose, CA 95021", + "Lat": "37.01", + "Long": "-121.57" + }, + "ShipAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-18", + "TotalAmt": 78.6, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Tony@Rondonuwu.com" + }, + "Balance": 78.6 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "103", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:41:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:42:08-07:00" + }, + "CustomField": [], + "DocNumber": "1033", + "TxnDate": "2024-06-18", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Custom Design", + "Amount": 262.5, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 3.5, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Fountain Pump", + "Amount": 45, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 22.5, + "Qty": 2, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 582.5, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 46.6, + "TaxLine": [ + { + "Amount": 46.6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 582.5 + } + } + ] + }, + "CustomerRef": { + "value": "10", + "name": "Geeta Kalapatapu" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "87", + "Line1": "Geeta Kalapatapu", + "Line2": "1987 Main St.", + "Line3": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-18", + "TotalAmt": 629.1, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Geeta@Kalapatapu.com" + }, + "Balance": 629.1 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "67", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-18T12:40:06-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:39:32-07:00" + }, + "CustomField": [], + "DocNumber": "1021", + "TxnDate": "2024-05-28", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "101", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "2 cubic ft. bag", + "Amount": 150, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "15", + "name": "Soil" + }, + "UnitPrice": 10, + "Qty": 15, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 425, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 34, + "TaxLine": [ + { + "Amount": 34, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 425 + } + } + ] + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "74", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-27", + "TotalAmt": 459, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 239 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "99", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T13:36:31-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:36:31-07:00" + }, + "CustomField": [], + "DocNumber": "1032", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sod", + "Amount": 300, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 15, + "Qty": 20, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Rocks", + "Amount": 84, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 7, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 384, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 30.72, + "TaxLine": [ + { + "Amount": 30.72, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 384 + } + } + ] + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "85", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 414.72, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 414.72 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "42", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-17T11:24:29-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:35:42-07:00" + }, + "CustomField": [], + "DocNumber": "1013", + "TxnDate": "2024-06-07", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "29", + "TxnType": "StatementCharge" + }, + { + "TxnId": "98", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "60", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-07", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "95", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:29:56-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:31:45-07:00" + }, + "CustomField": [], + "DocNumber": "1030", + "TxnDate": "2024-03-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "97", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Sod", + "Amount": 131.25, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 8.75, + "Qty": 15, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 216.25, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 10.5, + "TaxLine": [ + { + "Amount": 10.5, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 131.25 + } + } + ] + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "83", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-04-03", + "TotalAmt": 226.75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "93", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:27:49-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:28:29-07:00" + }, + "CustomField": [], + "DocNumber": "1029", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "94", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Concrete for fountain installation", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "3", + "name": "Concrete" + }, + "UnitPrice": 15, + "Qty": 5, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Rocks", + "Amount": 72, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 6, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 422, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "3" + }, + "TotalTax": 38.4, + "TaxLine": [ + { + "Amount": 29.96, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "1" + }, + "PercentBased": true, + "TaxPercent": 7.1, + "NetAmountTaxable": 422 + } + }, + { + "Amount": 8.44, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "2" + }, + "PercentBased": true, + "TaxPercent": 2, + "NetAmountTaxable": 422 + } + } + ] + }, + "CustomerRef": { + "value": "5", + "name": "Dukes Basketball Camp" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "82", + "Line1": "Peter Dukes", + "Line2": "Dukes Basketball Camp", + "Line3": "25 Court St.", + "Line4": "Tucson, AZ 85719", + "Lat": "32.2546522", + "Long": "-110.9447027" + }, + "ShipAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 460.4, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Dukes_bball@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "68", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T12:41:24-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:25:43-07:00" + }, + "CustomField": [], + "DocNumber": "1022", + "TxnDate": "2024-05-28", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "12", + "name": "Jeff's Jalopies" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "75", + "Line1": "Jeff Chin", + "Line2": "Jeff's Jalopies", + "Line3": "12 Willow Rd.", + "Line4": "Menlo Park, CA 94305", + "Lat": "37.4135757", + "Long": "-122.1689284" + }, + "ShipAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-27", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Jalopies@intuit.com" + }, + "Balance": 81 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "9", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T14:49:30-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:25:20-07:00" + }, + "CustomField": [], + "DocNumber": "1001", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PrivateNote": "Front yard, hedges, and sidewalks", + "LinkedTxn": [ + { + "TxnId": "31", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 100, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 100, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 8, + "TaxLine": [ + { + "Amount": 8, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 100 + } + } + ] + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "47", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 108, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "13", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-16T15:05:48-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:23:52-07:00" + }, + "CustomField": [], + "DocNumber": "1005", + "TxnDate": "2024-06-10", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "33", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 50, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 4, + "TaxLine": [ + { + "Amount": 4, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 50 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "51", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-10", + "TotalAmt": 54, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 4 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "14", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T15:06:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:23:11-07:00" + }, + "CustomField": [], + "DocNumber": "1006", + "TxnDate": "2024-05-11", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "15", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6.4, + "TaxLine": [ + { + "Amount": 6.4, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 80 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "52", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-10", + "TotalAmt": 86.4, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "92", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:22:13-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:22:27-07:00" + }, + "CustomField": [], + "DocNumber": "1028", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "81", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 81 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "10", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-16T14:57:16-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:56:01-07:00" + }, + "CustomField": [], + "DocNumber": "1002", + "TxnDate": "2024-03-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "76", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 140, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 35, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 175, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "2", + "name": "Bill's Windsurf Shop" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "48", + "Line1": "Bill Lucchini", + "Line2": "Bill's Windsurf Shop", + "Line3": "12 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-04-03", + "TotalAmt": 175, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Surf@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "75", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:54:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:54:08-07:00" + }, + "CustomField": [], + "DocNumber": "1027", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 85, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "2", + "name": "Bill's Windsurf Shop" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "80", + "Line1": "Bill Lucchini", + "Line2": "Bill's Windsurf Shop", + "Line3": "12 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 85, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Surf@Intuit.com" + }, + "Balance": 85 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "71", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-18T12:49:30-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:51:28-07:00" + }, + "CustomField": [], + "DocNumber": "1025", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "74", + "TxnType": "Payment" + }, + { + "TxnId": "72", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 120, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 30, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Maintenance & Repair", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "9", + "name": "Maintenance & Repair" + }, + "UnitPrice": 50, + "Qty": 1, + "ItemAccountRef": { + "value": "53", + "name": "Landscaping Services:Labor:Maintenance and Repair" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 205, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "78", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 205, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "70", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:44:45-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:44:45-07:00" + }, + "CustomField": [], + "DocNumber": "1024", + "TxnDate": "2024-04-11", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Pipes", + "Amount": 48, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 12, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 60, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 15, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Garden Rocks", + "Amount": 48, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 4, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 156, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "20", + "name": "Red Rock Diner" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "77", + "Line1": "Stephanie Martini", + "Line2": "Red Rock Diner", + "Line3": "500 Red Rock Rd.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-11", + "TotalAmt": 156, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 156, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "69", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:42:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:42:59-07:00" + }, + "CustomField": [], + "DocNumber": "1023", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "46", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Pest Control Services", + "Amount": 70, + "LinkedTxn": [ + { + "TxnId": "46", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 2, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 70, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "20", + "name": "Red Rock Diner" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "76", + "Line1": "Stephanie Martini", + "Line2": "Red Rock Diner", + "Line3": "500 Red Rock Rd.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 70, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 70 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "65", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-17T15:29:27-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:18:01-07:00" + }, + "CustomField": [], + "DocNumber": "1019", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "72", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 80 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "64", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T15:29:00-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T15:29:00-07:00" + }, + "CustomField": [], + "DocNumber": "1018", + "TxnDate": "2024-06-10", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "71", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-10", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 80 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "60", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T15:18:18-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T15:18:18-07:00" + }, + "CustomField": [], + "DocNumber": "1016", + "TxnDate": "2024-05-01", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "16", + "name": "Kookies by Kathy" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "69", + "Line1": "Kathy Kuplis", + "Line2": "Kookies by Kathy", + "Line3": "789 Sugar Lane", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-31", + "TotalAmt": 75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 75, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "49", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:43:20-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:43:20-07:00" + }, + "CustomField": [], + "DocNumber": "1015", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 300, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 4, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Installation of landscape design", + "Amount": 250, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "7", + "name": "Installation" + }, + "UnitPrice": 50, + "Qty": 5, + "ItemAccountRef": { + "value": "52", + "name": "Landscaping Services:Labor:Installation" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Garden Rocks", + "Amount": 180, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 22.5, + "Qty": 8, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 1005, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + }, + { + "Amount": 50.25, + "DetailType": "DiscountLineDetail", + "DiscountLineDetail": { + "PercentBased": true, + "DiscountPercent": 5, + "DiscountAccountRef": { + "value": "86", + "name": "Discounts given" + } + } + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "18", + "name": "Paulsen Medical Supplies" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "68", + "Line1": "Kathy Paulsen", + "Line2": "Paulsen Medical Supplies", + "Line3": "900 Main St.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "19", + "Line1": "38921 S. Boise Ave", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.3989376", + "Long": "-122.1443935" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 954.75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Medical@intuit.com" + }, + "Balance": 954.75 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "27", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-16T15:38:07-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:17:33-07:00" + }, + "CustomField": [], + "DocNumber": "1009", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "26", + "TxnType": "ReimburseCharge" + }, + { + "TxnId": "40", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Lumber", + "Amount": 103.55, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-16", + "ItemRef": { + "value": "1", + "name": "Services" + }, + "MarkupInfo": { + "PercentBased": false, + "Value": 103.55, + "MarkUpIncomeAccountRef": { + "value": "1", + "name": "Services" + } + }, + "ItemAccountRef": { + "value": "1", + "name": "Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 103.55, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "55", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 103.55, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "39", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:16:52-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "CustomField": [], + "DocNumber": "1012", + "TxnDate": "2024-06-05", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Heads", + "Amount": 30, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "16", + "name": "Sprinkler Heads" + }, + "UnitPrice": 2, + "Qty": 15, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 305, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + }, + { + "Amount": 30.5, + "DetailType": "DiscountLineDetail", + "DiscountLineDetail": { + "PercentBased": true, + "DiscountPercent": 10, + "DiscountAccountRef": { + "value": "86", + "name": "Discounts given" + } + } + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "23", + "name": "Barnett Design" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "58", + "Line1": "Shara Barnett", + "Line2": "Barnett Design", + "Line3": "19 Main St.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-05", + "TotalAmt": 274.5, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "Design@intuit.com" + }, + "Balance": 274.5, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "34", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:09:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:09:08-07:00" + }, + "CustomField": [], + "DocNumber": "1010", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 375, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 5, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 375, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "29", + "name": "Weiskopf Consulting" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "56", + "Line1": "Nicola Weiskopf", + "Line2": "Weiskopf Consulting", + "Line3": "45612 Main St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 375, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "Consulting@intuit.com" + }, + "Balance": 375, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "16", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-16T15:10:40-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:06:49-07:00" + }, + "CustomField": [], + "DocNumber": "1007", + "TxnDate": "2024-05-25", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "32", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 750, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 10, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 750, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "13", + "name": "John Melton" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "53", + "Line1": "John Melton", + "Line2": "85 Pine St.", + "Line3": "Menlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-24", + "TotalAmt": 750, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "John@Melton.com" + }, + "Balance": 450 + } + ], + "startPosition": 1, + "maxResults": 31, + "totalCount": 31 + } \ No newline at end of file diff --git a/tests/Mock/Response/Http/200-invoice-response.txt b/tests/Mock/Response/Http/200-invoice-response.txt index 8038934e64ea..2d8a7a1fbe9e 100644 --- a/tests/Mock/Response/Http/200-invoice-response.txt +++ b/tests/Mock/Response/Http/200-invoice-response.txt @@ -9,153 +9,4106 @@ Access-Control-Max-Age: 300 Access-Control-Allow-Credentials: true { - "Invoice": { - "TxnDate": "2014-09-19", - "domain": "QBO", - "PrintStatus": "NeedToPrint", - "SalesTermRef": { - "value": "3" - }, - "TotalAmt": 362.07, - "Line": [ - { - "Description": "Rock Fountain", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 1, - "UnitPrice": 275, - "ItemRef": { - "name": "Rock Fountain", - "value": "5" - } - }, - "LineNum": 1, - "Amount": 275.0, - "Id": "1" - }, - { - "Description": "Fountain Pump", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 1, - "UnitPrice": 12.75, - "ItemRef": { - "name": "Pump", - "value": "11" - } - }, - "LineNum": 2, - "Amount": 12.75, - "Id": "2" - }, - { - "Description": "Concrete for fountain installation", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 5, - "UnitPrice": 9.5, - "ItemRef": { - "name": "Concrete", - "value": "3" - } - }, - "LineNum": 3, - "Amount": 47.5, - "Id": "3" - }, - { - "DetailType": "SubTotalLineDetail", - "Amount": 335.25, - "SubTotalLineDetail": {} - } - ], - "DueDate": "2014-10-19", - "ApplyTaxAfterDiscount": false, - "DocNumber": "1037", - "sparse": false, - "CustomerMemo": { - "value": "Thank you for your business and have a great day!" - }, - "ProjectRef": { - "value": "39298045" - }, - "Deposit": 0, - "Balance": 362.07, - "CustomerRef": { - "name": "Sonnenschein Family Store", - "value": "24" - }, - "TxnTaxDetail": { - "TxnTaxCodeRef": { - "value": "2" - }, - "TotalTax": 26.82, - "TaxLine": [ - { - "DetailType": "TaxLineDetail", - "Amount": 26.82, - "TaxLineDetail": { - "NetAmountTaxable": 335.25, - "TaxPercent": 8, - "TaxRateRef": { - "value": "3" - }, - "PercentBased": true - } - } - ] - }, - "SyncToken": "0", - "LinkedTxn": [ - { - "TxnId": "100", - "TxnType": "Estimate" - } - ], - "BillEmail": { - "Address": "Familiystore@intuit.com" - }, - "ShipAddr": { - "City": "Middlefield", - "Line1": "5647 Cypress Hill Ave.", - "PostalCode": "94303", - "Lat": "37.4238562", - "Long": "-122.1141681", - "CountrySubDivisionCode": "CA", - "Id": "25" - }, - "EmailStatus": "NotSet", - "BillAddr": { - "Line4": "Middlefield, CA 94303", - "Line3": "5647 Cypress Hill Ave.", - "Line2": "Sonnenschein Family Store", - "Line1": "Russ Sonnenschein", - "Long": "-122.1141681", - "Lat": "37.4238562", - "Id": "95" - }, + "QueryResponse": { + "Invoice": [ + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "130", + "SyncToken": "0", "MetaData": { - "CreateTime": "2014-09-19T13:16:17-07:00", - "LastUpdatedTime": "2014-09-19T13:16:17-07:00" - }, - "CustomField": [ - { - "DefinitionId": "1", - "StringValue": "102", - "Type": "StringType", - "Name": "Crew #" + "CreateTime": "2024-06-19T13:16:17-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:16:17-07:00" + }, + "CustomField": [], + "DocNumber": "1037", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } } - ], - "Id": "130" - }, - "time": "2015-07-24T10:48:27.082-07:00" + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Fountain Pump", + "Amount": 12.75, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 12.75, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Concrete for fountain installation", + "Amount": 47.5, + "LinkedTxn": [ + { + "TxnId": "100", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "3", + "name": "Concrete" + }, + "UnitPrice": 9.5, + "Qty": 5, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 335.25, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 26.82, + "TaxLine": [ + { + "Amount": 26.82, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 335.25 + } + } + ] + }, + "CustomerRef": { + "value": "24", + "name": "Sonnenschein Family Store" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "95", + "Line1": "Russ Sonnenschein", + "Line2": "Sonnenschein Family Store", + "Line3": "5647 Cypress Hill Ave.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "ShipAddr": { + "Id": "25", + "Line1": "5647 Cypress Hill Ave.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.4238562", + "Long": "-122.1141681" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 362.07, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Familiystore@intuit.com" + }, + "Balance": 362.07 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "129", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-19T13:15:36-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:15:36-07:00" + }, + "CustomField": [], + "DocNumber": "1036", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sod", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 10, + "Qty": 5, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "2 cubic ft. bag", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "15", + "name": "Soil" + }, + "UnitPrice": 10, + "Qty": 5, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Weekly Gardening Service", + "Amount": 87.5, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3.5, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "5", + "LineNum": 5, + "Description": "Fountain Pump", + "Amount": 15, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 15, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 477.5, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "94", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 477.5, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 477.5 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "96", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:30:49-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T13:13:33-07:00" + }, + "CustomField": [], + "DocNumber": "1031", + "TxnDate": "2024-04-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "128", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 90, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 30, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 365, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 22, + "TaxLine": [ + { + "Amount": 22, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 275 + } + } + ] + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "84", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-04", + "TotalAmt": 387, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "12", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T15:04:04-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:59:21-07:00" + }, + "CustomField": [], + "DocNumber": "1004", + "TxnDate": "2024-06-07", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "120", + "TxnType": "Payment" + }, + { + "TxnId": "61", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Heads", + "Amount": 20, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "16", + "name": "Sprinkler Heads" + }, + "UnitPrice": 2, + "Qty": 10, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 24, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 6, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Sod", + "Amount": 1750, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 35, + "Qty": 50, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Installation Hours", + "Amount": 400, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "1", + "name": "Services" + }, + "UnitPrice": 50, + "Qty": 8, + "ItemAccountRef": { + "value": "1", + "name": "Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 2194, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 175.52, + "TaxLine": [ + { + "Amount": 175.52, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 2194 + } + } + ] + }, + "CustomerRef": { + "value": "3", + "name": "Cool Cars" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "50", + "Line1": "Grace Pariente", + "Line2": "Cool Cars", + "Line3": "65 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-07", + "TotalAmt": 2369.52, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Cool_Cars@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "119", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-19T12:57:24-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:57:24-07:00" + }, + "CustomField": [], + "DocNumber": "1035", + "TxnDate": "2024-06-19", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 16, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 4, + "ItemAccountRef": { + "value": "79", + "name": "Sales of Product Income" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 291, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 23.28, + "TaxLine": [ + { + "Amount": 23.28, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 291 + } + } + ] + }, + "CustomerRef": { + "value": "17", + "name": "Mark Cho" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "91", + "Line1": "Mark Cho", + "Line2": "36 Willow Rd", + "Line3": "Menlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "17", + "Line1": "36 Willow Rd", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.450412", + "Long": "-122.170593" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-19", + "TotalAmt": 314.28, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Mark@Cho.com" + }, + "Balance": 314.28 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "63", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-17T15:28:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-19T12:52:44-07:00" + }, + "CustomField": [], + "DocNumber": "1017", + "TxnDate": "2024-06-03", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "116", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "70", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-03", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "106", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T13:45:12-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:45:12-07:00" + }, + "CustomField": [], + "DocNumber": "1034", + "TxnDate": "2024-06-18", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "4", + "TxnType": "TimeActivity" + }, + { + "TxnId": "5", + "TxnType": "TimeActivity" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Tree and Shrub Trimming", + "Amount": 30, + "LinkedTxn": [ + { + "TxnId": "4", + "TxnType": "TimeActivity" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-17", + "ItemRef": { + "value": "18", + "name": "Trimming" + }, + "UnitPrice": 15, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Lighting", + "Amount": 45, + "LinkedTxn": [ + { + "TxnId": "5", + "TxnType": "TimeActivity" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-17", + "ItemRef": { + "value": "8", + "name": "Lighting" + }, + "UnitPrice": 15, + "Qty": 3, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 3.6, + "TaxLine": [ + { + "Amount": 3.6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 45 + } + } + ] + }, + "CustomerRef": { + "value": "21", + "name": "Rondonuwu Fruit and Vegi" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "88", + "Line1": "Rondonuwu Fruit and Vegi", + "Line2": "847 California Ave.", + "Line3": "San Jose, CA 95021", + "Lat": "37.01", + "Long": "-121.57" + }, + "ShipAddr": { + "Id": "22", + "Line1": "847 California Ave.", + "City": "San Jose", + "CountrySubDivisionCode": "CA", + "PostalCode": "95021", + "Lat": "37.3313585", + "Long": "-121.911372" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-18", + "TotalAmt": 78.6, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Tony@Rondonuwu.com" + }, + "Balance": 78.6 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "103", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:41:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:42:08-07:00" + }, + "CustomField": [], + "DocNumber": "1033", + "TxnDate": "2024-06-18", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Custom Design", + "Amount": 262.5, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 3.5, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Fountain Pump", + "Amount": 45, + "LinkedTxn": [ + { + "TxnId": "41", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "11", + "name": "Pump" + }, + "UnitPrice": 22.5, + "Qty": 2, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 582.5, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 46.6, + "TaxLine": [ + { + "Amount": 46.6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 582.5 + } + } + ] + }, + "CustomerRef": { + "value": "10", + "name": "Geeta Kalapatapu" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "87", + "Line1": "Geeta Kalapatapu", + "Line2": "1987 Main St.", + "Line3": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "10", + "Line1": "1987 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-18", + "TotalAmt": 629.1, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Geeta@Kalapatapu.com" + }, + "Balance": 629.1 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "67", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-18T12:40:06-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:39:32-07:00" + }, + "CustomField": [], + "DocNumber": "1021", + "TxnDate": "2024-05-28", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "101", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "2 cubic ft. bag", + "Amount": 150, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "15", + "name": "Soil" + }, + "UnitPrice": 10, + "Qty": 15, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 425, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 34, + "TaxLine": [ + { + "Amount": 34, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 425 + } + } + ] + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "74", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-27", + "TotalAmt": 459, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 239 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "99", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T13:36:31-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:36:31-07:00" + }, + "CustomField": [], + "DocNumber": "1032", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sod", + "Amount": 300, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 15, + "Qty": 20, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Rocks", + "Amount": 84, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 7, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 384, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 30.72, + "TaxLine": [ + { + "Amount": 30.72, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 384 + } + } + ] + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "85", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 414.72, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 414.72 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "42", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-17T11:24:29-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:35:42-07:00" + }, + "CustomField": [], + "DocNumber": "1013", + "TxnDate": "2024-06-07", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "29", + "TxnType": "StatementCharge" + }, + { + "TxnId": "98", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "60", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-07", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "95", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:29:56-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:31:45-07:00" + }, + "CustomField": [], + "DocNumber": "1030", + "TxnDate": "2024-03-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "97", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Sod", + "Amount": 131.25, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "14", + "name": "Sod" + }, + "UnitPrice": 8.75, + "Qty": 15, + "ItemAccountRef": { + "value": "49", + "name": "Landscaping Services:Job Materials:Plants and Soil" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 216.25, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 10.5, + "TaxLine": [ + { + "Amount": 10.5, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 131.25 + } + } + ] + }, + "CustomerRef": { + "value": "8", + "name": "0969 Ocean View Road" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "83", + "Line1": "Sasha Tillou", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "8", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-04-03", + "TotalAmt": 226.75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "93", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:27:49-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:28:29-07:00" + }, + "CustomField": [], + "DocNumber": "1029", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "94", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Concrete for fountain installation", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "3", + "name": "Concrete" + }, + "UnitPrice": 15, + "Qty": 5, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Garden Rocks", + "Amount": 72, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 6, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 422, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "3" + }, + "TotalTax": 38.4, + "TaxLine": [ + { + "Amount": 29.96, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "1" + }, + "PercentBased": true, + "TaxPercent": 7.1, + "NetAmountTaxable": 422 + } + }, + { + "Amount": 8.44, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "2" + }, + "PercentBased": true, + "TaxPercent": 2, + "NetAmountTaxable": 422 + } + } + ] + }, + "CustomerRef": { + "value": "5", + "name": "Dukes Basketball Camp" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "82", + "Line1": "Peter Dukes", + "Line2": "Dukes Basketball Camp", + "Line3": "25 Court St.", + "Line4": "Tucson, AZ 85719", + "Lat": "32.2546522", + "Long": "-110.9447027" + }, + "ShipAddr": { + "Id": "6", + "Line1": "25 Court St.", + "City": "Tucson", + "CountrySubDivisionCode": "AZ", + "PostalCode": "85719", + "Lat": "32.2841116", + "Long": "-110.9744298" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 460.4, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Dukes_bball@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "68", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T12:41:24-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:25:43-07:00" + }, + "CustomField": [], + "DocNumber": "1022", + "TxnDate": "2024-05-28", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "12", + "name": "Jeff's Jalopies" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "75", + "Line1": "Jeff Chin", + "Line2": "Jeff's Jalopies", + "Line3": "12 Willow Rd.", + "Line4": "Menlo Park, CA 94305", + "Lat": "37.4135757", + "Long": "-122.1689284" + }, + "ShipAddr": { + "Id": "12", + "Line1": "12 Willow Rd.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94305", + "Lat": "37.4495308", + "Long": "-122.1726923" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-27", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Jalopies@intuit.com" + }, + "Balance": 81 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "9", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T14:49:30-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:25:20-07:00" + }, + "CustomField": [], + "DocNumber": "1001", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "PrivateNote": "Front yard, hedges, and sidewalks", + "LinkedTxn": [ + { + "TxnId": "31", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 100, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 100, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 8, + "TaxLine": [ + { + "Amount": 8, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 100 + } + } + ] + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "47", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 108, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "13", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-16T15:05:48-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:23:52-07:00" + }, + "CustomField": [], + "DocNumber": "1005", + "TxnDate": "2024-06-10", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "33", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 50, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 4, + "TaxLine": [ + { + "Amount": 4, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 50 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "51", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-10", + "TotalAmt": 54, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 4 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "14", + "SyncToken": "3", + "MetaData": { + "CreateTime": "2024-06-16T15:06:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:23:11-07:00" + }, + "CustomField": [], + "DocNumber": "1006", + "TxnDate": "2024-05-11", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "15", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6.4, + "TaxLine": [ + { + "Amount": 6.4, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 80 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "52", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-10", + "TotalAmt": 86.4, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "92", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-18T13:22:13-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T13:22:27-07:00" + }, + "CustomField": [], + "DocNumber": "1028", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TxnTaxCodeRef": { + "value": "2" + }, + "TotalTax": 6, + "TaxLine": [ + { + "Amount": 6, + "DetailType": "TaxLineDetail", + "TaxLineDetail": { + "TaxRateRef": { + "value": "3" + }, + "PercentBased": true, + "TaxPercent": 8, + "NetAmountTaxable": 75 + } + } + ] + }, + "CustomerRef": { + "value": "9", + "name": "55 Twin Lane" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "81", + "Line1": "Amelia", + "Line2": "Freeman Sporting Goods", + "Line3": "370 Easy St.", + "Line4": "Middlefield, CA 94482", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "9", + "Line1": "370 Easy St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94482", + "Lat": "37.4031672", + "Long": "-122.0642815" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 81, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sporting_goods@intuit.com" + }, + "Balance": 81 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "10", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-16T14:57:16-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:56:01-07:00" + }, + "CustomField": [], + "DocNumber": "1002", + "TxnDate": "2024-03-04", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "76", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 140, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 35, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 175, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "2", + "name": "Bill's Windsurf Shop" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "48", + "Line1": "Bill Lucchini", + "Line2": "Bill's Windsurf Shop", + "Line3": "12 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-04-03", + "TotalAmt": 175, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Surf@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "75", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:54:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:54:08-07:00" + }, + "CustomField": [], + "DocNumber": "1027", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 2, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 85, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "2", + "name": "Bill's Windsurf Shop" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "80", + "Line1": "Bill Lucchini", + "Line2": "Bill's Windsurf Shop", + "Line3": "12 Ocean Dr.", + "Line4": "Half Moon Bay, CA 94213", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 85, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Surf@Intuit.com" + }, + "Balance": 85 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "71", + "SyncToken": "2", + "MetaData": { + "CreateTime": "2024-06-18T12:49:30-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:51:28-07:00" + }, + "CustomField": [], + "DocNumber": "1025", + "TxnDate": "2024-05-02", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "74", + "TxnType": "Payment" + }, + { + "TxnId": "72", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 120, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 30, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Pest Control Services", + "Amount": 35, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 1, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Maintenance & Repair", + "Amount": 50, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "9", + "name": "Maintenance & Repair" + }, + "UnitPrice": 50, + "Qty": 1, + "ItemAccountRef": { + "value": "53", + "name": "Landscaping Services:Labor:Maintenance and Repair" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 205, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "1", + "name": "Amy's Bird Sanctuary" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "78", + "Line1": "Amy Lauterbach", + "Line2": "Amy's Bird Sanctuary", + "Line3": "4581 Finch St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "2", + "Line1": "4581 Finch St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-01", + "TotalAmt": 205, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Birds@Intuit.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "70", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:44:45-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:44:45-07:00" + }, + "CustomField": [], + "DocNumber": "1024", + "TxnDate": "2024-04-11", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Pipes", + "Amount": 48, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 12, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Sprinkler Pipes", + "Amount": 60, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "17", + "name": "Sprinkler Pipes" + }, + "UnitPrice": 4, + "Qty": 15, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Garden Rocks", + "Amount": 48, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 12, + "Qty": 4, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 156, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "20", + "name": "Red Rock Diner" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "77", + "Line1": "Stephanie Martini", + "Line2": "Red Rock Diner", + "Line3": "500 Red Rock Rd.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-11", + "TotalAmt": 156, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 156, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "69", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-18T12:42:59-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:42:59-07:00" + }, + "CustomField": [], + "DocNumber": "1023", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "46", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Pest Control Services", + "Amount": 70, + "LinkedTxn": [ + { + "TxnId": "46", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "10", + "name": "Pest Control" + }, + "UnitPrice": 35, + "Qty": 2, + "ItemAccountRef": { + "value": "54", + "name": "Pest Control Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 70, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "20", + "name": "Red Rock Diner" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "76", + "Line1": "Stephanie Martini", + "Line2": "Red Rock Diner", + "Line3": "500 Red Rock Rd.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "21", + "Line1": "500 Red Rock Rd.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 70, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 70 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "65", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-17T15:29:27-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-18T12:18:01-07:00" + }, + "CustomField": [], + "DocNumber": "1019", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "TAX" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "72", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 80 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "64", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T15:29:00-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T15:29:00-07:00" + }, + "CustomField": [], + "DocNumber": "1018", + "TxnDate": "2024-06-10", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 80, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 20, + "Qty": 4, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 80, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "25", + "name": "Sushi by Katsuyuki" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "71", + "Line1": "Katsuyuki Yanagawa", + "Line2": "Sushi by Katsuyuki", + "Line3": "898 Elm St.", + "Line4": "Maplewood, NJ 07040", + "Lat": "40.7312956", + "Long": "-74.2707509" + }, + "ShipAddr": { + "Id": "26", + "Line1": "898 Elm St.", + "City": "Maplewood", + "CountrySubDivisionCode": "NJ", + "PostalCode": "07040", + "Lat": "40.7312816", + "Long": "-74.2652908" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-10", + "TotalAmt": 80, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Sushi@intuit.com" + }, + "Balance": 80 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "60", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T15:18:18-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T15:18:18-07:00" + }, + "CustomField": [], + "DocNumber": "1016", + "TxnDate": "2024-05-01", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Weekly Gardening Service", + "Amount": 75, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "6", + "name": "Gardening" + }, + "UnitPrice": 25, + "Qty": 3, + "ItemAccountRef": { + "value": "45", + "name": "Landscaping Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 75, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "16", + "name": "Kookies by Kathy" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "69", + "Line1": "Kathy Kuplis", + "Line2": "Kookies by Kathy", + "Line3": "789 Sugar Lane", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "FreeFormAddress": false, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-05-31", + "TotalAmt": 75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "qbwebsamplecompany@yahoo.com" + }, + "Balance": 75, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "49", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:43:20-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:43:20-07:00" + }, + "CustomField": [], + "DocNumber": "1015", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 300, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 4, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Installation of landscape design", + "Amount": 250, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "7", + "name": "Installation" + }, + "UnitPrice": 50, + "Qty": 5, + "ItemAccountRef": { + "value": "52", + "name": "Landscaping Services:Labor:Installation" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "3", + "LineNum": 3, + "Description": "Rock Fountain", + "Amount": 275, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "4", + "LineNum": 4, + "Description": "Garden Rocks", + "Amount": 180, + "LinkedTxn": [ + { + "TxnId": "48", + "TxnType": "Estimate" + } + ], + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "13", + "name": "Rocks" + }, + "UnitPrice": 22.5, + "Qty": 8, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 1005, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + }, + { + "Amount": 50.25, + "DetailType": "DiscountLineDetail", + "DiscountLineDetail": { + "PercentBased": true, + "DiscountPercent": 5, + "DiscountAccountRef": { + "value": "86", + "name": "Discounts given" + } + } + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "18", + "name": "Paulsen Medical Supplies" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "68", + "Line1": "Kathy Paulsen", + "Line2": "Paulsen Medical Supplies", + "Line3": "900 Main St.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "19", + "Line1": "38921 S. Boise Ave", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.3989376", + "Long": "-122.1443935" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 954.75, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Medical@intuit.com" + }, + "Balance": 954.75 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "27", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-16T15:38:07-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:17:33-07:00" + }, + "CustomField": [], + "DocNumber": "1009", + "TxnDate": "2024-06-16", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "26", + "TxnType": "ReimburseCharge" + }, + { + "TxnId": "40", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Lumber", + "Amount": 103.55, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ServiceDate": "2024-06-16", + "ItemRef": { + "value": "1", + "name": "Services" + }, + "MarkupInfo": { + "PercentBased": false, + "Value": 103.55, + "MarkUpIncomeAccountRef": { + "value": "1", + "name": "Services" + } + }, + "ItemAccountRef": { + "value": "1", + "name": "Services" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 103.55, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "26", + "name": "Travis Waldron" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "55", + "Line1": "Travis Waldron", + "Line2": "78 First St.", + "Line3": "Monlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "27", + "Line1": "78 First St.", + "City": "Monlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4585825", + "Long": "-122.1352789" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-16", + "TotalAmt": 103.55, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "Travis@Waldron.com" + }, + "Balance": 0 + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "39", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:16:52-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:16:52-07:00" + }, + "CustomField": [], + "DocNumber": "1012", + "TxnDate": "2024-06-05", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Sprinkler Heads", + "Amount": 30, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "16", + "name": "Sprinkler Heads" + }, + "UnitPrice": 2, + "Qty": 15, + "ItemAccountRef": { + "value": "50", + "name": "Landscaping Services:Job Materials:Sprinklers and Drip Systems" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Id": "2", + "LineNum": 2, + "Description": "Rock Fountain", + "Amount": 275, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "5", + "name": "Rock Fountain" + }, + "UnitPrice": 275, + "Qty": 1, + "ItemAccountRef": { + "value": "48", + "name": "Landscaping Services:Job Materials:Fountains and Garden Lighting" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 305, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + }, + { + "Amount": 30.5, + "DetailType": "DiscountLineDetail", + "DiscountLineDetail": { + "PercentBased": true, + "DiscountPercent": 10, + "DiscountAccountRef": { + "value": "86", + "name": "Discounts given" + } + } + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "23", + "name": "Barnett Design" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "58", + "Line1": "Shara Barnett", + "Line2": "Barnett Design", + "Line3": "19 Main St.", + "Line4": "Middlefield, CA 94303", + "Lat": "37.4530553", + "Long": "-122.1178261" + }, + "ShipAddr": { + "Id": "24", + "Line1": "19 Main St.", + "City": "Middlefield", + "CountrySubDivisionCode": "CA", + "PostalCode": "94303", + "Lat": "37.445013", + "Long": "-122.1391443" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-05", + "TotalAmt": 274.5, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "Design@intuit.com" + }, + "Balance": 274.5, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "34", + "SyncToken": "0", + "MetaData": { + "CreateTime": "2024-06-17T11:09:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:09:08-07:00" + }, + "CustomField": [], + "DocNumber": "1010", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 375, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 5, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 375, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "29", + "name": "Weiskopf Consulting" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "56", + "Line1": "Nicola Weiskopf", + "Line2": "Weiskopf Consulting", + "Line3": "45612 Main St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 375, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "Consulting@intuit.com" + }, + "Balance": 375, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, + { + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "16", + "SyncToken": "1", + "MetaData": { + "CreateTime": "2024-06-16T15:10:40-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:06:49-07:00" + }, + "CustomField": [], + "DocNumber": "1007", + "TxnDate": "2024-05-25", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [ + { + "TxnId": "32", + "TxnType": "Payment" + } + ], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 750, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 10, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } + } + }, + { + "Amount": 750, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "13", + "name": "John Melton" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "53", + "Line1": "John Melton", + "Line2": "85 Pine St.", + "Line3": "Menlo Park, CA 94304", + "Lat": "37.3813444", + "Long": "-122.1802812" + }, + "ShipAddr": { + "Id": "13", + "Line1": "85 Pine St.", + "City": "Menlo Park", + "CountrySubDivisionCode": "CA", + "PostalCode": "94304", + "Lat": "37.4451342", + "Long": "-122.1409626" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-06-24", + "TotalAmt": 750, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NeedToPrint", + "EmailStatus": "NotSet", + "BillEmail": { + "Address": "John@Melton.com" + }, + "Balance": 450 + } + ], + "startPosition": 1, + "maxResults": 31, + "totalCount": 31 + }, + "time": "2024-07-16T07:08:44.246-07:00" } \ No newline at end of file diff --git a/tests/Mock/Response/Quickbooks/invoice.json b/tests/Mock/Response/Quickbooks/invoice.json index 9c72504550c3..ed7ebf84ddf4 100644 --- a/tests/Mock/Response/Quickbooks/invoice.json +++ b/tests/Mock/Response/Quickbooks/invoice.json @@ -1,152 +1,103 @@ { "Invoice": { - "TxnDate": "2014-09-19", - "domain": "QBO", - "PrintStatus": "NeedToPrint", - "SalesTermRef": { - "value": "3" - }, - "TotalAmt": 362.07, - "Line": [ - { - "Description": "Rock Fountain", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 1, - "UnitPrice": 275, - "ItemRef": { - "name": "Rock Fountain", - "value": "5" - } - }, - "LineNum": 1, - "Amount": 275.0, - "Id": "1" - }, - { - "Description": "Fountain Pump", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 1, - "UnitPrice": 12.75, - "ItemRef": { - "name": "Pump", - "value": "11" - } - }, - "LineNum": 2, - "Amount": 12.75, - "Id": "2" - }, - { - "Description": "Concrete for fountain installation", - "DetailType": "SalesItemLineDetail", - "SalesItemLineDetail": { - "TaxCodeRef": { - "value": "TAX" - }, - "Qty": 5, - "UnitPrice": 9.5, - "ItemRef": { - "name": "Concrete", - "value": "3" - } - }, - "LineNum": 3, - "Amount": 47.5, - "Id": "3" - }, - { - "DetailType": "SubTotalLineDetail", - "Amount": 335.25, - "SubTotalLineDetail": {} - } - ], - "DueDate": "2014-10-19", - "ApplyTaxAfterDiscount": false, - "DocNumber": "1037", - "sparse": false, - "CustomerMemo": { - "value": "Thank you for your business and have a great day!" - }, - "ProjectRef": { - "value": "39298045" - }, - "Deposit": 0, - "Balance": 362.07, - "CustomerRef": { - "name": "Sonnenschein Family Store", - "value": "24" - }, - "TxnTaxDetail": { - "TxnTaxCodeRef": { - "value": "2" - }, - "TotalTax": 26.82, - "TaxLine": [ - { - "DetailType": "TaxLineDetail", - "Amount": 26.82, - "TaxLineDetail": { - "NetAmountTaxable": 335.25, - "TaxPercent": 8, - "TaxRateRef": { - "value": "3" - }, - "PercentBased": true - } - } - ] - }, - "SyncToken": "0", - "LinkedTxn": [ - { - "TxnId": "100", - "TxnType": "Estimate" - } - ], - "BillEmail": { - "Address": "Familiystore@intuit.com" - }, - "ShipAddr": { - "City": "Middlefield", - "Line1": "5647 Cypress Hill Ave.", - "PostalCode": "94303", - "Lat": "37.4238562", - "Long": "-122.1141681", - "CountrySubDivisionCode": "CA", - "Id": "25" - }, - "EmailStatus": "NotSet", - "BillAddr": { - "Line4": "Middlefield, CA 94303", - "Line3": "5647 Cypress Hill Ave.", - "Line2": "Sonnenschein Family Store", - "Line1": "Russ Sonnenschein", - "Long": "-122.1141681", - "Lat": "37.4238562", - "Id": "95" - }, + "AllowIPNPayment": false, + "AllowOnlinePayment": false, + "AllowOnlineCreditCardPayment": false, + "AllowOnlineACHPayment": false, + "domain": "QBO", + "sparse": false, + "Id": "34", + "SyncToken": "0", "MetaData": { - "CreateTime": "2014-09-19T13:16:17-07:00", - "LastUpdatedTime": "2014-09-19T13:16:17-07:00" - }, - "CustomField": [ - { - "DefinitionId": "1", - "StringValue": "102", - "Type": "StringType", - "Name": "Crew #" + "CreateTime": "2024-06-17T11:09:08-07:00", + "LastModifiedByRef": { + "value": "9341452725837119" + }, + "LastUpdatedTime": "2024-06-17T11:09:08-07:00" + }, + "CustomField": [], + "DocNumber": "1010", + "TxnDate": "2024-06-17", + "CurrencyRef": { + "value": "USD", + "name": "United States Dollar" + }, + "LinkedTxn": [], + "Line": [ + { + "Id": "1", + "LineNum": 1, + "Description": "Custom Design", + "Amount": 375, + "DetailType": "SalesItemLineDetail", + "SalesItemLineDetail": { + "ItemRef": { + "value": "4", + "name": "Design" + }, + "UnitPrice": 75, + "Qty": 5, + "ItemAccountRef": { + "value": "82", + "name": "Design income" + }, + "TaxCodeRef": { + "value": "NON" + } } - ], - "Id": "130" - }, + }, + { + "Amount": 375, + "DetailType": "SubTotalLineDetail", + "SubTotalLineDetail": {} + } + ], + "TxnTaxDetail": { + "TotalTax": 0 + }, + "CustomerRef": { + "value": "29", + "name": "Weiskopf Consulting" + }, + "CustomerMemo": { + "value": "Thank you for your business and have a great day!" + }, + "BillAddr": { + "Id": "56", + "Line1": "Nicola Weiskopf", + "Line2": "Weiskopf Consulting", + "Line3": "45612 Main St.", + "Line4": "Bayshore, CA 94326", + "Lat": "INVALID", + "Long": "INVALID" + }, + "ShipAddr": { + "Id": "30", + "Line1": "45612 Main St.", + "City": "Bayshore", + "CountrySubDivisionCode": "CA", + "PostalCode": "94326", + "Lat": "45.256574", + "Long": "-66.0943698" + }, + "FreeFormAddress": true, + "SalesTermRef": { + "value": "3", + "name": "Net 30" + }, + "DueDate": "2024-07-17", + "TotalAmt": 375, + "ApplyTaxAfterDiscount": false, + "PrintStatus": "NotSet", + "EmailStatus": "NeedToSend", + "BillEmail": { + "Address": "Consulting@intuit.com" + }, + "Balance": 375, + "DeliveryInfo": { + "DeliveryType": "Email" + } + }, "time": "2015-07-24T10:48:27.082-07:00" } \ No newline at end of file From 596ed1a6626bab8afbc953c189bf97365635a719 Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 16 Jul 2024 13:03:37 -0400 Subject: [PATCH 17/69] update test. test for client --- .../Quickbooks/InvoiceTransformerTest.php | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php index 05dc0c7c5ba1..40d7f287c6f4 100644 --- a/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php +++ b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php @@ -3,10 +3,16 @@ namespace Tests\Unit\Import\Transformer\Quickbooks; use Tests\TestCase; +use Tests\MockAccountData; +use Illuminate\Support\Facades\Auth; +use Illuminate\Foundation\Testing\DatabaseTransactions; use App\Import\Transformer\Quickbooks\InvoiceTransformer; class InvoiceTransformerTest extends TestCase { + use MockAccountData; + use DatabaseTransactions; + private $invoiceData; private $tranformedData; private $transformer; @@ -14,12 +20,13 @@ class InvoiceTransformerTest extends TestCase protected function setUp(): void { parent::setUp(); - // Mock the company object - $company = (new \App\Factory\CompanyFactory)->create(1234); + $this->makeTestData(); + $this->withoutExceptionHandling(); + Auth::setUser($this->user); // Read the JSON string from a file and decode into an associative array $this->invoiceData = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/invoice.json') ), true); - $this->transformer = new InvoiceTransformer($company); + $this->transformer = new InvoiceTransformer($this->company); $this->transformedData = $this->transformer->transform($this->invoiceData['Invoice']); } @@ -51,4 +58,18 @@ class InvoiceTransformerTest extends TestCase $this->assertIsFloat($this->transformedData['amount']); $this->assertEquals($this->invoiceData['Invoice']['TotalAmt'], $this->transformedData['amount']); } + + public function testTransformContainsLineItems() + { + $this->assertArrayHasKey('line_items', $this->transformedData); + $this->assertNotNull($this->transformedData['line_items']); + $this->assertEquals( count($this->invoiceData['Invoice']["Line"]) - 1, count($this->transformedData['line_items']) ); + } + + public function testTransformHasClient() + { + $this->assertArrayHasKey('client', $this->transformedData); + $this->assertArrayHasKey('contacts', $this->transformedData['client']); + $this->assertEquals($this->invoiceData['Invoice']['BillEmail']['Address'], $this->transformedData['client']['contacts'][0]['email']); + } } From b1d2f85af3ee0fec705ad6974ce0b8cde4e25789 Mon Sep 17 00:00:00 2001 From: karneaud Date: Wed, 17 Jul 2024 12:33:51 -0400 Subject: [PATCH 18/69] add import invoices feature --- app/Import/Providers/Quickbooks.php | 97 ++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 1 deletion(-) diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php index 60af270f7547..dd1fd4477680 100644 --- a/app/Import/Providers/Quickbooks.php +++ b/app/Import/Providers/Quickbooks.php @@ -117,9 +117,104 @@ class Quickbooks extends BaseImport $this->repository = app()->make($this->repository_name); $this->repository->import_mode = true; $this->transformer = new InvoiceTransformer($this->company); - $invoice_count = $this->ingestInvoices($data, 'Invoice #'); + $invoice_count = $this->ingestInvoices($data,''); $this->entity_count['invoices'] = $invoice_count; $this->company->update_products = $initial_update_products_value; $this->company->save(); } + + public function ingestInvoices($invoices, $invoice_number_key) + { + $count = 0; + $invoice_transformer = $this->transformer; + /** @var ClientRepository $client_repository */ + $client_repository = app()->make(ClientRepository::class); + $client_repository->import_mode = true; + $invoice_repository = new InvoiceRepository(); + $invoice_repository->import_mode = true; + + foreach ($invoices as $raw_invoice) { + if(!is_array($raw_invoice)) { + continue; + } + + try { + $invoice_data = $invoice_transformer->transform($raw_invoice); + $invoice_data['user_id'] = $this->company->owner()->id; + $invoice_data['line_items'] = (array) $invoice_data['line_items']; + + if ( + empty($invoice_data['client_id']) && + ! empty($invoice_data['client']) + ) { + $client_data = $invoice_data['client']; + $client_data['user_id'] = $this->getUserIDForRecord( + $invoice_data + ); + $client_repository->save( + $client_data, + $client = ClientFactory::create( + $this->company->id, + $client_data['user_id'] + ) + ); + $invoice_data['client_id'] = $client->id; + unset($invoice_data['client']); + } + + $validator = $this->request_name::runFormRequest($invoice_data); + if ($validator->fails()) { + $this->error_array['invoice'][] = [ + 'invoice' => $invoice_data, + 'error' => $validator->errors()->all(), + ]; + } else { + $invoice = InvoiceFactory::create( + $this->company->id, + $this->company->owner()->id + ); + $invoice->mergeFillable(['partial','amount','balance','line_items']); + if (! empty($invoice_data['status_id'])) { + $invoice->status_id = $invoice_data['status_id']; + } + + $saveable_invoice_data = $invoice_data; + if(array_key_exists('payments', $saveable_invoice_data)) { + unset($saveable_invoice_data['payments']); + } + + $invoice->fill($saveable_invoice_data); + $invoice->save(); + $count++; + + // $this->actionInvoiceStatus( + // $invoice, + // $invoice_data, + // $invoice_repository + // ); + } + } catch (\Exception $ex) { + if (\DB::connection(config('database.default'))->transactionLevel() > 0) { + \DB::connection(config('database.default'))->rollBack(); + } + + if ($ex instanceof ImportException) { + $message = $ex->getMessage(); + } else { + report($ex); + $message = 'Unknown error '; + nlog($ex->getMessage()); + nlog($raw_invoice); + } + + $this->error_array['invoice'][] = [ + 'invoice' => $raw_invoice, + 'error' => $message, + ]; + } + } + + return $count; + } + } From 7520b6318dbbbe480ae9b7237202f398cf39a584 Mon Sep 17 00:00:00 2001 From: karneaud Date: Wed, 17 Jul 2024 12:34:09 -0400 Subject: [PATCH 19/69] refactor to include client object/ id --- app/Import/Transformer/Quickbooks/InvoiceTransformer.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php index 37edb38e151e..9667c87d312d 100644 --- a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -119,10 +119,13 @@ class InvoiceTransformer extends BaseTransformer $customer = explode(" ", $this->getString($data, 'CustomerRef.name')); $customer = ['GivenName' => $customer[0], 'FamilyName' => $customer[1]]; $has_company = property_exists($bill_address, 'Line4'); + $address = $has_company? $bill_address->Line4 : $bill_address->Line3; + $address_1 = substr($address, 0, stripos($address,',')); + $address =array_filter( [$address_1] + (explode(' ', substr($address, stripos($address,",") + 1 )))); $client = [ "CompanyName" => $has_company? $bill_address->Line2 : $bill_address->Line1, - "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_filter(explode(" ", $has_company? $bill_address->Line4 : $bill_address->Line3 ))) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ], + "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], $address) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ], "ShipAddr" => $ship_address ] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]]; $client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address')); From 473f9ea0a0e60b28e019824abd8e6351eeea2ae6 Mon Sep 17 00:00:00 2001 From: karneaud Date: Wed, 17 Jul 2024 12:34:26 -0400 Subject: [PATCH 20/69] update test case for import invoice feature --- .../Import/Quickbooks/QuickbooksTest.php | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/Feature/Import/Quickbooks/QuickbooksTest.php b/tests/Feature/Import/Quickbooks/QuickbooksTest.php index 29a894baa774..ebdd3af37da3 100644 --- a/tests/Feature/Import/Quickbooks/QuickbooksTest.php +++ b/tests/Feature/Import/Quickbooks/QuickbooksTest.php @@ -13,6 +13,7 @@ use Illuminate\Support\Facades\Cache; use Mockery; use App\Models\Client; use App\Models\Product; +use App\Models\Invoice; use Illuminate\Support\Str; use ReflectionClass; use Illuminate\Support\Facades\Auth; @@ -34,6 +35,7 @@ class QuickbooksTest extends TestCase $this->withoutMiddleware(ThrottleRequests::class); config(['database.default' => config('ninja.db.default')]); $this->makeTestData(); + // $this->withoutExceptionHandling(); Auth::setUser($this->user); @@ -102,6 +104,33 @@ class QuickbooksTest extends TestCase $this->assertLessThanOrEqual(0, $product->quantity); } + public function testImportCallsGetDataOnceForInvoices() + { + $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/invoices.json') ), true))['Invoice']; + $hash = Str::random(32); + Cache::put($hash.'-invoice', base64_encode(json_encode($data)), 360); + $quickbooks = Mockery::mock(Quickbooks::class,[[ + 'hash' => $hash, + 'column_map' => ['invoice' => ['mapping' => []]], + 'skip_header' => true, + 'import_type' => 'quickbooks', + ], $this->company ])->makePartial(); + $quickbooks->shouldReceive('getData') + ->once() + ->with('invoice') + ->andReturn($data); + $quickbooks->import('invoice'); + $this->assertArrayHasKey('invoices', $quickbooks->entity_count); + $this->assertGreaterThan(0, $quickbooks->entity_count['invoices']); + $base_transformer = new BaseTransformer($this->company); + $this->assertTrue($base_transformer->hasInvoice(1007)); + $invoice = Invoice::where('number',1012)->first(); + $data = collect($data)->where('DocNumber','1012')->first(); + $this->assertGreaterThanOrEqual( $data['TotalAmt'], $invoice->amount); + $this->assertEquals( count($data['Line']) - 1 , count((array)$invoice->line_items)); + } + + protected function tearDown(): void { Mockery::close(); From b43aa77ff8244ac5d9edf3f1cf3136afd83678e0 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 19 Jul 2024 13:04:40 -0400 Subject: [PATCH 21/69] fixed class not found --- app/Import/Providers/Quickbooks.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php index dd1fd4477680..ac938e6f5436 100644 --- a/app/Import/Providers/Quickbooks.php +++ b/app/Import/Providers/Quickbooks.php @@ -14,6 +14,7 @@ namespace App\Import\Providers; use App\Factory\ProductFactory; use App\Factory\ClientFactory; use App\Factory\InvoiceFactory; +use Illuminate\Support\Facades\Cache; use App\Http\Requests\Client\StoreClientRequest; use App\Http\Requests\Product\StoreProductRequest; use App\Http\Requests\Invoice\StoreInvoiceRequest; @@ -91,7 +92,12 @@ class Quickbooks extends BaseImport public function getData($type) { // get the data from cache? file? or api ? - return []; + return json_decode(base64_decode(Cache::get("{$this->hash}-$type")), 1); + } + + public function payment() + { + } public function invoice() From 207c3ec80a7023598032caca59db68d152574eaa Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 19 Jul 2024 13:04:54 -0400 Subject: [PATCH 22/69] add inget job for quickbooks --- app/Jobs/Import/QuickbooksIngest.php | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 app/Jobs/Import/QuickbooksIngest.php diff --git a/app/Jobs/Import/QuickbooksIngest.php b/app/Jobs/Import/QuickbooksIngest.php new file mode 100644 index 000000000000..18889b0d8908 --- /dev/null +++ b/app/Jobs/Import/QuickbooksIngest.php @@ -0,0 +1,42 @@ +company = $company; + $this->request = $request; + } + + /** + * Execute the job. + */ + public function handle(): void + { + MultiDB::setDb($this->company->db); + set_time_limit(0); + $engine = new Quickbooks($this->request, $this->company); + foreach (['client', 'product', 'invoice', 'payment'] as $entity) { + $engine->import($entity); + } + + $engine->finalizeImport(); + } +} From 8e5c6509c27cf935240fcac4fd85d994b0eaf7f7 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 19 Jul 2024 13:05:19 -0400 Subject: [PATCH 23/69] add test quickbooks ingest job --- .../Jobs/Import/QuickbooksIngestTest.php | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/Feature/Jobs/Import/QuickbooksIngestTest.php diff --git a/tests/Feature/Jobs/Import/QuickbooksIngestTest.php b/tests/Feature/Jobs/Import/QuickbooksIngestTest.php new file mode 100644 index 000000000000..a2bbdd4974c0 --- /dev/null +++ b/tests/Feature/Jobs/Import/QuickbooksIngestTest.php @@ -0,0 +1,53 @@ + config('ninja.db.default')]); + $this->makeTestData(); + $this->withoutExceptionHandling(); + Auth::setUser($this->user); + + } + + /** + * A basic feature test example. + */ + public function testCanQuickbooksIngest(): void + { + $data = (json_decode( file_get_contents( base_path('tests/Feature/Import/customers.json') ), true))['Customer']; + $hash = Str::random(32); + Cache::put($hash.'-client', base64_encode(json_encode($data)), 360); + QuickbooksIngest::dispatch([ + 'hash' => $hash, + 'column_map' => ['client' => ['mapping' => []]], + 'skip_header' => true, + 'import_type' => 'quickbooks', + ], $this->company )->handle(); + $this->assertTrue(Client::withTrashed()->where(['company_id' => $this->company->id, 'name' => "Freeman Sporting Goods"])->exists()); + } +} From 630c48193d180dc0768ebdd5a0913719b73e2a7a Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 22 Jul 2024 20:02:13 -0400 Subject: [PATCH 24/69] add quicbooks controller/routes --- .../ImportQuickbooksController.php | 92 +++++++++++++++++++ routes/api.php | 5 +- routes/web.php | 2 + 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 app/Http/Controllers/ImportQuickbooksController.php diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php new file mode 100644 index 000000000000..cf17cc7ff8ab --- /dev/null +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -0,0 +1,92 @@ +user(); + + return $user->isAdmin() ; + } + + public function preimport(Request $request) + { + // Check for authorization otherwise + + // Create a reference + $hash = Str::random(32); + $data = [ + 'hash' => $hash, + 'type' => $request->input('import_type') + ]; + $contents = $this->getData(); + Cache::put("$hash-{$data['type']}", base64_encode(json_encode($contents)), 600); + + return response()->json(['message' => 'Data cached for import'] + $data, 200); + } + + public function authorizeQuickbooks() { + + } + + protected function getData() { + + } + + /** + * @OA\Post( + * path="/api/v1/import_json", + * operationId="getImportJson", + * tags={"import"}, + * summary="Import data from the system", + * description="Import data from the system", + * @OA\Parameter(ref="#/components/parameters/X-Requested-With"), + * @OA\Response( + * response=200, + * description="success", + * @OA\Header(header="X-MINIMUM-CLIENT-VERSION", ref="#/components/headers/X-MINIMUM-CLIENT-VERSION"), + * @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"), + * @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"), + * ), + * @OA\Response( + * response=422, + * description="Validation error", + * @OA\JsonContent(ref="#/components/schemas/ValidationError"), + * ), + * @OA\Response( + * response="default", + * description="Unexpected Error", + * @OA\JsonContent(ref="#/components/schemas/Error"), + * ), + * ) + */ + public function import(Request $request) + { + /** @var \App\Models\User $user */ + $user = auth()->user(); + if (Ninja::isHosted()) { + QuickbooksIngest::dispatch($request->all(), $user->company() ); + } else { + QuickbooksIngest::dispatch($request->all(), $user->company() ); + } + + return response()->json(['message' => 'Processing'], 200); + } +} diff --git a/routes/api.php b/routes/api.php index 0b0ffa772b15..f496e14c428d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -57,6 +57,7 @@ use App\Http\Controllers\SystemLogController; use App\Http\Controllers\TwoFactorController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\ImportJsonController; +use App\Http\Controllers\ImportQuickbooksController; use App\Http\Controllers\SelfUpdateController; use App\Http\Controllers\TaskStatusController; use App\Http\Controllers\Bank\YodleeController; @@ -243,7 +244,9 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('import', [ImportController::class, 'import'])->name('import.import'); Route::post('import_json', [ImportJsonController::class, 'import'])->name('import.import_json'); Route::post('preimport', [ImportController::class, 'preimport'])->name('import.preimport'); - + ; + Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); + Route::post('import/quickbooks/preimport', [ImportQuickbooksController::class, 'preimport'])->name('import.quickbooks.preimport'); Route::resource('invoices', InvoiceController::class); // name = (invoices. index / create / show / update / destroy / edit Route::get('invoices/{invoice}/delivery_note', [InvoiceController::class, 'deliveryNote'])->name('invoices.delivery_note'); Route::get('invoices/{invoice}/{action}', [InvoiceController::class, 'action'])->name('invoices.action'); diff --git a/routes/web.php b/routes/web.php index 1d56f5d88390..6487eaa50975 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,7 @@ use App\Http\Controllers\Auth\ResetPasswordController; use App\Http\Controllers\Bank\NordigenController; use App\Http\Controllers\Bank\YodleeController; use App\Http\Controllers\BaseController; +use App\Http\Controllers\ImportQuickbooksController; use App\Http\Controllers\ClientPortal\ApplePayDomainController; use App\Http\Controllers\Gateways\Checkout3dsController; use App\Http\Controllers\Gateways\GoCardlessController; @@ -37,6 +38,7 @@ Route::middleware('url_db')->group(function () { Route::post('/user/confirm/{confirmation_code}', [UserController::class, 'confirmWithPassword']); }); +Route::post('import/quickbooks/authorize', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('import.auth.quickbooks'); Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize'])->name('stripe_connect.initialization'); Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); From 245fc2ad777c172eebbc33eeb366c644fd904831 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 22 Jul 2024 20:03:27 -0400 Subject: [PATCH 25/69] restructure mocks --- .../Data}/customer.json | 0 .../Data}/invoice.json | 0 .../Quickbooks => Quickbooks/Data}/item.json | 0 .../Http/Response/200-api-tokens-response.txt | 21 +++++++++++++++++++ .../Http/Response}/200-cutomer-response.txt | 0 .../Http/Response}/200-invoice-response.txt | 0 .../Response/200-refresh-token-response.txt | 21 +++++++++++++++++++ 7 files changed, 42 insertions(+) rename tests/Mock/{Response/Quickbooks => Quickbooks/Data}/customer.json (100%) rename tests/Mock/{Response/Quickbooks => Quickbooks/Data}/invoice.json (100%) rename tests/Mock/{Response/Quickbooks => Quickbooks/Data}/item.json (100%) create mode 100644 tests/Mock/Quickbooks/Http/Response/200-api-tokens-response.txt rename tests/Mock/{Response/Http => Quickbooks/Http/Response}/200-cutomer-response.txt (100%) rename tests/Mock/{Response/Http => Quickbooks/Http/Response}/200-invoice-response.txt (100%) create mode 100644 tests/Mock/Quickbooks/Http/Response/200-refresh-token-response.txt diff --git a/tests/Mock/Response/Quickbooks/customer.json b/tests/Mock/Quickbooks/Data/customer.json similarity index 100% rename from tests/Mock/Response/Quickbooks/customer.json rename to tests/Mock/Quickbooks/Data/customer.json diff --git a/tests/Mock/Response/Quickbooks/invoice.json b/tests/Mock/Quickbooks/Data/invoice.json similarity index 100% rename from tests/Mock/Response/Quickbooks/invoice.json rename to tests/Mock/Quickbooks/Data/invoice.json diff --git a/tests/Mock/Response/Quickbooks/item.json b/tests/Mock/Quickbooks/Data/item.json similarity index 100% rename from tests/Mock/Response/Quickbooks/item.json rename to tests/Mock/Quickbooks/Data/item.json diff --git a/tests/Mock/Quickbooks/Http/Response/200-api-tokens-response.txt b/tests/Mock/Quickbooks/Http/Response/200-api-tokens-response.txt new file mode 100644 index 000000000000..f73a6eda5578 --- /dev/null +++ b/tests/Mock/Quickbooks/Http/Response/200-api-tokens-response.txt @@ -0,0 +1,21 @@ +Status: 200 +Connection: keep-alive +Keep-Alive: timeout=5 +Strict-Transport-Security: max-age=15552000 +Cache-Control: no-cache, no-store +Content-Type: application/json;charset=utf-8 +Server: nginx +{ + "refreshToken": "AB11730384727MVrceUmO0gapXJFBT0IkpkI71FCkkWrRAVn8P", + "accessToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..vW8l3PbGZGjF5Nbq-ssyKw +     .mSxu2T4j4OYXa8Gr7z2OU5cqVg19MasGFllKMvT2Jo0wYrovyN6q9OZIwul1iSKI10A3ZIBT5VnsTnW6ZQG +     -rKcvmPNImZhYyCeBwzAUcnFQDPax5sWqlSHwEEviLPSH6R6Rs0wSNtszD4sEva8Z2pHmMZ4q2VNX3SAJA2R8spLhkItcULW4COdUNhZHl9fk4m7Xo66q8iBmE +     IV5269DXI-x8tCe7BdBrQbIabd5tqmD4gllS4vInK__86LLvESHDuY51eQEJt8eLRhSFIK7ZT2zoS6JKp1TfuvEg7HULkP53u12hSZsTETCfFneofOXhqEAQa8 +     SgqiYV-8_I4Mm7P_TDeRX06CdPMMwH9Wrvmmmihhk8TNz6MynEn4Cpjf_iAedtyhXnELTnefTUC8_ +     --w9_5FCCHyHgS45A2289mhIY1eLH_i8gjzuGKs7zYOaTvm1nQ_Jt1Z1Guy-jteNwt6_2OoMYfQaMss8AwHAZr7c +     -bhvtBY0Qi0VoqfMUdicuxMXd3HZCXUuKqXpGn5TXwJ6T6flf8Slgh2GIKUhMg1IoXMFKD3IVaPU81qyaGbuBIhwVdLywgY0guAlnYYUAHX4n_pmDf6zGxGQ0V +     RXMF2iQY1Q21OKGUqgkS8xaQajV5KERVZkp4thEurLg4EAxDdT71VxOjU5IszIRPfg8bw_X5g73pTNyF +     -3TRCJHe_FXz7P_Ee3Py_9O1Bw7MtbKxiwlSmumOOBKHAtorQhakw.nYNzTumdNDhpqLwCIkJNlw", + "expires_in": 3600, + "x_refresh_token_expires_in": 8726400 +} \ No newline at end of file diff --git a/tests/Mock/Response/Http/200-cutomer-response.txt b/tests/Mock/Quickbooks/Http/Response/200-cutomer-response.txt similarity index 100% rename from tests/Mock/Response/Http/200-cutomer-response.txt rename to tests/Mock/Quickbooks/Http/Response/200-cutomer-response.txt diff --git a/tests/Mock/Response/Http/200-invoice-response.txt b/tests/Mock/Quickbooks/Http/Response/200-invoice-response.txt similarity index 100% rename from tests/Mock/Response/Http/200-invoice-response.txt rename to tests/Mock/Quickbooks/Http/Response/200-invoice-response.txt diff --git a/tests/Mock/Quickbooks/Http/Response/200-refresh-token-response.txt b/tests/Mock/Quickbooks/Http/Response/200-refresh-token-response.txt new file mode 100644 index 000000000000..9646d0941cd9 --- /dev/null +++ b/tests/Mock/Quickbooks/Http/Response/200-refresh-token-response.txt @@ -0,0 +1,21 @@ +Status: 200 +Connection: keep-alive +Keep-Alive: timeout=5 +Strict-Transport-Security: max-age=15552000 +Cache-Control: no-cache, no-store +Content-Type: application/json;charset=utf-8 +Server: nginx +{ + "refreshToken": "AB11730389456hovpbWZFrn4st1d0qkEdnL9g3N0TNWERGeJvg", + "accessToken": "eyJlbmMiOiJBMTI4Q0JDLUhTMjU2IiwiYWxnIjoiZGlyIn0..JKD1fuk8W29Agq8x-CF3FQ +     .ioAPe43etRVWLT3GUYdF9yS_mqYqDsIkOE3Y69g7i5tj1N0mHkdxoTpfAoP59Q5PtAzndTWHjFEIF_z2mMkyuTUAJqTvrMjyRM +     -AymqFpPTly7UUs_lWZCCta5RRT8uEifeUT5VcJNT3_1nbNL85mHpXbkW-Ficovp0tR4E52BPjdrSfFbAkVQQoZnHe13rJWLgj9L +     -R63UzwlktE3lF7sgaGg5Gdsu41b8ATa0Jp2LZ4lT3QnPVcbhx8awWlwJ8qW-hElkmeBKPLju2xR1JIhw7bpDtV1BAOBkEjs7TL5oKKiUxV-slZQBhmf8cTJT +     -p6RqtOCyV-mlAL7kixc7CGWtuqwnb6j_HckdIwV8CX7Wivvt-Gnl80v0qjUQsacwighkm0KAYMoM__eTi8DM-xdvskzoLfPzblImxwKte +     -31tBg_o9v9oE6ZHr0D-yllGv58Cu5vdsFajEEo7-MTFnxpxihd8_OmXSdegGF3lLA2lMk7qlkrq6 +     -Uw46rOfv4LkBo2C0OIYN1c5Wnt6zf7461BM5NerMEzeHrgjFDiTBamdWeXQfBOZAX1KJ1IoM_RJrpCParJqTx1Ia0bDn_fcT5ysTEUO9c811GWYpMhyVCq +     -qIzz4JrdIx7F7xVc4dLffVL2PHibC-cXtWcx85j1KzPOr0YEAFGoawyCH6uvMuubJlisc_BSDa_SH_gDyTtMhzeOW +     -zQi0HXveOjtQPbIa91K9ddhcilB1LSDO4Qg.fxg-wLi7mDiHgi6YTgm36Q", + "expires_in": 3600, + "x_refresh_token_expires_in": 8726345 +} \ No newline at end of file From 7200591b1ea496418c220486113f80e4329f69de Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 22 Jul 2024 20:03:46 -0400 Subject: [PATCH 26/69] update mock refs --- .../Import/Transformer/Quickbooks/ClientTransformerTest.php | 2 +- .../Import/Transformer/Quickbooks/InvoiceTransformerTest.php | 2 +- .../Import/Transformer/Quickbooks/ProductTransformerTest.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php index f9fdcbabca7e..57f7b05f5449 100644 --- a/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php +++ b/tests/Unit/Import/Transformer/Quickbooks/ClientTransformerTest.php @@ -18,7 +18,7 @@ class ClientTransformerTest extends TestCase $company = (new \App\Factory\CompanyFactory)->create(1234); // Read the JSON string from a file and decode into an associative array - $this->customer_data = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/customer.json') ), true); + $this->customer_data = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/customer.json') ), true); $this->transformer = new ClientTransformer($company); $this->transformed_data = $this->transformer->transform($this->customer_data['Customer']); } diff --git a/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php index 40d7f287c6f4..eeeafb18e07c 100644 --- a/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php +++ b/tests/Unit/Import/Transformer/Quickbooks/InvoiceTransformerTest.php @@ -25,7 +25,7 @@ class InvoiceTransformerTest extends TestCase $this->withoutExceptionHandling(); Auth::setUser($this->user); // Read the JSON string from a file and decode into an associative array - $this->invoiceData = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/invoice.json') ), true); + $this->invoiceData = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/invoice.json') ), true); $this->transformer = new InvoiceTransformer($this->company); $this->transformedData = $this->transformer->transform($this->invoiceData['Invoice']); } diff --git a/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php b/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php index 04dbd6ff7312..194f0f8ecf2d 100644 --- a/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php +++ b/tests/Unit/Import/Transformer/Quickbooks/ProductTransformerTest.php @@ -18,7 +18,7 @@ class ProductTransformerTest extends TestCase $company = (new \App\Factory\CompanyFactory)->create(1234); // Read the JSON string from a file and decode into an associative array - $this->product_data = json_decode( file_get_contents( app_path('/../tests/Mock/Response/Quickbooks/item.json') ), true); + $this->product_data = json_decode( file_get_contents( app_path('/../tests/Mock/Quickbooks/Data/item.json') ), true); $this->transformer = new ProductTransformer($company); $this->transformed_data = $this->transformer->transform($this->product_data['Item']); } From 1d1a01fcd1b302b63dfc8d05484ffca1d29490c5 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 22 Jul 2024 20:04:02 -0400 Subject: [PATCH 27/69] add quickbooks imports controller test --- .../ImportQuickbooksControllerTest.php | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php diff --git a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php new file mode 100644 index 000000000000..8534e33ed681 --- /dev/null +++ b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php @@ -0,0 +1,99 @@ +makeTestData(); + Session::start(); + } + + public function testPreImportQuickbooksController(): void + { + Cache::spy(); + + $data = ($this->setUpTestResponseData('200-cutomer-response.txt'))['QueryResponse']['Customer']; + // Create a mock of the UserController + $controllerMock = Mockery::mock('App\Http\Controllers\ImportQuickbooksController[getData]')->shouldAllowMockingProtectedMethods(); + // Define what the mocked getData method should return + $controllerMock->shouldReceive('getData') + ->once() + ->andReturn( $data, true); + // Bind the mock to the Laravel container + $this->app->instance('App\Http\Controllers\ImportQuickbooksController', $controllerMock); + // Perform the test + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/import/quickbooks/preimport',[ + 'import_type' => 'client' + ]); + $response->assertStatus(200); + $response = json_decode( $response->getContent()); + $this->assertNotNull($response->hash); + + Cache::shouldHaveReceived('put')->once()->with("{$response->hash}-client", base64_encode(json_encode($data)),600); + } + + public function testImportQuickbooksCustomers(): void + { + + Bus::fake(); + + $data = ($this->setUpTestResponseData('200-cutomer-response.txt'))['QueryResponse']['Customer']; + // Create a mock of the UserController + $controllerMock = Mockery::mock('App\Http\Controllers\ImportQuickbooksController[getData]')->shouldAllowMockingProtectedMethods(); + // Define what the mocked getData method should return + $controllerMock->shouldReceive('getData') + ->once() + ->andReturn( $data, true); + // Bind the mock to the Laravel container + $this->app->instance('App\Http\Controllers\ImportQuickbooksController', $controllerMock); + // Perform the test + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/import/quickbooks/preimport',[ + 'import_type' => 'client' + ]); + $response->assertStatus(200); + $response = json_decode( $response->getContent()); + $this->assertNotNull($response->hash); + $response = $this->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/import/quickbooks',[ + 'import_type' => 'client', + 'hash' => $response->hash + ]); + $response->assertStatus(200); + + Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); + } + + protected function setUpTestResponseData($file) { + $fullResponse = file_get_contents( base_path("tests/Mock/Quickbooks/Http/Response/$file") ); + // Parse the full response using Guzzle + $response = Message::parseResponse($fullResponse); + // Extract the JSON body + $jsonBody = (string) $response->getBody(); + // Decode the JSON body to an array + return json_decode($jsonBody, true); + } +} From 6e587f96cf8d79c65b0a551aea598c7a6366eb0f Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:15:43 -0400 Subject: [PATCH 28/69] add intuit sdk depdency --- composer.json | 7 +++--- composer.lock | 65 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index e3b795c88121..f101e614e3cb 100644 --- a/composer.json +++ b/composer.json @@ -32,10 +32,10 @@ "type": "project", "require": { "php": "^8.2", + "ext-curl": "*", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", - "ext-curl": "*", "afosto/yaac": "^1.4", "asm/php-ansible": "dev-main", "authorizenet/authorizenet": "^2.0", @@ -57,12 +57,12 @@ "hedii/laravel-gelf-logger": "^9", "horstoeko/orderx": "dev-master", "horstoeko/zugferd": "^1", - "horstoeko/zugferdvisualizer":"^1", + "horstoeko/zugferdvisualizer": "^1", "hyvor/php-json-exporter": "^0.0.3", "imdhemy/laravel-purchases": "^1.7", "intervention/image": "^2.5", - "invoiceninja/inspector": "^3.0", "invoiceninja/einvoice": "dev-main", + "invoiceninja/inspector": "^3.0", "invoiceninja/ubl_invoice": "^2", "josemmo/facturae-php": "^1.7", "laracasts/presenter": "^0.2.1", @@ -87,6 +87,7 @@ "predis/predis": "^2", "psr/http-message": "^1.0", "pusher/pusher-php-server": "^7.2", + "quickbooks/v3-php-sdk": "^6.1", "razorpay/razorpay": "2.*", "sentry/sentry-laravel": "^4", "setasign/fpdf": "^1.8", diff --git a/composer.lock b/composer.lock index 541f0c04527b..9da1cb356009 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "edc6905124cb32fef6a13befb3d5a4e1", + "content-hash": "f137c4c97faf7721366179f36c1ca72a", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -9896,6 +9896,65 @@ }, "time": "2023-12-15T10:58:53+00:00" }, + { + "name": "quickbooks/v3-php-sdk", + "version": "v6.1.3", + "source": { + "type": "git", + "url": "https://github.com/intuit/QuickBooks-V3-PHP-SDK.git", + "reference": "2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/intuit/QuickBooks-V3-PHP-SDK/zipball/2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2", + "reference": "2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=5.6.0" + }, + "require-dev": { + "php-mock/php-mock": "^2.3", + "php-mock/php-mock-phpunit": "^2.6", + "phpunit/phpunit": "^5.0 || ^6.0 || ^7.0 || ^8" + }, + "suggest": { + "ext-curl": "Uses Curl to make HTTP Requests", + "guzzlehttp/guzzle": "Uses Guzzle to make HTTP Requests" + }, + "type": "library", + "autoload": { + "psr-4": { + "QuickBooksOnline\\API\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "abisalehalliprasan", + "email": "anil_kumar3@intuit.com" + } + ], + "description": "The Official PHP SDK for QuickBooks Online Accounting API", + "homepage": "http://developer.intuit.com", + "keywords": [ + "api", + "http", + "quickbooks", + "rest", + "smallbusiness" + ], + "support": { + "issues": "https://github.com/intuit/QuickBooks-V3-PHP-SDK/issues", + "source": "https://github.com/intuit/QuickBooks-V3-PHP-SDK/tree/v6.1.3" + }, + "time": "2024-05-28T11:13:18+00:00" + }, { "name": "ralouphie/getallheaders", "version": "3.0.3", @@ -19590,10 +19649,10 @@ "prefer-lowest": false, "platform": { "php": "^8.2", + "ext-curl": "*", "ext-dom": "*", "ext-json": "*", - "ext-libxml": "*", - "ext-curl": "*" + "ext-libxml": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" From ee334fd974dfc9e5f71aa37c6eff5e5447381963 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:16:10 -0400 Subject: [PATCH 29/69] add quickbooks service provider --- app/Providers/QuickbooksServiceProvider.php | 72 +++++++++++++++++++++ config/app.php | 5 +- 2 files changed, 75 insertions(+), 2 deletions(-) create mode 100644 app/Providers/QuickbooksServiceProvider.php diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php new file mode 100644 index 000000000000..bf517aae3a6a --- /dev/null +++ b/app/Providers/QuickbooksServiceProvider.php @@ -0,0 +1,72 @@ +app->singleton(QuickbooksInterface::class, function ($app) { + // TODO: Possibly load tokens from Cache or DB? + $sdk = DataService::Configure(config('services.quickbooks.settings')); + if(env('APP_DEBUG')) { + $sdk->setLogLocation(storage_path("logs/quickbooks.log")); + $sdk->enableLog(); + } + + $sdk->setMinorVersion("73"); + $sdk->throwExceptionOnError(true); + + return new QuickbooksSDKWrapper($sdk); + }); + + // Register SDKWrapper with DataService dependency + $this->app->singleton(QuickbooksService::class, function ($app) { + return new QuickbooksService($app->make(QuickbooksInterface::class)); + }); + + $this->app->singleton(QuickbooksAuthService::class, function ($app) { + return new QuickbooksAuthService($app->make(QuickbooksInterface::class)); + }); + + $this->app->singleton(QuickbooksTransformer::class,QuickbooksTransformer::class); + } + /** + * Bootstrap services. + * + * @return void + */ + public function boot() + { + $this->registerConfig(); + } + + protected function registerConfig() { + config()->set( 'services.quickbooks' , + ['settings' => [ + 'auth_mode' => 'oauth2', + 'ClientID' => env('QUICKBOOKS_CLIENT_ID', false), + 'ClientSecret' => env('QUICKBOOKS_CLIENT_SECRET', false), + 'RedirectURI' => env('QUICKBOOKS_REDIRECT_URL', env('APP_URL')), + 'scope' => "com.intuit.quickbooks.accounting", + 'baseUrl' => ucfirst(env('APP_ENV')) + ], + 'debug' => env('APP_DEBUG') || env('APP_ENV') + ] + ); + } +} diff --git a/config/app.php b/config/app.php index 59b709d0a9b6..6a812d1322e0 100644 --- a/config/app.php +++ b/config/app.php @@ -200,7 +200,8 @@ return [ App\Providers\MultiDBProvider::class, App\Providers\ClientPortalServiceProvider::class, App\Providers\NinjaTranslationServiceProvider::class, - App\Providers\StaticServiceProvider::class + App\Providers\StaticServiceProvider::class, + App\Providers\QuickbooksServiceProvider::class ], /* @@ -217,7 +218,7 @@ return [ 'aliases' => Facade::defaultAliases()->merge([ 'Collector' => Turbo124\Beacon\CollectorFacade::class, 'CustomMessage' => App\Utils\ClientPortal\CustomMessage\CustomMessageFacade::class, - 'Redis' => Illuminate\Support\Facades\Redis::class, + 'Redis' => Illuminate\Support\Facades\Redis::class ])->toArray(), ]; From 4c3829b8c1fd26bbc48a1c042acdbe05496c6b8f Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:16:48 -0400 Subject: [PATCH 30/69] add repository for quickbooks data --- .../Contracts/RepositoryInterface.php | 12 ++++++ .../Import/Quickbooks/CustomerRepository.php | 11 ++++++ .../Import/Quickbooks/Repository.php | 38 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 app/Repositories/Import/Quickbooks/Contracts/RepositoryInterface.php create mode 100644 app/Repositories/Import/Quickbooks/CustomerRepository.php create mode 100644 app/Repositories/Import/Quickbooks/Repository.php diff --git a/app/Repositories/Import/Quickbooks/Contracts/RepositoryInterface.php b/app/Repositories/Import/Quickbooks/Contracts/RepositoryInterface.php new file mode 100644 index 000000000000..9bd07f7e01f2 --- /dev/null +++ b/app/Repositories/Import/Quickbooks/Contracts/RepositoryInterface.php @@ -0,0 +1,12 @@ +db= $db; + $this->transformer = $transfomer; + } + + public function count() : int { + return $this->db->totalRecords($this->entity); + } + + public function all() : Collection + { + return $this->get($this->count()); + } + + public function get(int $max = 100): Collection + { + return $this->transformer->transform($this->db->fetchRecords($this->entity, $max), $this->entity); + } + + +} \ No newline at end of file From 85220bf58f2cb4cc8ddcc4c6198af465b93ec455 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:17:09 -0400 Subject: [PATCH 31/69] add transformer for underline raw data --- .../Quickbooks/Transformers/Transformer.php | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 app/Repositories/Import/Quickbooks/Transformers/Transformer.php diff --git a/app/Repositories/Import/Quickbooks/Transformers/Transformer.php b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php new file mode 100644 index 000000000000..26b3137e36a9 --- /dev/null +++ b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php @@ -0,0 +1,42 @@ +transformation($items, []); + } + + protected function transformCustomers(array $items): Collection + { + return $this->transformation($items, [ + 'CompanyName', + 'PrimaryPhone', + 'BillAddr', + 'ShipAddr', + 'Notes', + 'GivenName', + 'FamilyName', + 'PrimaryEmailAddr', + 'CurrencyRef', + 'MetaData' + ]); + } + + protected function transformation(array $items, array $keys) : Collection + { + return collect($items)->select($keys); + } + +} From 79ed325a2902e72b4b14aef236cbdf97278a4301 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:17:29 -0400 Subject: [PATCH 32/69] add sdk wrapper --- .../Quickbooks/Contracts/SdkInterface.php | 14 +++ app/Services/Import/Quickbooks/SdkWrapper.php | 103 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 app/Services/Import/Quickbooks/Contracts/SdkInterface.php create mode 100644 app/Services/Import/Quickbooks/SdkWrapper.php diff --git a/app/Services/Import/Quickbooks/Contracts/SdkInterface.php b/app/Services/Import/Quickbooks/Contracts/SdkInterface.php new file mode 100644 index 000000000000..5153a8923eb9 --- /dev/null +++ b/app/Services/Import/Quickbooks/Contracts/SdkInterface.php @@ -0,0 +1,14 @@ +sdk = $sdk; + } + + public function getAuthorizationUrl() : string + { + return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL(); + + } + + public function getAccessToken() : array + { + return $this->getTokens(); + } + + public function getRefreshToken(): array{ + return $this->gettokens(); + } + + public function accessToken(string $code, string $realm) : array + { + $token = ($this->sdk->getOAuth2LoginHelper())->exchangeAuthorizationCodeForToken($code,$realm); + + return $this->getTokens(); + } + + private function getTokens() : array { + + $token =($this->sdk->getOAuth2LoginHelper())->getAccessToken(); + $access_token = $token->getAccessToken(); + $refresh_token = $token->getRefreshToken(); + $access_token_expires = $token->getAccessTokenExpiresAt(); + $refresh_token_expires = $token->getRefreshTokenExpiresAt(); + + return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); + } + + public function refreshToken(): array + { + $token = ($this->sdk->getOAuth2LoginHelper())->refreshToken(); + $this->sdk = $this->sdk->updateOAuth2Token($token); + + return $this->getTokens(); + } + + public function handleCallbacks(array $data): void { + + } + + public function totalRecords(string $entity) : int { + return $this->sdk->Query("select count(*) from $entity"); + } + + private function queryData(string $query, int $start = 1, $limit = 100) : array + { + return (array) $this->sdk->Query($query, $start, $limit); + } + + public function fetchRecords( string $entity, int $max = 1000): array { + + if(!in_array($entity, $this->entities)) return []; + + $records = []; + $start = 1; + $limit = 100; + try { + $total = $this->totalRecords($entity); + $total = min($max, $total); + + // Step 3 & 4: Get chunks of records until the total required records are retrieved + do { + $limit = min(self::MAXRESULTS, $total - $start); + $recordsChunk = $this->queryData("select * from $entity", $start, $limit); + if(empty($recordsChunk)) break; + + $records = array_merge($records,$recordsChunk); + $start += $limit; + } while ($start < $total); + if(empty($records)) throw new \Exceptions("No records retrieved!"); + + } catch (\Throwable $th) { + nlog("Fetch Quickbooks API Error: {$th->getMessage()}"); + } + + return $records; + } +} From d5e499febd19f245a07a6365f7141e0a28fe2646 Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:18:17 -0400 Subject: [PATCH 33/69] add api service for quickboks --- app/Services/Import/Quickbooks/Service.php | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 app/Services/Import/Quickbooks/Service.php diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php new file mode 100644 index 000000000000..58461f4a4d25 --- /dev/null +++ b/app/Services/Import/Quickbooks/Service.php @@ -0,0 +1,67 @@ +sdk = $quickbooks; + } + + public function getAccessToken() : array + { + // TODO: Cache token and + $token = $this->sdk->getAccessToken(); + $access_token = $token->getAccessToken(); + $refresh_token = $token->getRefreshToken(); + $access_token_expires = $token->getAccessTokenExpiresAt(); + $refresh_token_expires = $token->getRefreshTokenExpiresAt(); + //TODO: Cache token object. Update $sdk instance? + return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); + } + + public function getRefreshToken() : array + { + // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire + return $this->getAccessToken(); + } + /** + * fetch QuickBooks invoice records + * @param int $max The maximum records to fetch. Default 100 + * @return Illuminate\Support\Collection; + */ + public function fetchInvoices(int $max = 100): Collection + { + return $this->transformer->transform($this->fetchRecords( 'Invoice', $max), 'Invoice'); + } + + protected function fetchRecords(string $entity, $max = 100) : Collection { + return (self::RepositoryFactory($entity))->get($max); + } + + private static function RepositoryFactory(string $entity) : RepositoryInterface + { + return app("\\App\\Repositories\\Import\Quickbooks\\{$entity}Repository"); + } + + /** + * fetch QuickBooks customer records + * @param int $max The maximum records to fetch. Default 100 + * @return Illuminate\Support\Collection; + */ + public function fetchCustomers(int $max = 100): Collection + { + return $this->fetchRecords('Customer', $max) ; + } + + public function totalRecords(string $entity) : int + { + return (self::RepositoryFactory($entity))->count(); + } +} \ No newline at end of file From 3ba4533b28a63c3aebdcb2ba1f30635e46457b6c Mon Sep 17 00:00:00 2001 From: karneaud Date: Mon, 29 Jul 2024 16:18:40 -0400 Subject: [PATCH 34/69] add service tests for quickbooks --- .../Quickbooks/QuickBooksServiceTest.php | 48 +++++++++++++++++ .../Import/Quickbooks/SdkWrapperTest.php | 47 ++++++++++++++++ .../Import/Quickbooks/ServiceTest.php | 54 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 tests/Integration/Services/Import/Quickbooks/QuickBooksServiceTest.php create mode 100644 tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php create mode 100644 tests/Unit/Services/Import/Quickbooks/ServiceTest.php diff --git a/tests/Integration/Services/Import/Quickbooks/QuickBooksServiceTest.php b/tests/Integration/Services/Import/Quickbooks/QuickBooksServiceTest.php new file mode 100644 index 000000000000..5b9502129464 --- /dev/null +++ b/tests/Integration/Services/Import/Quickbooks/QuickBooksServiceTest.php @@ -0,0 +1,48 @@ +shouldReceive('Query')->andReturnUsing(function($val) use ($count, $data) { + if(stristr($val, 'count')) { + return $count; + } + + return Arr::take($data,4); + }); + app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($sdkMock)); + + $this->service = app(QuickbooksService::class); + + } + + public function testImportCustomers() + { + $collection = $this->service->fetchCustomers(4); + + $this->assertInstanceOf(Collection::class, $collection); + $this->assertEquals(4, $collection->count()); + $this->assertNotNull($item = $collection->whereStrict('CompanyName', "Cool Cars")->first()); + $this->assertEquals("Grace", $item['GivenName']); + } +} diff --git a/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php new file mode 100644 index 000000000000..a76da2720fe1 --- /dev/null +++ b/tests/Unit/Services/Import/Quickbooks/SdkWrapperTest.php @@ -0,0 +1,47 @@ +sdkMock = Mockery::mock(sdtClass::class); + $this->sdk = new QuickbookSDK($this->sdkMock); + + + } + + function testIsInstanceOf() { + $this->assertInstanceOf(SdkInterface::class, $this->sdk); + } + + function testMethodFetchRecords() { + $data = json_decode( + file_get_contents(base_path('tests/Mock/Quickbooks/Data/customers.json')),true + ); + $count = count($data); + $this->sdkMock->shouldReceive('Query')->andReturnUsing(function($val) use ($count, $data) { + if(stristr($val, 'count')) { + return $count; + } + + return Arr::take($data,4); + }); + + $this->assertEquals($count, $this->sdk->totalRecords('Customer')); + $this->assertEquals(4, count($this->sdk->fetchRecords('Customer', 4))); + } +} diff --git a/tests/Unit/Services/Import/Quickbooks/ServiceTest.php b/tests/Unit/Services/Import/Quickbooks/ServiceTest.php new file mode 100644 index 000000000000..2c0f2b5553d8 --- /dev/null +++ b/tests/Unit/Services/Import/Quickbooks/ServiceTest.php @@ -0,0 +1,54 @@ +service = Mockery::mock( new QuickbooksService(Mockery::mock(QuickbooksInterface::class)))->shouldAllowMockingProtectedMethods(); + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } + + public function testTotalRecords() + { + $entity = 'Customer'; + $count = 10; + + $this->service->shouldReceive('totalRecords') + ->with($entity) + ->andReturn($count); + + $result = $this->service->totalRecords($entity); + + $this->assertEquals($count, $result); + } + + public function testHasFetchRecords() + { + $entity = 'Customer'; + $count = 10; + + $this->service->shouldReceive('fetchRecords') + ->with($entity, $count) + ->andReturn(collect()); + + $result = $this->service->fetchCustomers($count); + + $this->assertInstanceOf(Collection::class, $result); + } +} From 90b6907e71381cf46c26839f38edc48db62da9d0 Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 30 Jul 2024 14:19:15 -0400 Subject: [PATCH 35/69] modify to use quickbooks service and store data in cache --- .../ImportQuickbooksController.php | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index cf17cc7ff8ab..ecd3788ccfbe 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -9,10 +9,24 @@ use Illuminate\Http\Response; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Cache; use App\Jobs\Import\QuickbooksIngest; -use App\Http\Controllers\ImportController as BaseController; +use App\Services\Import\Quickbooks\Service as QuickbooksService; class ImportQuickbooksController extends BaseController { + protected QuickbooksService $service; + private $import_entities = [ + 'client' => 'Customer', + 'invoice' => 'Invoice', + 'product' => 'Item', + 'payment' => 'Payment' + ]; + + public function __construct(QuickbooksService $service) { + parent::__construct(); + + $this->service = $service; + } + /** * Determine if the user is authorized to make this request. * @@ -29,25 +43,28 @@ class ImportQuickbooksController extends BaseController public function preimport(Request $request) { // Check for authorization otherwise - // Create a reference $hash = Str::random(32); $data = [ - 'hash' => $hash, - 'type' => $request->input('import_type') + 'hash' => $hash, + 'type' => $request->input('import_type', 'client'), + 'max' => $request->input('max', 100) ]; - $contents = $this->getData(); - Cache::put("$hash-{$data['type']}", base64_encode(json_encode($contents)), 600); + $this->getData($data); return response()->json(['message' => 'Data cached for import'] + $data, 200); } - public function authorizeQuickbooks() { - - } - - protected function getData() { + protected function getData($data) { + $entity = $this->import_entities[$data['type']]; + $cache_name = "{$data['hash']}-{$data['type']}"; + // TODO: Get or put cache or DB? + if(! Cache::has($cache_name) ) + { + $contents = call_user_func([$this->service, "fetch{$entity}s"], $data['max']); + Cache::put($cache_name, base64_encode( $contents->toJson()), 600); + } } /** From 6d40891c742d957657f43510516df47b426bff08 Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 30 Jul 2024 14:19:27 -0400 Subject: [PATCH 36/69] start at 0 --- app/Services/Import/Quickbooks/SdkWrapper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 234a66fd6d7a..85dd5a529b82 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -77,7 +77,7 @@ final class SdkWrapper implements QuickbooksInterface if(!in_array($entity, $this->entities)) return []; $records = []; - $start = 1; + $start = 0; $limit = 100; try { $total = $this->totalRecords($entity); From 404933625f598e44fb861b2020469812f66221ff Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 30 Jul 2024 14:19:46 -0400 Subject: [PATCH 37/69] import controller test for quickbooks --- .../ImportQuickbooksControllerTest.php | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php index 8534e33ed681..6a7094c9922c 100644 --- a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php +++ b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php @@ -2,6 +2,9 @@ namespace Tests\Feature\Http\Controllers; +use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; +use App\Services\Import\Quickbooks\Service as QuickbooksService; +use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDK; use Illuminate\Foundation\Testing\DatabaseTransactions; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; @@ -10,6 +13,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Bus; use GuzzleHttp\Psr7\Message; +use Illuminate\Support\Arr; use Tests\MockAccountData; use Tests\TestCase; use Mockery; @@ -24,6 +28,7 @@ class ImportQuickbooksControllerTest extends TestCase parent::setUp(); $this->makeTestData(); + Session::start(); } @@ -31,42 +36,27 @@ class ImportQuickbooksControllerTest extends TestCase { Cache::spy(); - $data = ($this->setUpTestResponseData('200-cutomer-response.txt'))['QueryResponse']['Customer']; - // Create a mock of the UserController - $controllerMock = Mockery::mock('App\Http\Controllers\ImportQuickbooksController[getData]')->shouldAllowMockingProtectedMethods(); - // Define what the mocked getData method should return - $controllerMock->shouldReceive('getData') - ->once() - ->andReturn( $data, true); - // Bind the mock to the Laravel container - $this->app->instance('App\Http\Controllers\ImportQuickbooksController', $controllerMock); + $data = $this->setUpTestData('customers'); // Perform the test $response = $this->withHeaders([ 'X-API-TOKEN' => $this->token, ])->post('/api/v1/import/quickbooks/preimport',[ 'import_type' => 'client' ]); + $response->assertStatus(200); $response = json_decode( $response->getContent()); - $this->assertNotNull($response->hash); - Cache::shouldHaveReceived('put')->once()->with("{$response->hash}-client", base64_encode(json_encode($data)),600); + $this->assertNotNull($response->hash); + Cache::shouldHaveReceived('put')->once(); } public function testImportQuickbooksCustomers(): void { - + Cache::spy(); Bus::fake(); - $data = ($this->setUpTestResponseData('200-cutomer-response.txt'))['QueryResponse']['Customer']; - // Create a mock of the UserController - $controllerMock = Mockery::mock('App\Http\Controllers\ImportQuickbooksController[getData]')->shouldAllowMockingProtectedMethods(); - // Define what the mocked getData method should return - $controllerMock->shouldReceive('getData') - ->once() - ->andReturn( $data, true); - // Bind the mock to the Laravel container - $this->app->instance('App\Http\Controllers\ImportQuickbooksController', $controllerMock); + $this->setUpTestData('customers'); // Perform the test $response = $this->withHeaders([ 'X-API-TOKEN' => $this->token, @@ -76,24 +66,34 @@ class ImportQuickbooksControllerTest extends TestCase $response->assertStatus(200); $response = json_decode( $response->getContent()); $this->assertNotNull($response->hash); + $hash = $response->hash; $response = $this->withHeaders([ 'X-API-TOKEN' => $this->token, ])->post('/api/v1/import/quickbooks',[ 'import_type' => 'client', 'hash' => $response->hash ]); + $response->assertStatus(200); - + Cache::shouldHaveReceived('has')->once()->with("{$hash}-client"); Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); } - protected function setUpTestResponseData($file) { - $fullResponse = file_get_contents( base_path("tests/Mock/Quickbooks/Http/Response/$file") ); - // Parse the full response using Guzzle - $response = Message::parseResponse($fullResponse); - // Extract the JSON body - $jsonBody = (string) $response->getBody(); - // Decode the JSON body to an array - return json_decode($jsonBody, true); + protected function setUpTestData($file) { + $data = json_decode( + file_get_contents(base_path("tests/Mock/Quickbooks/Data/$file.json")),true + ); + $count = count($data); + $sdkMock = Mockery::mock(sdtClass::class); + $sdkMock->shouldReceive('Query')->andReturnUsing(function($val, $s = 1, $max = 1000) use ($count, $data) { + if(stristr($val, 'count')) { + return $count; + } + + return Arr::take($data,$max); + }); + app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($sdkMock)); + + return $data; } } From 014a3c1f5c8cf8499205159934649ff3512947a8 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:32:20 -0400 Subject: [PATCH 38/69] added middleware with validation. refactored methods --- .../ImportQuickbooksController.php | 101 ++++++++++++++++-- 1 file changed, 94 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index ecd3788ccfbe..2e655f21050c 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -2,13 +2,16 @@ namespace App\Http\Controllers; +use \Closure; use App\Utils\Ninja; +use App\Models\Company; use Illuminate\Support\Str; use Illuminate\Http\Request; use Illuminate\Http\Response; use App\Utils\Traits\MakesHash; use Illuminate\Support\Facades\Cache; use App\Jobs\Import\QuickbooksIngest; +use Illuminate\Support\Facades\Validator; use App\Services\Import\Quickbooks\Service as QuickbooksService; class ImportQuickbooksController extends BaseController @@ -23,21 +26,104 @@ class ImportQuickbooksController extends BaseController public function __construct(QuickbooksService $service) { parent::__construct(); - + $this->service = $service; - } + $this->middleware( + function (Request $request, Closure $next) { + + // Check for the required query parameters + if (!$request->has(['code', 'state', 'realmId'])) { + return abort(400,'Unauthorized'); + } + + $rules = [ + 'state' => [ + 'required', + 'valid' => function ($attribute, $value, $fail) { + if (!Cache::has($value)) { + $fail('The state is invalid.'); + } + }, + ] + ]; + // Custom error messages + $messages = [ + 'state.required' => 'The state is required.', + 'state.valid' => 'state token not valid' + ]; + // Perform the validation + $validator = Validator::make($request->all(), $rules, $messages); + if ($validator->fails()) { + // If validation fails, redirect back with errors and input + return redirect('/') + ->withErrors($validator) + ->withInput(); + } + + $token = Cache::pull($request->state); + $request->merge(['company' => Cache::get($token) ]); + + return $next($request); + } + )->only('onAuthorized'); + $this->middleware( + function ( Request $request, Closure $next) { + $rules = [ + 'token' => [ + 'required', + 'valid' => function ($attribute, $value, $fail) { + if (!Cache::has($value) || (!Company::where('company_key', (Cache::get($value))['company_key'])->exists() )) { + $fail('The company is invalid.'); + } + }, + ] + ]; + // Custom error messages + $messages = [ + 'token.required' => 'The token is required.', + 'token.valid' => 'Token note valid!' + ]; + // Perform the validation + $validator = Validator::make(['token' => $request->token ], $rules, $messages); + if ($validator->fails()) { + // If validation fails, redirect back with errors and input + return redirect()->back() + ->withErrors($validator) + ->withInput(); + } + + //If validation passes, proceed to the next middleware/controller + return $next($request); + } + )->only('authorizeQuickbooks'); + } + + public function onAuthorized(Request $request) { + + $realmId = $request->query('realmId'); + $tokens = $this->service->getOAuth()->accessToken($request->query('code'), $realmId); + $company = $request->input('company'); + Cache::put($company['company_key'], $tokens['access_token'], $tokens['access_token_expires']); + // TODO: save refresh token and realmId in company DB + + return response(200); + } /** * Determine if the user is authorized to make this request. * * @return bool */ - public function authorize($ability, $arguments = []): bool + public function authorizeQuickbooks(Request $request) { - /** @var \App\Models\User $user */ - $user = auth()->user(); + $token = $request->token; + $auth = $this->service->getOAuth(); + $authorizationUrl = $auth->getAuthorizationUrl(); + $state = $auth->getState(); - return $user->isAdmin() ; + Cache::put($state, $token, 90); + + return redirect()->to($authorizationUrl); } public function preimport(Request $request) @@ -52,7 +138,7 @@ class ImportQuickbooksController extends BaseController ]; $this->getData($data); - return response()->json(['message' => 'Data cached for import'] + $data, 200); + return $data; } protected function getData($data) { @@ -96,6 +182,7 @@ class ImportQuickbooksController extends BaseController */ public function import(Request $request) { + $this->preimport($request); /** @var \App\Models\User $user */ $user = auth()->user(); if (Ninja::isHosted()) { From 6f2d139f45fc840b83e0f9a32f8ad30fe8524ebe Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:32:45 -0400 Subject: [PATCH 39/69] bind instead of singleton --- app/Providers/QuickbooksServiceProvider.php | 38 ++++++++++++++++++--- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php index bf517aae3a6a..5ee7b79aec85 100644 --- a/app/Providers/QuickbooksServiceProvider.php +++ b/app/Providers/QuickbooksServiceProvider.php @@ -2,8 +2,12 @@ namespace App\Providers; +use Illuminate\Support\Str; +use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; use QuickBooksOnline\API\DataService\DataService; +use App\Http\Controllers\ImportQuickbooksController; use App\Services\Import\Quickbooks\Service as QuickbooksService; use App\Services\Import\Quickbooks\Auth as QuickbooksAuthService; use App\Repositories\Import\Quickcbooks\Contracts\RepositoryInterface; @@ -20,9 +24,10 @@ class QuickbooksServiceProvider extends ServiceProvider */ public function register() { - $this->app->singleton(QuickbooksInterface::class, function ($app) { - // TODO: Possibly load tokens from Cache or DB? - $sdk = DataService::Configure(config('services.quickbooks.settings')); + + $this->app->bind(QuickbooksInterface::class, function ($app) { + // TODO: Load tokens from Cache and DB? + $sdk = DataService::Configure(config('services.quickbooks.settings') + ['state' => Str::random(12)]); if(env('APP_DEBUG')) { $sdk->setLogLocation(storage_path("logs/quickbooks.log")); $sdk->enableLog(); @@ -52,6 +57,7 @@ class QuickbooksServiceProvider extends ServiceProvider */ public function boot() { + $this->registerRoutes(); $this->registerConfig(); } @@ -61,7 +67,8 @@ class QuickbooksServiceProvider extends ServiceProvider 'auth_mode' => 'oauth2', 'ClientID' => env('QUICKBOOKS_CLIENT_ID', false), 'ClientSecret' => env('QUICKBOOKS_CLIENT_SECRET', false), - 'RedirectURI' => env('QUICKBOOKS_REDIRECT_URL', env('APP_URL')), + // TODO use env('QUICKBOOKS_REDIRECT_URI') or route()/ url() + 'RedirectURI' => url("/quickbooks/authorized"), 'scope' => "com.intuit.quickbooks.accounting", 'baseUrl' => ucfirst(env('APP_ENV')) ], @@ -69,4 +76,27 @@ class QuickbooksServiceProvider extends ServiceProvider ] ); } + + /** + * Register custom routes. + * + * @return void + */ + protected function registerRoutes() + { + Route::middleware('web') + ->namespace($this->app->getNamespace() . 'Http\Controllers') + ->group(function () { + + Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('authorize.quickbooks'); + Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('authorized.quickbooks'); + }); + + Route::middleware('api') + ->namespace($this->app->getNamespace() . 'Http\Controllers') + ->group(function () { + Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); + //Route::post('import/quickbooks/preimport', [ImportQuickbooksController::class, 'preimport'])->name('import.quickbooks.preimport'); + }); + } } From af47d5d8e41d68738835fb1b6097065b28fead6e Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:33:13 -0400 Subject: [PATCH 40/69] move quickbooks routes to provider --- routes/api.php | 2 -- routes/web.php | 1 - 2 files changed, 3 deletions(-) diff --git a/routes/api.php b/routes/api.php index f496e14c428d..47f2cfafa547 100644 --- a/routes/api.php +++ b/routes/api.php @@ -245,8 +245,6 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale'] Route::post('import_json', [ImportJsonController::class, 'import'])->name('import.import_json'); Route::post('preimport', [ImportController::class, 'preimport'])->name('import.preimport'); ; - Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); - Route::post('import/quickbooks/preimport', [ImportQuickbooksController::class, 'preimport'])->name('import.quickbooks.preimport'); Route::resource('invoices', InvoiceController::class); // name = (invoices. index / create / show / update / destroy / edit Route::get('invoices/{invoice}/delivery_note', [InvoiceController::class, 'deliveryNote'])->name('invoices.delivery_note'); Route::get('invoices/{invoice}/{action}', [InvoiceController::class, 'action'])->name('invoices.action'); diff --git a/routes/web.php b/routes/web.php index 6487eaa50975..d1231316b1f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -38,7 +38,6 @@ Route::middleware('url_db')->group(function () { Route::post('/user/confirm/{confirmation_code}', [UserController::class, 'confirmWithPassword']); }); -Route::post('import/quickbooks/authorize', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('import.auth.quickbooks'); Route::get('stripe/signup/{token}', [StripeConnectController::class, 'initialize'])->name('stripe_connect.initialization'); Route::get('stripe/completed', [StripeConnectController::class, 'completed'])->name('stripe_connect.return'); From b3ab9e468cc497cfb272e9d7329c037b90520105 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:33:44 -0400 Subject: [PATCH 41/69] separate auth logic from entity logic --- app/Services/Import/Quickbooks/Auth.php | 37 ++++++++++++++++++++++ app/Services/Import/Quickbooks/Service.php | 6 ++++ 2 files changed, 43 insertions(+) create mode 100644 app/Services/Import/Quickbooks/Auth.php diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php new file mode 100644 index 000000000000..4fcb373775ec --- /dev/null +++ b/app/Services/Import/Quickbooks/Auth.php @@ -0,0 +1,37 @@ +sdk = $quickbooks; + } + + public function accessToken(string $code, string $realm ) : array + { + // TODO: Get or put token in Cache or DB? + return $this->sdk->accessToken($code, $realm); + } + + public function refreshToken() : array + { + // TODO: Get or put token in Cache or DB? + return $this->sdk->refreshToken(); + } + + public function getAuthorizationUrl(): string + { + return $this->sdk->getAuthorizationUrl(); + } + + public function getState() : string + { + return $this->sdk->getState(); + } +} \ No newline at end of file diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php index 58461f4a4d25..2060c822ef64 100644 --- a/app/Services/Import/Quickbooks/Service.php +++ b/app/Services/Import/Quickbooks/Service.php @@ -3,6 +3,7 @@ namespace App\Services\Import\Quickbooks; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use App\Services\Import\Quickbooks\Auth; use App\Repositories\Import\Quickbooks\Contracts\RepositoryInterface; use App\Services\Import\QuickBooks\Contracts\SdkInterface as QuickbooksInterface; @@ -14,6 +15,11 @@ final class Service $this->sdk = $quickbooks; } + public function getOAuth() : Auth + { + return new Auth($this->sdk); + } + public function getAccessToken() : array { // TODO: Cache token and From ab5e05485ec2722093d96351b4c6ab1fad6b4966 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:34:13 -0400 Subject: [PATCH 42/69] add get state --- app/Services/Import/Quickbooks/SdkWrapper.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/Services/Import/Quickbooks/SdkWrapper.php b/app/Services/Import/Quickbooks/SdkWrapper.php index 85dd5a529b82..e14c86689f67 100644 --- a/app/Services/Import/Quickbooks/SdkWrapper.php +++ b/app/Services/Import/Quickbooks/SdkWrapper.php @@ -21,7 +21,11 @@ final class SdkWrapper implements QuickbooksInterface public function getAuthorizationUrl() : string { return ($this->sdk->getOAuth2LoginHelper())->getAuthorizationCodeURL(); + } + public function getState() : string + { + return ($this->sdk->getOAuth2LoginHelper())->getState(); } public function getAccessToken() : array @@ -30,7 +34,7 @@ final class SdkWrapper implements QuickbooksInterface } public function getRefreshToken(): array{ - return $this->gettokens(); + return $this->getTokens(); } public function accessToken(string $code, string $realm) : array From 32aad063083a6282e8d4ffe731ff0b27c805fdba Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 1 Aug 2024 22:34:36 -0400 Subject: [PATCH 43/69] update quickbooks controller tests --- .../ImportQuickbooksControllerTest.php | 132 +++++++++++------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php index 6a7094c9922c..92d6f58f9148 100644 --- a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php +++ b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php @@ -14,6 +14,8 @@ use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Bus; use GuzzleHttp\Psr7\Message; use Illuminate\Support\Arr; +use Illuminate\Support\Str; +use Mockery\MockInterface; use Tests\MockAccountData; use Tests\TestCase; use Mockery; @@ -22,78 +24,112 @@ class ImportQuickbooksControllerTest extends TestCase { use MockAccountData; use DatabaseTransactions; + + private $mock; + private $state; protected function setUp(): void { parent::setUp(); + $this->state = Str::random(4); + $this->mock = Mockery::mock(stdClass::class); $this->makeTestData(); - + Session::start(); + + app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($this->mock)); } - public function testPreImportQuickbooksController(): void + public function testAuthorize(): void { - Cache::spy(); + + $this->mock->shouldReceive('getState')->andReturn($this->state); + $this->mock->shouldReceive('getAuthorizationCodeURL')->andReturn('https://example.com'); + $this->mock->shouldReceive("getOAuth2LoginHelper")->andReturn($this->mock); - $data = $this->setUpTestData('customers'); + Cache::spy(); + Cache::shouldReceive('get') + ->with($token = $this->company->company_key) + ->andReturn( ['company_key' => $token, 'id' => $this->company->id]); + Cache::shouldReceive('has') + ->andReturn(true); // Perform the test - $response = $this->withHeaders([ - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/import/quickbooks/preimport',[ - 'import_type' => 'client' - ]); + $response = $this->get(route('authorize.quickbooks', ['token' => $token])); + $response->assertStatus(302); + + Cache::shouldHaveReceived('put')->once()->with($this->state, $token, 90); + } + + public function testOnAuthorized(): void + { + $token = ['company_key' => $this->company->company_key, 'id' => $this->company->id] ; + + $this->mock->shouldReceive('getAccessToken')->andReturn(Mockery::mock(stdClass::class,function(MockInterface $mock){ + $mock->shouldReceive('getAccessToken')->andReturn('abcdefg'); + $mock->shouldReceive('getRefreshToken')->andReturn('abcdefghi'); + $mock->shouldReceive('getAccessTokenExpiresAt')->andReturn(3600); + $mock->shouldReceive('getRefreshTokenExpiresAt')->andReturn(8726400); + })); + $this->mock->shouldReceive("getOAuth2LoginHelper")->andReturn($this->mock); + $this->mock->shouldReceive('exchangeAuthorizationCodeForToken')->once(); - $response->assertStatus(200); - $response = json_decode( $response->getContent()); - - $this->assertNotNull($response->hash); - Cache::shouldHaveReceived('put')->once(); - } - - public function testImportQuickbooksCustomers(): void - { Cache::spy(); - Bus::fake(); - - $this->setUpTestData('customers'); + Cache::shouldReceive('has') + ->andReturn(true); + Cache::shouldReceive('get')->andReturn($token); + Cache::shouldReceive('pull')->andReturn($token['company_key']); // Perform the test - $response = $this->withHeaders([ - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/import/quickbooks/preimport',[ - 'import_type' => 'client' - ]); + $response = $this->get("/quickbooks/authorized/?code=123456&state={$this->state}&realmId=12345678"); $response->assertStatus(200); - $response = json_decode( $response->getContent()); - $this->assertNotNull($response->hash); - $hash = $response->hash; - $response = $this->withHeaders([ - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/import/quickbooks',[ - 'import_type' => 'client', - 'hash' => $response->hash - ]); - $response->assertStatus(200); - Cache::shouldHaveReceived('has')->once()->with("{$hash}-client"); - Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); + Cache::shouldHaveReceived('put')->once()->with($token['company_key'], 'abcdefg', 3600); + + $this->mock->shouldHaveReceived('exchangeAuthorizationCodeForToken')->once()->with(123456,12345678); } + // public function testImport(): void + // { + // Cache::spy(); + // Bus::fake(); + // $this->mock->shouldReceive('Query')->andReturnUsing( + // function($val, $s = 1, $max = 1000) use ($count, $data) { + // if(stristr($val, 'count')) { + // return $count; + // } + + // return Arr::take($data,$max); + // } + // ); + // $this->setUpTestData('customers'); + // // Perform the test + // $response = $this->withHeaders([ + // 'X-API-TOKEN' => $this->token, + // ])->post('/api/v1/import/quickbooks/preimport',[ + // 'import_type' => 'client' + // ]); + // $response->assertStatus(200); + // $response = json_decode( $response->getContent()); + // $this->assertNotNull($response->hash); + // $hash = $response->hash; + // $response = $this->withHeaders([ + // 'X-API-TOKEN' => $this->token, + // ])->post('/api/v1/import/quickbooks',[ + // 'import_type' => 'client', + // 'hash' => $response->hash + // ]); + // $response->assertStatus(200); + + // Cache::shouldHaveReceived('has')->once()->with("{$hash}-client"); + // Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); + // } + protected function setUpTestData($file) { $data = json_decode( file_get_contents(base_path("tests/Mock/Quickbooks/Data/$file.json")),true ); $count = count($data); - $sdkMock = Mockery::mock(sdtClass::class); - $sdkMock->shouldReceive('Query')->andReturnUsing(function($val, $s = 1, $max = 1000) use ($count, $data) { - if(stristr($val, 'count')) { - return $count; - } - - return Arr::take($data,$max); - }); - app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($sdkMock)); - + return $data; } } From 5a28a81f0ffcd9ffd8b268500bf5dc2d50f477ac Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 2 Aug 2024 21:43:35 -0400 Subject: [PATCH 44/69] update lock --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 7f035e6a9785..8b7e799a48b5 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8fdb8245fbc563f8c09da161876f52a7", + "content-hash": "f137c4c97faf7721366179f36c1ca72a", "packages": [ { "name": "adrienrn/php-mimetyper", From eaa12e8b3171f8c64c5406e9220a38437b5dd471 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 2 Aug 2024 21:43:52 -0400 Subject: [PATCH 45/69] add quickbooks sdk factory --- app/Factory/QuickbooksSDKFactory.php | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/Factory/QuickbooksSDKFactory.php diff --git a/app/Factory/QuickbooksSDKFactory.php b/app/Factory/QuickbooksSDKFactory.php new file mode 100644 index 000000000000..128b57a97d0c --- /dev/null +++ b/app/Factory/QuickbooksSDKFactory.php @@ -0,0 +1,46 @@ +company(); + MultiDB::findAndSetDbByCompanyKey($company->company_key); + // Retrieve token from the database + if(($quickbooks = DB::table('companies')->where('id', $company->id)->select(['quickbook_refresh_token','quickbooks_realm_id'])->first())) { + $refreshTokenKey = $quickbooks->quickbooks_refresh_token; + $QBORealmID = $quickbooks->quickbooks_realm_id; + // Retrieve value from cache + $accessTokenKey = Cache::get($company->company_key); + $tokens = compact('accessTokenKey','refreskTokenKey','QBORealmID'); + } + } + + $config = $tokens + config('services.quickbooks.settings') + [ + 'state' => Str::random(12) + ]; + $sdk = DataService::Configure($config); + if (env('APP_DEBUG')) { + $sdk->setLogLocation(storage_path("logs/quickbooks.log")); + $sdk->enableLog(); + } + + $sdk->setMinorVersion("73"); + $sdk->throwExceptionOnError(true); + + return $sdk; + } +} From bacd5a0f0df8735a53ace5d8170e69b315a735a8 Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 2 Aug 2024 21:47:12 -0400 Subject: [PATCH 46/69] fix routes. use factory --- app/Providers/QuickbooksServiceProvider.php | 29 ++++----------------- 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php index 5ee7b79aec85..f6cf2d09368e 100644 --- a/app/Providers/QuickbooksServiceProvider.php +++ b/app/Providers/QuickbooksServiceProvider.php @@ -3,13 +3,10 @@ namespace App\Providers; use Illuminate\Support\Str; -use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; +use App\Factory\QuickbooksSDKFactory; use Illuminate\Support\ServiceProvider; -use QuickBooksOnline\API\DataService\DataService; -use App\Http\Controllers\ImportQuickbooksController; use App\Services\Import\Quickbooks\Service as QuickbooksService; -use App\Services\Import\Quickbooks\Auth as QuickbooksAuthService; use App\Repositories\Import\Quickcbooks\Contracts\RepositoryInterface; use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDKWrapper; use App\Services\Import\Quickbooks\Contracts\SdkInterface as QuickbooksInterface; @@ -26,26 +23,12 @@ class QuickbooksServiceProvider extends ServiceProvider { $this->app->bind(QuickbooksInterface::class, function ($app) { - // TODO: Load tokens from Cache and DB? - $sdk = DataService::Configure(config('services.quickbooks.settings') + ['state' => Str::random(12)]); - if(env('APP_DEBUG')) { - $sdk->setLogLocation(storage_path("logs/quickbooks.log")); - $sdk->enableLog(); - } - - $sdk->setMinorVersion("73"); - $sdk->throwExceptionOnError(true); - - return new QuickbooksSDKWrapper($sdk); + return new QuickbooksSDKWrapper(QuickbooksSDKFactory::create()); }); // Register SDKWrapper with DataService dependency $this->app->singleton(QuickbooksService::class, function ($app) { - return new QuickbooksService($app->make(QuickbooksInterface::class)); - }); - - $this->app->singleton(QuickbooksAuthService::class, function ($app) { - return new QuickbooksAuthService($app->make(QuickbooksInterface::class)); + return new QuickbooksService($app->make(QuickbooksInterface::class)); }); $this->app->singleton(QuickbooksTransformer::class,QuickbooksTransformer::class); @@ -87,16 +70,14 @@ class QuickbooksServiceProvider extends ServiceProvider Route::middleware('web') ->namespace($this->app->getNamespace() . 'Http\Controllers') ->group(function () { - Route::get('quickbooks/authorize/{token}', [ImportQuickbooksController::class, 'authorizeQuickbooks'])->name('authorize.quickbooks'); Route::get('quickbooks/authorized', [ImportQuickbooksController::class, 'onAuthorized'])->name('authorized.quickbooks'); }); - - Route::middleware('api') + Route::prefix('api/v1') + ->middleware('api') ->namespace($this->app->getNamespace() . 'Http\Controllers') ->group(function () { Route::post('import/quickbooks', [ImportQuickbooksController::class, 'import'])->name('import.quickbooks'); - //Route::post('import/quickbooks/preimport', [ImportQuickbooksController::class, 'preimport'])->name('import.quickbooks.preimport'); }); } } From 32c0006825cf09a5de34651b132a83a25268adda Mon Sep 17 00:00:00 2001 From: karneaud Date: Fri, 2 Aug 2024 21:48:15 -0400 Subject: [PATCH 47/69] move get methods for access token --- app/Services/Import/Quickbooks/Auth.php | 18 ++++++++++++++++++ app/Services/Import/Quickbooks/Service.php | 9 +-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index 4fcb373775ec..667a0bccacb0 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -34,4 +34,22 @@ final class Auth { return $this->sdk->getState(); } + + public function getAccessToken() : array + { + // TODO: Cache token and + $token = $this->sdk->getAccessToken(); + $access_token = $token->getAccessToken(); + $refresh_token = $token->getRefreshToken(); + $access_token_expires = $token->getAccessTokenExpiresAt(); + $refresh_token_expires = $token->getRefreshTokenExpiresAt(); + //TODO: Cache token object. Update $sdk instance? + return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); + } + + public function getRefreshToken() : array + { + // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire + return $this->getAccessToken(); + } } \ No newline at end of file diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php index 2060c822ef64..e4db2879e5b5 100644 --- a/app/Services/Import/Quickbooks/Service.php +++ b/app/Services/Import/Quickbooks/Service.php @@ -22,14 +22,7 @@ final class Service public function getAccessToken() : array { - // TODO: Cache token and - $token = $this->sdk->getAccessToken(); - $access_token = $token->getAccessToken(); - $refresh_token = $token->getRefreshToken(); - $access_token_expires = $token->getAccessTokenExpiresAt(); - $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - //TODO: Cache token object. Update $sdk instance? - return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); + return $this->getOAuth()->getAccessToken(); } public function getRefreshToken() : array From c7817f9bfd65d41a3ca0aa785b427170a70ecd7c Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:50:29 -0400 Subject: [PATCH 48/69] get and refresh tokens --- app/Factory/QuickbooksSDKFactory.php | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/app/Factory/QuickbooksSDKFactory.php b/app/Factory/QuickbooksSDKFactory.php index 128b57a97d0c..64d3263dfcf5 100644 --- a/app/Factory/QuickbooksSDKFactory.php +++ b/app/Factory/QuickbooksSDKFactory.php @@ -8,6 +8,9 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use QuickBooksOnline\API\DataService\DataService; +use App\Services\Import\Quickbooks\Auth as QuickbooksService; +use App\Services\Import\Quickbooks\Repositories\CompanyTokensRepository; + class QuickbooksSDKFactory { @@ -18,14 +21,16 @@ class QuickbooksSDKFactory if(($user = Auth::user())) { $company = $user->company(); - MultiDB::findAndSetDbByCompanyKey($company->company_key); - // Retrieve token from the database - if(($quickbooks = DB::table('companies')->where('id', $company->id)->select(['quickbook_refresh_token','quickbooks_realm_id'])->first())) { - $refreshTokenKey = $quickbooks->quickbooks_refresh_token; - $QBORealmID = $quickbooks->quickbooks_realm_id; - // Retrieve value from cache - $accessTokenKey = Cache::get($company->company_key); - $tokens = compact('accessTokenKey','refreskTokenKey','QBORealmID'); + + $tokens = (new CompanyTokensRepository($company->company_key)); + $tokens = array_filter($tokens->get()); + if(!empty($tokens)) { + $keys = ['refreshTokenKey','QBORealmID']; + if(array_key_exists('access_token', $tokens)) { + $keys = array_merge(['accessTokenKey'] ,$keys); + } + + $tokens = array_combine($keys, array_values($tokens)); } } @@ -40,6 +45,12 @@ class QuickbooksSDKFactory $sdk->setMinorVersion("73"); $sdk->throwExceptionOnError(true); + if(array_key_exists('refreshTokenKey', $config) && !array_key_exists('accessTokenKey', $config)) + { + $auth = new QuickbooksService($sdk); + $tokens = $auth->refreshTokens(); + $auth->saveTokens($tokens); + } return $sdk; } From d79dce66696f7a051b65dbb5deeb54c94350ca39 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:53:45 -0400 Subject: [PATCH 49/69] propertly run import for multiple entities. fix caching logic --- .../ImportQuickbooksController.php | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/app/Http/Controllers/ImportQuickbooksController.php b/app/Http/Controllers/ImportQuickbooksController.php index 2e655f21050c..7419de3e57cc 100644 --- a/app/Http/Controllers/ImportQuickbooksController.php +++ b/app/Http/Controllers/ImportQuickbooksController.php @@ -86,8 +86,8 @@ class ImportQuickbooksController extends BaseController // Perform the validation $validator = Validator::make(['token' => $request->token ], $rules, $messages); if ($validator->fails()) { - // If validation fails, redirect back with errors and input - return redirect()->back() + return redirect() + ->back() ->withErrors($validator) ->withInput(); } @@ -98,16 +98,16 @@ class ImportQuickbooksController extends BaseController )->only('authorizeQuickbooks'); } - public function onAuthorized(Request $request) { - - $realmId = $request->query('realmId'); - $tokens = $this->service->getOAuth()->accessToken($request->query('code'), $realmId); - $company = $request->input('company'); - Cache::put($company['company_key'], $tokens['access_token'], $tokens['access_token_expires']); - // TODO: save refresh token and realmId in company DB - + public function onAuthorized(Request $request) + { + $realm = $request->query('realmId'); + $company_key = $request->input('company.company_key'); + $company_id = $request->input('company.id'); + $tokens = ($auth_service = $this->service->getOAuth())->accessToken($request->query('code'), $realm); + $auth_service->saveTokens($company_key, ['realm' => $realm] + $tokens); + return response(200); - } + } /** * Determine if the user is authorized to make this request. @@ -121,24 +121,20 @@ class ImportQuickbooksController extends BaseController $authorizationUrl = $auth->getAuthorizationUrl(); $state = $auth->getState(); - Cache::put($state, $token, 90); + Cache::put($state, $token, 190); return redirect()->to($authorizationUrl); } - public function preimport(Request $request) + public function preimport(string $type, string $hash) { // Check for authorization otherwise // Create a reference - $hash = Str::random(32); $data = [ 'hash' => $hash, - 'type' => $request->input('import_type', 'client'), - 'max' => $request->input('max', 100) + 'type' => $type ]; $this->getData($data); - - return $data; } protected function getData($data) { @@ -146,9 +142,11 @@ class ImportQuickbooksController extends BaseController $entity = $this->import_entities[$data['type']]; $cache_name = "{$data['hash']}-{$data['type']}"; // TODO: Get or put cache or DB? - if(! Cache::has($cache_name) ) + if(! Cache::has($cache_name) ) { - $contents = call_user_func([$this->service, "fetch{$entity}s"], $data['max']); + $contents = call_user_func([$this->service, "fetch{$entity}s"]); + if($contents->isEmpty()) return; + Cache::put($cache_name, base64_encode( $contents->toJson()), 600); } } @@ -182,13 +180,18 @@ class ImportQuickbooksController extends BaseController */ public function import(Request $request) { - $this->preimport($request); + $hash = Str::random(32); + foreach($request->input('import_types') as $type) + { + $this->preimport($type, $hash); + } /** @var \App\Models\User $user */ - $user = auth()->user(); + $user = auth()->user() ?? Auth::loginUsingId(60); + $data = ['import_types' => $request->input('import_types') ] + compact('hash'); if (Ninja::isHosted()) { - QuickbooksIngest::dispatch($request->all(), $user->company() ); + QuickbooksIngest::dispatch( $data , $user->company() ); } else { - QuickbooksIngest::dispatch($request->all(), $user->company() ); + QuickbooksIngest::dispatch($data, $user->company() ); } return response()->json(['message' => 'Processing'], 200); From ba40fc54ba24cd622b36f531779721d1bb9aa409 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:54:23 -0400 Subject: [PATCH 50/69] adjust for multiple entities --- app/Jobs/Import/QuickbooksIngest.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Jobs/Import/QuickbooksIngest.php b/app/Jobs/Import/QuickbooksIngest.php index 18889b0d8908..e240623bf9ac 100644 --- a/app/Jobs/Import/QuickbooksIngest.php +++ b/app/Jobs/Import/QuickbooksIngest.php @@ -15,6 +15,8 @@ class QuickbooksIngest implements ShouldQueue use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; protected $engine; + protected $request; + protected $company; /** * Create a new job instance. @@ -32,8 +34,9 @@ class QuickbooksIngest implements ShouldQueue { MultiDB::setDb($this->company->db); set_time_limit(0); - $engine = new Quickbooks($this->request, $this->company); - foreach (['client', 'product', 'invoice', 'payment'] as $entity) { + + $engine = new Quickbooks(['import_type' => 'client', 'hash'=> $this->request['hash'] ], $this->company); + foreach ($this->request['import_types'] as $entity) { $engine->import($entity); } From 2fc4b3d4190c97a662de973790b9251c530edbc9 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:54:41 -0400 Subject: [PATCH 51/69] fix class not found error --- app/Providers/QuickbooksServiceProvider.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Providers/QuickbooksServiceProvider.php b/app/Providers/QuickbooksServiceProvider.php index f6cf2d09368e..0a3777eb3f80 100644 --- a/app/Providers/QuickbooksServiceProvider.php +++ b/app/Providers/QuickbooksServiceProvider.php @@ -6,6 +6,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Facades\Route; use App\Factory\QuickbooksSDKFactory; use Illuminate\Support\ServiceProvider; +use App\Http\Controllers\ImportQuickbooksController; use App\Services\Import\Quickbooks\Service as QuickbooksService; use App\Repositories\Import\Quickcbooks\Contracts\RepositoryInterface; use App\Services\Import\Quickbooks\SdkWrapper as QuickbooksSDKWrapper; From 6d0952231aefc5c8180082abf445fa729e4508ec Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:55:17 -0400 Subject: [PATCH 52/69] get the tokens from store or sdk --- app/Services/Import/Quickbooks/Auth.php | 29 +++++++++++++++++-------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/app/Services/Import/Quickbooks/Auth.php b/app/Services/Import/Quickbooks/Auth.php index 667a0bccacb0..a8f08c7cfdf0 100644 --- a/app/Services/Import/Quickbooks/Auth.php +++ b/app/Services/Import/Quickbooks/Auth.php @@ -3,6 +3,7 @@ namespace App\Services\Import\Quickbooks; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use App\Services\Import\Quickbooks\Repositories\CompanyTokensRepository; use App\Services\Import\QuickBooks\Contracts\SDKInterface as QuickbooksInterface; final class Auth @@ -35,21 +36,31 @@ final class Auth return $this->sdk->getState(); } + public function saveTokens($key, $tokens) + { + $token_store = new CompanyTokensRepository($key); + $token_store->save($tokens); + } + public function getAccessToken() : array { - // TODO: Cache token and - $token = $this->sdk->getAccessToken(); - $access_token = $token->getAccessToken(); - $refresh_token = $token->getRefreshToken(); - $access_token_expires = $token->getAccessTokenExpiresAt(); - $refresh_token_expires = $token->getRefreshTokenExpiresAt(); - //TODO: Cache token object. Update $sdk instance? - return compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires'); + $token_store = new CompanyTokensRepository(); + $tokens = $token_store->get(); + if(empty($tokens)) { + $token = $this->sdk->getAccessToken(); + $access_token = $token->getAccessToken(); + $realm = $token->getRealmID(); + $refresh_token = $token->getRefreshToken(); + $access_token_expires = $token->getAccessTokenExpiresAt(); + $refresh_token_expires = $token->getRefreshTokenExpiresAt(); + $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); + } + + return $tokens; } public function getRefreshToken() : array { - // TODO: Check if token is Cached otherwise fetch a new one and Cache token and expire return $this->getAccessToken(); } } \ No newline at end of file From fe2248d097dcb849c0b8337397e6f3073b87d7bc Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:56:00 -0400 Subject: [PATCH 53/69] repository for token storage logic --- .../Repositories/CompanyTokensRepository.php | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php diff --git a/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php b/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php new file mode 100644 index 000000000000..3e6a18a3fc3a --- /dev/null +++ b/app/Services/Import/Quickbooks/Repositories/CompanyTokensRepository.php @@ -0,0 +1,76 @@ +company_key = $key ?? auth()->user->company()->company_key ?? null; + $this->store_key .= $key; + $this->setCompanyDbByKey(); + } + + public function save(array $tokens) { + $this->updateAccessToken($tokens['access_token'], $tokens['access_token_expires']); + $this->updateRefreshToken($tokens['refresh_token'], $tokens['refresh_token_expires'], $tokens['realm']); + } + + + public function findByCompanyKey(): ?Company + { + return Company::where('company_key', $this->company_key)->first(); + } + + public function setCompanyDbByKey() + { + MultiDB::findAndSetDbByCompanyKey($this->company_key); + } + + public function get() { + return $this->getAccessToken() + $this->getRefreshToken(); + } + + + protected function updateRefreshToken(string $token, string $expires, string $realm) + { + DB::table('companies') + ->where('company_key', $this->company_key) + ->update(['quickbooks_refresh_token' => $token, + 'quickbooks_realm_id' => $realm, + 'quickbooks_refresh_expires' => $expires ]); + } + + protected function updateAccessToken(string $token, string $expires ) + { + + Cache::put([$this->store_key => $token], $expires); + } + + protected function getAccessToken( ) + { + $result = Cache::get($this->store_key); + + return $result ? ['access_token' => $result] : []; + } + + protected function getRefreshToken() + { + $result = (array) DB::table('companies') + ->select('quickbooks_refresh_token', 'quickbooks_realm_id') + ->where('company_key',$this->company_key) + ->where('quickbooks_refresh_expires','>',now()) + ->first(); + + return $result? array_combine(['refresh_token','realm'], array_values($result) ) : []; + } + +} From 6cea02684ccc0758d2bd68589969661d861a500c Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:56:15 -0400 Subject: [PATCH 54/69] add quicbooks columns for companies --- ...8_02_144614_alter_companies_quickbooks.php | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 database/migrations/2024_08_02_144614_alter_companies_quickbooks.php diff --git a/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php new file mode 100644 index 000000000000..0050e38da824 --- /dev/null +++ b/database/migrations/2024_08_02_144614_alter_companies_quickbooks.php @@ -0,0 +1,34 @@ +string('quickbooks_realm_id')->nullable(); + $table->string('quickbooks_refresh_token')->nullable(); + $table->dateTime('quickbooks_refresh_expires')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('companies', function (Blueprint $table) { + $table->dropColumn(['quickbooks_realm_id', 'quickbooks_refresh_token','quickbooks_refresh_expires']); + }); + } +}; From f2755aa1f483bf11b4f33f93474cfcddc22a46d3 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 8 Aug 2024 12:56:36 -0400 Subject: [PATCH 55/69] update tests --- .../ImportQuickbooksControllerTest.php | 61 ++++++++----------- .../Jobs/Import/QuickbooksIngestTest.php | 2 +- 2 files changed, 27 insertions(+), 36 deletions(-) diff --git a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php index 92d6f58f9148..26185371675d 100644 --- a/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php +++ b/tests/Feature/Http/Controllers/ImportQuickbooksControllerTest.php @@ -38,7 +38,7 @@ class ImportQuickbooksControllerTest extends TestCase Session::start(); - app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($this->mock)); + //app()->singleton(QuickbooksInterface::class, fn() => new QuickbooksSDK($this->mock)); } public function testAuthorize(): void @@ -88,47 +88,38 @@ class ImportQuickbooksControllerTest extends TestCase $this->mock->shouldHaveReceived('exchangeAuthorizationCodeForToken')->once()->with(123456,12345678); } - // public function testImport(): void - // { - // Cache::spy(); - // Bus::fake(); - // $this->mock->shouldReceive('Query')->andReturnUsing( - // function($val, $s = 1, $max = 1000) use ($count, $data) { - // if(stristr($val, 'count')) { - // return $count; - // } + public function testImport(): void + { + // Cache::spy(); + //Bus::fake(); + $data = $this->setUpTestData('customers'); + $count = count($data); + $this->mock->shouldReceive('Query')->andReturnUsing( + function($val, $s = 1, $max = 1000) use ($count, $data) { + if(stristr($val, 'count')) { + return $count; + } - // return Arr::take($data,$max); - // } - // ); - // $this->setUpTestData('customers'); - // // Perform the test - // $response = $this->withHeaders([ - // 'X-API-TOKEN' => $this->token, - // ])->post('/api/v1/import/quickbooks/preimport',[ - // 'import_type' => 'client' - // ]); - // $response->assertStatus(200); - // $response = json_decode( $response->getContent()); - // $this->assertNotNull($response->hash); - // $hash = $response->hash; - // $response = $this->withHeaders([ - // 'X-API-TOKEN' => $this->token, - // ])->post('/api/v1/import/quickbooks',[ - // 'import_type' => 'client', - // 'hash' => $response->hash - // ]); - // $response->assertStatus(200); + return Arr::take($data,$max); + } + ); + + // Perform the test + $response = $this->actingAs($this->user)->withHeaders([ + 'X-API-TOKEN' => $this->token, + ])->post('/api/v1/import/quickbooks',[ + 'import_types' => ['client'] + ]); + $response->assertStatus(200); - // Cache::shouldHaveReceived('has')->once()->with("{$hash}-client"); - // Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); - // } + //Cache::shouldHaveReceived('has')->once()->with("{$hash}-client"); + //Bus::assertDispatched(\App\Jobs\Import\QuickbooksIngest::class); + } protected function setUpTestData($file) { $data = json_decode( file_get_contents(base_path("tests/Mock/Quickbooks/Data/$file.json")),true ); - $count = count($data); return $data; } diff --git a/tests/Feature/Jobs/Import/QuickbooksIngestTest.php b/tests/Feature/Jobs/Import/QuickbooksIngestTest.php index a2bbdd4974c0..0882d28c5b37 100644 --- a/tests/Feature/Jobs/Import/QuickbooksIngestTest.php +++ b/tests/Feature/Jobs/Import/QuickbooksIngestTest.php @@ -46,7 +46,7 @@ class QuickbooksIngestTest extends TestCase 'hash' => $hash, 'column_map' => ['client' => ['mapping' => []]], 'skip_header' => true, - 'import_type' => 'quickbooks', + 'import_types' => ['client'], ], $this->company )->handle(); $this->assertTrue(Client::withTrashed()->where(['company_id' => $this->company->id, 'name' => "Freeman Sporting Goods"])->exists()); } From b057908164c7217deed85ac3b0da1a63e2d272dc Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 13 Aug 2024 12:34:45 -0400 Subject: [PATCH 56/69] use repo and sdk for routines instead of service --- app/Factory/QuickbooksSDKFactory.php | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Factory/QuickbooksSDKFactory.php b/app/Factory/QuickbooksSDKFactory.php index 64d3263dfcf5..fcd9ebf70ca1 100644 --- a/app/Factory/QuickbooksSDKFactory.php +++ b/app/Factory/QuickbooksSDKFactory.php @@ -8,7 +8,6 @@ use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use QuickBooksOnline\API\DataService\DataService; -use App\Services\Import\Quickbooks\Auth as QuickbooksService; use App\Services\Import\Quickbooks\Repositories\CompanyTokensRepository; @@ -22,8 +21,8 @@ class QuickbooksSDKFactory { $company = $user->company(); - $tokens = (new CompanyTokensRepository($company->company_key)); - $tokens = array_filter($tokens->get()); + $token_store = (new CompanyTokensRepository($company->company_key)); + $tokens = array_filter($token_store->get()); if(!empty($tokens)) { $keys = ['refreshTokenKey','QBORealmID']; if(array_key_exists('access_token', $tokens)) { @@ -38,7 +37,7 @@ class QuickbooksSDKFactory 'state' => Str::random(12) ]; $sdk = DataService::Configure($config); - if (env('APP_DEBUG')) { + if (env('APP_DEBUG')) { $sdk->setLogLocation(storage_path("logs/quickbooks.log")); $sdk->enableLog(); } @@ -47,9 +46,16 @@ class QuickbooksSDKFactory $sdk->throwExceptionOnError(true); if(array_key_exists('refreshTokenKey', $config) && !array_key_exists('accessTokenKey', $config)) { - $auth = new QuickbooksService($sdk); - $tokens = $auth->refreshTokens(); - $auth->saveTokens($tokens); + $tokens = ($sdk->getOAuth2LoginHelper())->refreshToken(); + $sdk = $sdk->updateOAuth2Token($tokens); + $tokens = ($sdk->getOAuth2LoginHelper())->getAccessToken(); + $access_token = $tokens->getAccessToken(); + $realm = $tokens->getRealmID(); + $refresh_token = $tokens->getRefreshToken(); + $access_token_expires = $tokens->getAccessTokenExpiresAt(); + $refresh_token_expires = $tokens->getRefreshTokenExpiresAt(); + $tokens = compact('access_token', 'refresh_token','access_token_expires', 'refresh_token_expires','realm'); + $token_store->save($tokens); } return $sdk; From 7f9010b9a51b84287375a10cbf9efe2f994507bd Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 13 Aug 2024 12:35:04 -0400 Subject: [PATCH 57/69] fix namespace --- app/Repositories/Import/Quickbooks/CustomerRepository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/Import/Quickbooks/CustomerRepository.php b/app/Repositories/Import/Quickbooks/CustomerRepository.php index dae974bf6411..68b71b75017a 100644 --- a/app/Repositories/Import/Quickbooks/CustomerRepository.php +++ b/app/Repositories/Import/Quickbooks/CustomerRepository.php @@ -1,6 +1,6 @@ Date: Tue, 13 Aug 2024 12:35:50 -0400 Subject: [PATCH 58/69] add product/items logic --- .../Import/Quickbooks/ItemRepository.php | 11 +++++++++++ .../Import/Quickbooks/Transformers/Transformer.php | 12 ++++++++++++ app/Services/Import/Quickbooks/Service.php | 11 +++++++++++ 3 files changed, 34 insertions(+) create mode 100644 app/Repositories/Import/Quickbooks/ItemRepository.php diff --git a/app/Repositories/Import/Quickbooks/ItemRepository.php b/app/Repositories/Import/Quickbooks/ItemRepository.php new file mode 100644 index 000000000000..caa1b69355dd --- /dev/null +++ b/app/Repositories/Import/Quickbooks/ItemRepository.php @@ -0,0 +1,11 @@ +transformation($items, [ + 'Name', + 'Description', + 'PurchaseCost', + 'UnitPrice', + 'QtyOnHand', + 'MetaData' + ]); + } + protected function transformation(array $items, array $keys) : Collection { return collect($items)->select($keys); diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php index e4db2879e5b5..4001ea0c0c4d 100644 --- a/app/Services/Import/Quickbooks/Service.php +++ b/app/Services/Import/Quickbooks/Service.php @@ -40,6 +40,17 @@ final class Service return $this->transformer->transform($this->fetchRecords( 'Invoice', $max), 'Invoice'); } + + /** + * fetch QuickBooks product records + * @param int $max The maximum records to fetch. Default 100 + * @return Illuminate\Support\Collection; + */ + public function fetchItems(int $max = 100): Collection + { + return $this->fetchRecords('Item', $max) ; + } + protected function fetchRecords(string $entity, $max = 100) : Collection { return (self::RepositoryFactory($entity))->get($max); } From 4d51bbc9a2f5be4f62878f21c74d3d142d5f8e1d Mon Sep 17 00:00:00 2001 From: karneaud Date: Tue, 13 Aug 2024 12:36:12 -0400 Subject: [PATCH 59/69] fix namespace --- app/Repositories/Import/Quickbooks/Repository.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Repositories/Import/Quickbooks/Repository.php b/app/Repositories/Import/Quickbooks/Repository.php index f5001ba3a588..bcbf85ccff96 100644 --- a/app/Repositories/Import/Quickbooks/Repository.php +++ b/app/Repositories/Import/Quickbooks/Repository.php @@ -1,6 +1,6 @@ Date: Thu, 15 Aug 2024 20:52:22 -0400 Subject: [PATCH 60/69] clean line items. test for invoice --- app/Import/Providers/Quickbooks.php | 41 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php index ac938e6f5436..60c64e039609 100644 --- a/app/Import/Providers/Quickbooks.php +++ b/app/Import/Providers/Quickbooks.php @@ -11,6 +11,7 @@ namespace App\Import\Providers; +use App\Models\Invoice; use App\Factory\ProductFactory; use App\Factory\ClientFactory; use App\Factory\InvoiceFactory; @@ -148,6 +149,9 @@ class Quickbooks extends BaseImport $invoice_data = $invoice_transformer->transform($raw_invoice); $invoice_data['user_id'] = $this->company->owner()->id; $invoice_data['line_items'] = (array) $invoice_data['line_items']; + $invoice_data['line_items'] = $this->cleanItems( + $invoice_data['line_items'] ?? [] + ); if ( empty($invoice_data['client_id']) && @@ -175,24 +179,27 @@ class Quickbooks extends BaseImport 'error' => $validator->errors()->all(), ]; } else { - $invoice = InvoiceFactory::create( - $this->company->id, - $this->company->owner()->id - ); - $invoice->mergeFillable(['partial','amount','balance','line_items']); - if (! empty($invoice_data['status_id'])) { - $invoice->status_id = $invoice_data['status_id']; + if(!Invoice::where('number',$invoice_data['number'])->get()->first()) + { + $invoice = InvoiceFactory::create( + $this->company->id, + $this->company->owner()->id + ); + $invoice->mergeFillable(['partial','amount','balance','line_items']); + if (! empty($invoice_data['status_id'])) { + $invoice->status_id = $invoice_data['status_id']; + } + + $saveable_invoice_data = $invoice_data; + if(array_key_exists('payments', $saveable_invoice_data)) { + unset($saveable_invoice_data['payments']); + } + + $invoice->fill($saveable_invoice_data); + $invoice->save(); + $count++; + } - - $saveable_invoice_data = $invoice_data; - if(array_key_exists('payments', $saveable_invoice_data)) { - unset($saveable_invoice_data['payments']); - } - - $invoice->fill($saveable_invoice_data); - $invoice->save(); - $count++; - // $this->actionInvoiceStatus( // $invoice, // $invoice_data, From 193e35e21b331c6b3d7d322454ca8d47a0a631d3 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 15 Aug 2024 20:53:11 -0400 Subject: [PATCH 61/69] type cast values. check has client --- .../Quickbooks/InvoiceTransformer.php | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php index 9667c87d312d..0d801f95344a 100644 --- a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -59,9 +59,10 @@ class InvoiceTransformer extends BaseTransformer return array_map(function ($item) { return [ 'description' => $this->getString($item,'Description'), - 'quantity' => $this->getString($item,'SalesItemLineDetail.Qty'), - 'unit_price' =>$this->getString($item,'SalesItemLineDetail.UnitPrice'), - 'amount' => $this->getString($item,'Amount') + 'product_key' => $this->getString($item,'Description'), + 'quantity' => (int) $this->getString($item,'SalesItemLineDetail.Qty'), + 'unit_price' =>(float) $this->getString($item,'SalesItemLineDetail.UnitPrice'), + 'amount' => (float) $this->getString($item,'Amount') ]; }, array_filter($this->getString($data,'Line'), function ($item) { return $this->getString($item,'DetailType') !== 'SubTotalLineDetail'; @@ -122,13 +123,18 @@ class InvoiceTransformer extends BaseTransformer $address = $has_company? $bill_address->Line4 : $bill_address->Line3; $address_1 = substr($address, 0, stripos($address,',')); $address =array_filter( [$address_1] + (explode(' ', substr($address, stripos($address,",") + 1 )))); + $client_id = null; $client = [ "CompanyName" => $has_company? $bill_address->Line2 : $bill_address->Line1, - "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], $address) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ], + "BillAddr" => array_combine(['City','CountrySubDivisionCode','PostalCode'], array_pad($address,3,'N/A') ) + ['Line1' => $has_company? $bill_address->Line3 : $bill_address->Line2 ], "ShipAddr" => $ship_address ] + $customer + ['PrimaryEmailAddr' => ['Address' => $this->getString($data, 'BillEmail.Address') ]]; - $client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address')); + if($this->hasClient($client['CompanyName'])) + { + $client_id = $this->getClient($client['CompanyName'],$this->getString($client, 'PrimaryEmailAddr.Address')); + } + return ['client'=> (new ClientTransformer($this->company))->transform($client), 'client_id'=> $client_id ]; } From 7e8d82384a19a32479718c3529a3044193cf62b8 Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 15 Aug 2024 20:53:28 -0400 Subject: [PATCH 62/69] add invoice respository --- .../Import/Quickbooks/InvoiceRepository.php | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/Repositories/Import/Quickbooks/InvoiceRepository.php diff --git a/app/Repositories/Import/Quickbooks/InvoiceRepository.php b/app/Repositories/Import/Quickbooks/InvoiceRepository.php new file mode 100644 index 000000000000..6db80b810474 --- /dev/null +++ b/app/Repositories/Import/Quickbooks/InvoiceRepository.php @@ -0,0 +1,10 @@ + Date: Thu, 15 Aug 2024 20:54:00 -0400 Subject: [PATCH 63/69] add invoice transformer method --- .../Quickbooks/Transformers/Transformer.php | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/app/Repositories/Import/Quickbooks/Transformers/Transformer.php b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php index dfdc1f595317..723a0f380403 100644 --- a/app/Repositories/Import/Quickbooks/Transformers/Transformer.php +++ b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php @@ -13,11 +13,6 @@ class Transformer return call_user_func([$this, $method], $items); } - protected function transformInvoices(array $items): Collection - { - return $this->transformation($items, []); - } - protected function transformCustomers(array $items): Collection { return $this->transformation($items, [ @@ -34,6 +29,24 @@ class Transformer ]); } + protected function transformInvoices(array $items): Collection + { + return $this->transformation($items, [ + "TotalAmt", + "Line", + "DueDate", + "Deposit", + "Balance", + "CustomerMemo", + "DocNumber", + "CustomerRef", + "BillEmail", + 'MetaData', + "BillAddr", + "ShipAddr" + ]); + } + protected function transformItems(array $items): Collection { return $this->transformation($items, [ From fda7c693a3e20e83974727494af1916fe1b58cdf Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 15 Aug 2024 20:54:18 -0400 Subject: [PATCH 64/69] modify method --- app/Services/Import/Quickbooks/Service.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/Import/Quickbooks/Service.php b/app/Services/Import/Quickbooks/Service.php index 4001ea0c0c4d..39d2091cf1b7 100644 --- a/app/Services/Import/Quickbooks/Service.php +++ b/app/Services/Import/Quickbooks/Service.php @@ -37,7 +37,7 @@ final class Service */ public function fetchInvoices(int $max = 100): Collection { - return $this->transformer->transform($this->fetchRecords( 'Invoice', $max), 'Invoice'); + return $this->fetchRecords('Invoice', $max) ; } From 3e0c6f29865278460be2b67547789ef511c8a8dc Mon Sep 17 00:00:00 2001 From: karneaud Date: Thu, 15 Aug 2024 21:39:57 -0400 Subject: [PATCH 65/69] updae quickbooks sdk --- composer.json | 6 +++- composer.lock | 89 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 84 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index b5257d0bd0f8..f4547783fcab 100644 --- a/composer.json +++ b/composer.json @@ -86,7 +86,7 @@ "predis/predis": "^2", "psr/http-message": "^1.0", "pusher/pusher-php-server": "^7.2", - "quickbooks/v3-php-sdk": "^6.1", + "quickbooks/v3-php-sdk": "6.1.4-alpha", "razorpay/razorpay": "2.*", "sentry/sentry-laravel": "^4", "setasign/fpdf": "^1.8", @@ -200,6 +200,10 @@ { "type": "vcs", "url": "https://github.com/turbo124/snappdf" + }, + { + "type":"vcs", + "url":"https://github.com/karneaud/QuickBooks-V3-PHP-SDK.git" } ], "minimum-stability": "dev", diff --git a/composer.lock b/composer.lock index 425a1963ed95..9d754937d8e3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f137c4c97faf7721366179f36c1ca72a", + "content-hash": "9e7ea46cfef2848f4eac13cc9c0c679a", "packages": [ { "name": "adrienrn/php-mimetyper", @@ -4048,6 +4048,68 @@ }, "time": "2024-07-22T02:40:27+00:00" }, + { + "name": "invoiceninja/inspector", + "version": "v3.0", + "source": { + "type": "git", + "url": "https://github.com/invoiceninja/inspector.git", + "reference": "29bc1ee7ae9d967287ecbd3485a2fee41a13e65f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/invoiceninja/inspector/zipball/29bc1ee7ae9d967287ecbd3485a2fee41a13e65f", + "reference": "29bc1ee7ae9d967287ecbd3485a2fee41a13e65f", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^4.0", + "illuminate/support": "^11.0", + "php": "^8.2" + }, + "require-dev": { + "orchestra/testbench": "^9.1", + "phpunit/phpunit": "^11.1" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "InvoiceNinja\\Inspector\\InspectorServiceProvider" + ], + "aliases": { + "Inspector": "InvoiceNinja\\Inspector\\InspectorFacade" + } + } + }, + "autoload": { + "psr-4": { + "InvoiceNinja\\Inspector\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Beganović", + "email": "benjamin.beganovic4@outlook.com", + "role": "Developer" + } + ], + "description": "Simplified database records management", + "homepage": "https://github.com/invoiceninja/inspector", + "keywords": [ + "inspector", + "invoiceninja" + ], + "support": { + "issues": "https://github.com/invoiceninja/inspector/issues", + "source": "https://github.com/invoiceninja/inspector/tree/v3.0" + }, + "time": "2024-06-04T12:31:47+00:00" + }, { "name": "invoiceninja/ubl_invoice", "version": "v2.2.2", @@ -9090,16 +9152,16 @@ }, { "name": "quickbooks/v3-php-sdk", - "version": "v6.1.3", + "version": "v6.1.4-alpha", "source": { "type": "git", - "url": "https://github.com/intuit/QuickBooks-V3-PHP-SDK.git", - "reference": "2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2" + "url": "https://github.com/karneaud/QuickBooks-V3-PHP-SDK.git", + "reference": "89ff2b6dcfc94634cf5806cacda1286a6898249f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/intuit/QuickBooks-V3-PHP-SDK/zipball/2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2", - "reference": "2e7be89a9b2e846ec8c8fdceb4c9bf102317f3a2", + "url": "https://api.github.com/repos/karneaud/QuickBooks-V3-PHP-SDK/zipball/89ff2b6dcfc94634cf5806cacda1286a6898249f", + "reference": "89ff2b6dcfc94634cf5806cacda1286a6898249f", "shasum": "" }, "require": { @@ -9122,7 +9184,15 @@ "QuickBooksOnline\\API\\": "src/" } }, - "notification-url": "https://packagist.org/downloads/", + "archive": { + "exclude": [ + "/docs", + "/src/Utility.Test", + "/src/XSD2PHP/docs", + "/src/XSD2PHP/test", + "/test" + ] + }, "license": [ "Apache-2.0" ], @@ -9142,10 +9212,9 @@ "smallbusiness" ], "support": { - "issues": "https://github.com/intuit/QuickBooks-V3-PHP-SDK/issues", - "source": "https://github.com/intuit/QuickBooks-V3-PHP-SDK/tree/v6.1.3" + "source": "https://github.com/karneaud/QuickBooks-V3-PHP-SDK/tree/v6.1.4-alpha" }, - "time": "2024-05-28T11:13:18+00:00" + "time": "2024-08-16T01:21:19+00:00" }, { "name": "ralouphie/getallheaders", From 43b95125f0832dcf99de37f57a54941c28bc45c7 Mon Sep 17 00:00:00 2001 From: karneaud Date: Sat, 17 Aug 2024 11:24:08 -0400 Subject: [PATCH 66/69] add logic for import payment entity --- app/Import/Providers/Quickbooks.php | 31 ++++-- .../Quickbooks/PaymentTransformer.php | 100 ++++++++++++++++++ .../Import/Quickbooks/PaymentRepository.php | 10 ++ app/Services/Import/Quickbooks/Service.php | 9 ++ 4 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 app/Import/Transformer/Quickbooks/PaymentTransformer.php create mode 100644 app/Repositories/Import/Quickbooks/PaymentRepository.php diff --git a/app/Import/Providers/Quickbooks.php b/app/Import/Providers/Quickbooks.php index 60c64e039609..5bd968443b5c 100644 --- a/app/Import/Providers/Quickbooks.php +++ b/app/Import/Providers/Quickbooks.php @@ -15,16 +15,20 @@ use App\Models\Invoice; use App\Factory\ProductFactory; use App\Factory\ClientFactory; use App\Factory\InvoiceFactory; +use App\Factory\PaymentFactory; use Illuminate\Support\Facades\Cache; -use App\Http\Requests\Client\StoreClientRequest; -use App\Http\Requests\Product\StoreProductRequest; -use App\Http\Requests\Invoice\StoreInvoiceRequest; -use App\Import\Transformer\Quickbooks\ClientTransformer; -use App\Import\Transformer\Quickbooks\InvoiceTransformer; -use App\Import\Transformer\Quickbooks\ProductTransformer; use App\Repositories\ClientRepository; use App\Repositories\InvoiceRepository; use App\Repositories\ProductRepository; +use App\Repositories\PaymentRepository; +use App\Http\Requests\Client\StoreClientRequest; +use App\Http\Requests\Product\StoreProductRequest; +use App\Http\Requests\Invoice\StoreInvoiceRequest; +use App\Http\Requests\Payment\StorePaymentRequest; +use App\Import\Transformer\Quickbooks\ClientTransformer; +use App\Import\Transformer\Quickbooks\InvoiceTransformer; +use App\Import\Transformer\Quickbooks\ProductTransformer; +use App\Import\Transformer\Quickbooks\PaymentTransformer; class Quickbooks extends BaseImport { @@ -98,7 +102,22 @@ class Quickbooks extends BaseImport public function payment() { + $entity_type = 'payment'; + $data = $this->getData($entity_type); + if (empty($data)) { + $this->entity_count['payments'] = 0; + return; + } + + $this->request_name = StorePaymentRequest::class; + $this->repository_name = PaymentRepository::class; + $this->factory_name = PaymentFactory::class; + $this->repository = app()->make($this->repository_name); + $this->repository->import_mode = true; + $this->transformer = new PaymentTransformer($this->company); + $count = $this->ingest($data, $entity_type); + $this->entity_count['payments'] = $count; } public function invoice() diff --git a/app/Import/Transformer/Quickbooks/PaymentTransformer.php b/app/Import/Transformer/Quickbooks/PaymentTransformer.php new file mode 100644 index 000000000000..ff4b4f819b48 --- /dev/null +++ b/app/Import/Transformer/Quickbooks/PaymentTransformer.php @@ -0,0 +1,100 @@ + "PaymentRefNum", + 'amount' => "TotalAmt", + "client_id" => "CustomerRef", + "currency_id" => "CurrencyRef", + 'date' => "TxnDate", + "invoices" => "Line", + 'private_notes' => "PrivateNote", + 'created_at' => "CreateTime", + 'updated_at' => "LastUpdatedTime" + ]; + + public function __construct($company) + { + parent::__construct($company); + + $this->model = new Model; + } + + public function getTotalAmt($data, $field = null) { + return (float) $this->getString($data, $field); + } + + public function getTxnDate($data, $field = null) + { + return $this->parseDateOrNull($data, $field); + } + + public function getCustomerRef($data, $field = null ) + { + return $this->getClient($this->getString($data, 'CustomerRef.name'),null); + } + + public function getCurrencyRef($data, $field = null) + { + return $this->getCurrencyByCode($data['CurrencyRef'], 'value'); + } + + public function getLine($data, $field = null) + { + $invoices = []; + $invoice = $this->getString($data,'Line.LinkedTxn.TxnType'); + nlog(print_r([$invoice, $this->getString($data,'Line.LinkedTxn')], true)); + if(is_null($invoice) || $invoice !== 'Invoice') return $invoices; + if( is_null( ($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value')))) ) return $invoices; + + return [[ + 'amount' => (float) $this->getString($data, 'Line.Amount'), + 'invoice_id' => $invoice_id + ]]; + } + + /** + * @param $invoice_number + * + * @return int|null + */ + public function getInvoiceId($invoice_number) + { + $invoice = Invoice::query()->where('company_id', $this->company->id) + ->where('is_deleted', false) + ->where("number", "LIKE", + "%-$invoice_number%", + ) + ->first(); + + return $invoice ? $invoice->id : null; + } + +} diff --git a/app/Repositories/Import/Quickbooks/PaymentRepository.php b/app/Repositories/Import/Quickbooks/PaymentRepository.php new file mode 100644 index 000000000000..86995fc18e34 --- /dev/null +++ b/app/Repositories/Import/Quickbooks/PaymentRepository.php @@ -0,0 +1,10 @@ +fetchRecords('Invoice', $max) ; } + /** + * fetch QuickBooks payment records + * @param int $max The maximum records to fetch. Default 100 + * @return Illuminate\Support\Collection; + */ + public function fetchPayments(int $max = 100): Collection + { + return $this->fetchRecords('Payment', $max) ; + } /** * fetch QuickBooks product records From 73e596d29c8abcfccee9c50ed2776880113b9a25 Mon Sep 17 00:00:00 2001 From: karneaud Date: Sat, 17 Aug 2024 11:24:53 -0400 Subject: [PATCH 67/69] group common methods into trait --- .../Quickbooks/ClientTransformer.php | 29 +++++---- .../Transformer/Quickbooks/CommonTrait.php | 36 ++++++++++ .../Quickbooks/InvoiceTransformer.php | 65 ++++++++++++------- .../Quickbooks/ProductTransformer.php | 47 +++----------- 4 files changed, 105 insertions(+), 72 deletions(-) create mode 100644 app/Import/Transformer/Quickbooks/CommonTrait.php diff --git a/app/Import/Transformer/Quickbooks/ClientTransformer.php b/app/Import/Transformer/Quickbooks/ClientTransformer.php index 313476307847..8cfbd94befe7 100644 --- a/app/Import/Transformer/Quickbooks/ClientTransformer.php +++ b/app/Import/Transformer/Quickbooks/ClientTransformer.php @@ -12,18 +12,23 @@ namespace App\Import\Transformer\Quickbooks; +use App\Import\Transformer\Quickbooks\CommonTrait; use App\Import\Transformer\BaseTransformer; use App\Models\Client as Model; use App\Models\ClientContact; use App\Import\ImportException; use Illuminate\Support\Str; -use Illuminate\Support\Arr; /** * Class ClientTransformer. */ class ClientTransformer extends BaseTransformer { + + use CommonTrait { + transform as preTransform; + } + private $fillable = [ 'name' => 'CompanyName', 'phone' => 'PrimaryPhone.FreeFormNumber', @@ -40,6 +45,14 @@ class ClientTransformer extends BaseTransformer 'public_notes' => 'Notes' ]; + public function __construct($company) + { + parent::__construct($company); + + $this->model = new Model; + } + + /** * Transforms a Customer array into a ClientContact model. * @@ -54,18 +67,10 @@ class ClientTransformer extends BaseTransformer return false; } - foreach($this->fillable as $key => $field) { - $transformed_data[$key] = method_exists($this, $method = sprintf("get%s", str_replace(".","",$field)) )? call_user_func([$this, $method],$data,$field) : $this->getString($data, $field); - } + $transformed_data = $this->preTransform($data); + $transformed_data['contacts'][0] = $this->getContacts($data)->toArray()+['company_id' => $this->company->id ]; - $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data); - $transformed_data->contacts[0] = $this->getContacts($data)->toArray()+['company_id' => $this->company->id ]; - return $transformed_data->toArray() + ['company_id' => $this->company->id ] ; - } - - public function getString($data, $field) - { - return Arr::get($data, $field); + return $transformed_data; } protected function getContacts($data) { diff --git a/app/Import/Transformer/Quickbooks/CommonTrait.php b/app/Import/Transformer/Quickbooks/CommonTrait.php new file mode 100644 index 000000000000..d618f94120ed --- /dev/null +++ b/app/Import/Transformer/Quickbooks/CommonTrait.php @@ -0,0 +1,36 @@ +parseDateOrNull($data, 'MetaData.CreateTime'); + } + + public function getLastUpdatedTime($data, $field = null) + { + return $this->parseDateOrNull($data,'MetaData.LastUpdatedTime'); + } + + public function transform($data) + { + $transformed = []; + + foreach ($this->fillable as $key => $field) { + $transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field)); + } + + return $this->model->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + ['company_id' => $this->company->id ] ; + } + +} \ No newline at end of file diff --git a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php index 0d801f95344a..fe2f998c1c4a 100644 --- a/app/Import/Transformer/Quickbooks/InvoiceTransformer.php +++ b/app/Import/Transformer/Quickbooks/InvoiceTransformer.php @@ -17,6 +17,7 @@ use App\Import\ImportException; use App\DataMapper\InvoiceItem; use App\Models\Invoice as Model; use App\Import\Transformer\BaseTransformer; +use App\Import\Transformer\Quickbooks\CommonTrait; use App\Import\Transformer\Quickbooks\ClientTransformer; /** @@ -24,7 +25,9 @@ use App\Import\Transformer\Quickbooks\ClientTransformer; */ class InvoiceTransformer extends BaseTransformer { - + use CommonTrait { + transform as preTransform; + } private $fillable = [ 'amount' => "TotalAmt", @@ -32,21 +35,30 @@ class InvoiceTransformer extends BaseTransformer 'due_date' => "DueDate", 'partial' => "Deposit", 'balance' => "Balance", - 'comments' => "CustomerMemo", + 'private_notes' => "CustomerMemo", + 'public_notes' => "CustomerMemo", 'number' => "DocNumber", 'created_at' => "CreateTime", - 'updated_at' => "LastUpdatedTime" + 'updated_at' => "LastUpdatedTime", + 'payments' => 'LinkedTxn', + 'status_id' => 'InvoiceStatus', ]; + public function __construct($company) + { + parent::__construct($company); + + $this->model = new Model; + } + + public function getInvoiceStatus($data) + { + return Invoice::STATUS_SENT; + } + public function transform($data) { - $transformed = []; - - foreach ($this->fillable as $key => $field) { - $transformed[$key] = is_null((($v = $this->getString($data, $field))))? null : (method_exists($this, ($method = "get{$field}")) ? call_user_func([$this, $method], $data, $field ) : $this->getString($data,$field)); - } - - return (new Model)->fillable(array_keys($this->fillable))->fill($transformed)->toArray() + $this->getInvoiceClient($data); + return $this->preTransform($data) + $this->getInvoiceClient($data); } public function getTotalAmt($data) @@ -61,8 +73,11 @@ class InvoiceTransformer extends BaseTransformer 'description' => $this->getString($item,'Description'), 'product_key' => $this->getString($item,'Description'), 'quantity' => (int) $this->getString($item,'SalesItemLineDetail.Qty'), - 'unit_price' =>(float) $this->getString($item,'SalesItemLineDetail.UnitPrice'), - 'amount' => (float) $this->getString($item,'Amount') + 'unit_price' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'), + 'line_total' => (double) $this->getString($item,'Amount'), + 'cost' =>(double) $this->getString($item,'SalesItemLineDetail.UnitPrice'), + 'product_cost' => (double) $this->getString($item,'SalesItemLineDetail.UnitPrice'), + 'tax_amount' => (double) $this->getString($item,'TxnTaxDetail.TotalTax'), ]; }, array_filter($this->getString($data,'Line'), function ($item) { return $this->getString($item,'DetailType') !== 'SubTotalLineDetail'; @@ -139,10 +154,6 @@ class InvoiceTransformer extends BaseTransformer return ['client'=> (new ClientTransformer($this->company))->transform($client), 'client_id'=> $client_id ]; } - public function getString($data,$field) { - return Arr::get($data,$field); - } - public function getDueDate($data) { return $this->parseDateOrNull($data, 'DueDate'); @@ -150,12 +161,12 @@ class InvoiceTransformer extends BaseTransformer public function getDeposit($data) { - return (float) $this->getString($data,'Deposit'); + return (double) $this->getString($data,'Deposit'); } public function getBalance($data) { - return (float) $this->getString($data,'Balance'); + return (double) $this->getString($data,'Balance'); } public function getCustomerMemo($data) @@ -163,13 +174,23 @@ class InvoiceTransformer extends BaseTransformer return $this->getString($data,'CustomerMemo.value'); } - public function getCreateTime($data) + public function getDocNumber($data, $field = null) { - return $this->parseDateOrNull($data['MetaData'], 'CreateTime'); + return sprintf("%s-%s", + $this->getString($data, 'DocNumber'), + $this->getString($data, 'Id.value') + ); } - public function getLastUpdatedTime($data) + public function getLinkedTxn($data) { - return $this->parseDateOrNull($data['MetaData'],'LastUpdatedTime'); + $payments = $this->getString($data,'LinkedTxn'); + if(empty($payments)) return []; + + return [[ + 'amount' => $this->getTotalAmt($data), + 'date' => $this->parseDateOrNull($data, 'TxnDate') + ]]; + } } diff --git a/app/Import/Transformer/Quickbooks/ProductTransformer.php b/app/Import/Transformer/Quickbooks/ProductTransformer.php index f86a49eca1ed..af7b9342ab91 100644 --- a/app/Import/Transformer/Quickbooks/ProductTransformer.php +++ b/app/Import/Transformer/Quickbooks/ProductTransformer.php @@ -12,11 +12,10 @@ namespace App\Import\Transformer\Quickbooks; +use App\Import\Transformer\Quickbooks\CommonTrait; use App\Import\Transformer\BaseTransformer; use App\Models\Product as Model; use App\Import\ImportException; -use Illuminate\Support\Str; -use Illuminate\Support\Arr; /** * Class ProductTransformer. @@ -24,6 +23,8 @@ use Illuminate\Support\Arr; class ProductTransformer extends BaseTransformer { + use CommonTrait; + protected $fillable = [ 'product_key' => 'Name', 'notes' => 'Description', @@ -34,33 +35,13 @@ class ProductTransformer extends BaseTransformer 'created_at' => 'CreateTime', 'updated_at' => 'LastUpdatedTime', ]; - /** - * Transforms the JSON data into a ProductModel object. - * - * @param array $data - * @return ProductModel - */ - /** - * Transforms a Customer array into a Product model. - * - * @param array $data - * @return array|bool - */ - public function transform($data) - { - $transformed_data = []; - foreach($this->fillable as $key => $field) { - $transformed_data[$key] = method_exists($this, $method = sprintf("get%s", str_replace(".","",$field)) )? call_user_func([$this, $method],$data,$field) : $this->getString($data, $field); - } - - $transformed_data = (new Model)->fillable(array_keys($this->fillable))->fill($transformed_data); - - return $transformed_data->toArray() + ['company_id' => $this->company->id ] ; - } - public function getString($data, $field) + + public function __construct($company) { - return Arr::get($data, $field); + parent::__construct($company); + + $this->model = new Model; } public function getQtyOnHand($data, $field = null) { @@ -68,21 +49,11 @@ class ProductTransformer extends BaseTransformer } public function getPurchaseCost($data, $field = null) { - return (float) $this->getString($data, $field); + return (double) $this->getString($data, $field); } public function getUnitPrice($data, $field = null) { return (float) $this->getString($data, $field); } - - public function getCreateTime($data, $field = null) - { - return $this->parseDateOrNull($data['MetaData'], 'CreateTime'); - } - - public function getLastUpdatedTime($data, $field = null) - { - return $this->parseDateOrNull($data['MetaData'],'LastUpdatedTime'); - } } From 72938bf5ada4ba12510f35055c753c0892f0f8a7 Mon Sep 17 00:00:00 2001 From: karneaud Date: Sat, 17 Aug 2024 11:25:17 -0400 Subject: [PATCH 68/69] update tranformation keys --- .../Quickbooks/Transformers/Transformer.php | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/app/Repositories/Import/Quickbooks/Transformers/Transformer.php b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php index 723a0f380403..816b9e463cde 100644 --- a/app/Repositories/Import/Quickbooks/Transformers/Transformer.php +++ b/app/Repositories/Import/Quickbooks/Transformers/Transformer.php @@ -43,7 +43,26 @@ class Transformer "BillEmail", 'MetaData', "BillAddr", - "ShipAddr" + "ShipAddr", + "LinkedTxn", + "Id", + "CurrencyRef", + "TxnTaxDetail", + "TxnDate" + ]); + } + + protected function transformPayments(array $items): Collection + { + return $this->transformation($items, [ + "PaymentRefNum", + "TotalAmt", + "CustomerRef", + "CurrencyRef", + "TxnDate", + "Line", + "PrivateNote", + "MetaData" ]); } From 979b8ddcb2dd26b4d9a676fa553ac0dcec6933d6 Mon Sep 17 00:00:00 2001 From: karneaud Date: Sat, 17 Aug 2024 11:51:01 -0400 Subject: [PATCH 69/69] remove logger --- app/Import/Transformer/Quickbooks/PaymentTransformer.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Import/Transformer/Quickbooks/PaymentTransformer.php b/app/Import/Transformer/Quickbooks/PaymentTransformer.php index ff4b4f819b48..5156d0fa61d0 100644 --- a/app/Import/Transformer/Quickbooks/PaymentTransformer.php +++ b/app/Import/Transformer/Quickbooks/PaymentTransformer.php @@ -70,7 +70,6 @@ class PaymentTransformer extends BaseTransformer { $invoices = []; $invoice = $this->getString($data,'Line.LinkedTxn.TxnType'); - nlog(print_r([$invoice, $this->getString($data,'Line.LinkedTxn')], true)); if(is_null($invoice) || $invoice !== 'Invoice') return $invoices; if( is_null( ($invoice_id = $this->getInvoiceId($this->getString($data, 'Line.LinkedTxn.TxnId.value')))) ) return $invoices;