From 379479589fb2b92f55d9d4ba2bac03e0294378b2 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Tue, 24 Mar 2026 12:59:26 +1100 Subject: [PATCH] Update .gitignore --- .gitignore | 14 +- .../Peppol/Stylesheets/CEN-EN16931-UBL.xslt | 8 +- .../Stylesheets/PEPPOL-EN16931-UBL.xslt | 2 +- composer.json | 2 +- composer.lock | 72 +++--- lang/en/texts.php | 1 + tests/Feature/Export/LocationExportTest.php | 229 ++++++++---------- 7 files changed, 153 insertions(+), 175 deletions(-) diff --git a/.gitignore b/.gitignore index 0b3c53481c..9948f3f4a3 100644 --- a/.gitignore +++ b/.gitignore @@ -30,14 +30,22 @@ _ide_helper.php # Ignore local migrations storage/migrations -nbproject .php_cs.cache public/test.pdf public/storage/test.pdf -/Modules +Modules/ _ide_helper_models.php _ide_helper.php /composer.phar .tx/ -.phpunit.cache \ No newline at end of file +.phpunit.cache +CLAUDE.md +boost.json +AGENTS.md +junie/ +.agents/ +.claude/ +.codex/ +.cursor/ +.mcp.json \ No newline at end of file diff --git a/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/CEN-EN16931-UBL.xslt b/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/CEN-EN16931-UBL.xslt index 657314f62f..40466d6010 100644 --- a/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/CEN-EN16931-UBL.xslt +++ b/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/CEN-EN16931-UBL.xslt @@ -14966,9 +14966,9 @@ - + - + BR-CL-10 fatal @@ -15262,9 +15262,9 @@ - + - + BR-CL-25 fatal diff --git a/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/PEPPOL-EN16931-UBL.xslt b/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/PEPPOL-EN16931-UBL.xslt index d06d16451c..8fc6e37f4a 100644 --- a/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/PEPPOL-EN16931-UBL.xslt +++ b/app/Services/EDocument/Standards/Validation/Peppol/Stylesheets/PEPPOL-EN16931-UBL.xslt @@ -1448,7 +1448,7 @@ - + diff --git a/composer.json b/composer.json index 065a403ab8..3f9ec52c97 100644 --- a/composer.json +++ b/composer.json @@ -127,7 +127,7 @@ "friendsofphp/php-cs-fixer": "^3.14", "laracasts/cypress": "^3.0", "larastan/larastan": "^2|^3", - "laravel/boost": "^1.8", + "laravel/boost": "^2", "mockery/mockery": "^1.4.4", "nunomaduro/collision": "^8.1", "phpstan/phpstan": "^2.0", diff --git a/composer.lock b/composer.lock index 9916926fb1..5d06115c71 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": "8a50c2fe501dee5e823dc1d72ad78fc4", + "content-hash": "deac431061a71a45433f6202449167bc", "packages": [ { "name": "apimatic/core", @@ -18749,33 +18749,33 @@ }, { "name": "laravel/boost", - "version": "v1.8.12", + "version": "v2.3.4", "source": { "type": "git", "url": "https://github.com/laravel/boost.git", - "reference": "5a0bf68e48e6d159182859f9ed6e404313103309" + "reference": "9e3dd5f05b59394e463e78853067dc36c63a0394" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/boost/zipball/5a0bf68e48e6d159182859f9ed6e404313103309", - "reference": "5a0bf68e48e6d159182859f9ed6e404313103309", + "url": "https://api.github.com/repos/laravel/boost/zipball/9e3dd5f05b59394e463e78853067dc36c63a0394", + "reference": "9e3dd5f05b59394e463e78853067dc36c63a0394", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.9", - "illuminate/console": "^10.49.0|^11.45.3|^12.41.1", - "illuminate/contracts": "^10.49.0|^11.45.3|^12.41.1", - "illuminate/routing": "^10.49.0|^11.45.3|^12.41.1", - "illuminate/support": "^10.49.0|^11.45.3|^12.41.1", - "laravel/mcp": "^0.5.1", - "laravel/prompts": "0.1.25|^0.3.6", - "laravel/roster": "^0.2.9", - "php": "^8.1" + "illuminate/console": "^11.45.3|^12.41.1|^13.0", + "illuminate/contracts": "^11.45.3|^12.41.1|^13.0", + "illuminate/routing": "^11.45.3|^12.41.1|^13.0", + "illuminate/support": "^11.45.3|^12.41.1|^13.0", + "laravel/mcp": "^0.5.1|^0.6.0", + "laravel/prompts": "^0.3.10", + "laravel/roster": "^0.5.0", + "php": "^8.2" }, "require-dev": { - "laravel/pint": "^1.20.0", + "laravel/pint": "^1.27.0", "mockery/mockery": "^1.6.12", - "orchestra/testbench": "^8.36.0|^9.15.0|^10.6", + "orchestra/testbench": "^9.15.0|^10.6|^11.0", "pestphp/pest": "^2.36.0|^3.8.4|^4.1.5", "phpstan/phpstan": "^2.1.27", "rector/rector": "^2.1" @@ -18811,20 +18811,20 @@ "issues": "https://github.com/laravel/boost/issues", "source": "https://github.com/laravel/boost" }, - "time": "2026-03-12T07:25:30+00:00" + "time": "2026-03-17T16:42:14+00:00" }, { "name": "laravel/mcp", - "version": "v0.5.9", + "version": "v0.6.3", "source": { "type": "git", "url": "https://github.com/laravel/mcp.git", - "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129" + "reference": "8a2c97ec1184e16029080e3f6172a7ca73de4df9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/mcp/zipball/39e8da60eb7bce4737c5d868d35a3fe78938c129", - "reference": "39e8da60eb7bce4737c5d868d35a3fe78938c129", + "url": "https://api.github.com/repos/laravel/mcp/zipball/8a2c97ec1184e16029080e3f6172a7ca73de4df9", + "reference": "8a2c97ec1184e16029080e3f6172a7ca73de4df9", "shasum": "" }, "require": { @@ -18884,35 +18884,35 @@ "issues": "https://github.com/laravel/mcp/issues", "source": "https://github.com/laravel/mcp" }, - "time": "2026-02-17T19:05:53+00:00" + "time": "2026-03-12T12:46:43+00:00" }, { "name": "laravel/roster", - "version": "v0.2.9", + "version": "v0.5.1", "source": { "type": "git", "url": "https://github.com/laravel/roster.git", - "reference": "82bbd0e2de614906811aebdf16b4305956816fa6" + "reference": "5089de7615f72f78e831590ff9d0435fed0102bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/roster/zipball/82bbd0e2de614906811aebdf16b4305956816fa6", - "reference": "82bbd0e2de614906811aebdf16b4305956816fa6", + "url": "https://api.github.com/repos/laravel/roster/zipball/5089de7615f72f78e831590ff9d0435fed0102bb", + "reference": "5089de7615f72f78e831590ff9d0435fed0102bb", "shasum": "" }, "require": { - "illuminate/console": "^10.0|^11.0|^12.0", - "illuminate/contracts": "^10.0|^11.0|^12.0", - "illuminate/routing": "^10.0|^11.0|^12.0", - "illuminate/support": "^10.0|^11.0|^12.0", - "php": "^8.1|^8.2", - "symfony/yaml": "^6.4|^7.2" + "illuminate/console": "^11.0|^12.0|^13.0", + "illuminate/contracts": "^11.0|^12.0|^13.0", + "illuminate/routing": "^11.0|^12.0|^13.0", + "illuminate/support": "^11.0|^12.0|^13.0", + "php": "^8.2", + "symfony/yaml": "^7.2|^8.0" }, "require-dev": { "laravel/pint": "^1.14", "mockery/mockery": "^1.6", - "orchestra/testbench": "^8.22.0|^9.0|^10.0", - "pestphp/pest": "^2.0|^3.0", + "orchestra/testbench": "^9.0|^10.0|^11.0", + "pestphp/pest": "^3.0|^4.1", "phpstan/phpstan": "^2.0" }, "type": "library", @@ -18945,7 +18945,7 @@ "issues": "https://github.com/laravel/roster/issues", "source": "https://github.com/laravel/roster" }, - "time": "2025-10-20T09:56:46+00:00" + "time": "2026-03-05T07:58:43+00:00" }, { "name": "mockery/mockery", @@ -22147,6 +22147,6 @@ "ext-libxml": "*", "ext-simplexml": "*" }, - "platform-dev": {}, - "plugin-api-version": "2.9.0" + "platform-dev": [], + "plugin-api-version": "2.6.0" } diff --git a/lang/en/texts.php b/lang/en/texts.php index 3d829cfcfb..12bfe42486 100644 --- a/lang/en/texts.php +++ b/lang/en/texts.php @@ -5906,6 +5906,7 @@ $lang = array( 'welcome_to_docuninja' => 'Welcome to DocuNinja', 'yodlee' => 'Yodlee', 'location_name' => 'Location Name', + 'is_shipping_location' => 'Is Shipping Location', 'quote_rejected' => 'Quote Rejected', 'e_invoice_received_notification' => 'E-Invoice Received Notification', 'e_invoice_received_notification_help' => 'Send an email when an e-invoice has been received', diff --git a/tests/Feature/Export/LocationExportTest.php b/tests/Feature/Export/LocationExportTest.php index fa4a8e2d49..3767075683 100644 --- a/tests/Feature/Export/LocationExportTest.php +++ b/tests/Feature/Export/LocationExportTest.php @@ -13,6 +13,7 @@ namespace Tests\Feature\Export; use App\DataMapper\CompanySettings; +use App\Export\CSV\ClientExport; use App\Export\CSV\LocationExport; use App\Factory\CompanyUserFactory; use App\Models\Account; @@ -26,7 +27,6 @@ use App\Utils\Traits\MakesHash; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Facades\Http; use League\Csv\Reader; -use League\Csv\ResultSet; use Tests\TestCase; class LocationExportTest extends TestCase @@ -165,7 +165,7 @@ class LocationExportTest extends TestCase $reader = Reader::fromString($csv); $reader->setHeaderOffset(0); - $res = ResultSet::from($reader)->fetchColumn($column); + $res = $reader->fetchColumnByName($column); $res = iterator_to_array($res, true); return $res[1]; @@ -182,7 +182,7 @@ class LocationExportTest extends TestCase 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ + $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->postJson('/api/v1/reports/locations', $data) @@ -202,7 +202,7 @@ class LocationExportTest extends TestCase 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ + $this->withHeaders([ 'X-API-SECRET' => config('ninja.api_secret'), 'X-API-TOKEN' => $this->token, ])->postJson('/api/v1/reports/client_locations', $data) @@ -225,20 +225,11 @@ class LocationExportTest extends TestCase 'date_range' => 'all', 'report_keys' => [], 'send_email' => false, + 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); + $export = new LocationExport($this->company, $data); + $csv = $export->run(); $reader = Reader::fromString($csv); $reader->setHeaderOffset(0); @@ -246,50 +237,7 @@ class LocationExportTest extends TestCase $headers = $reader->getHeader(); $this->assertNotEmpty($headers); - $this->account->forceDelete(); - } - - public function testLocationCsvWithCustomReportKeys() - { - $this->createLocation([ - 'name' => 'Warehouse', - 'address1' => '789 Industrial Ave', - 'city' => 'Houston', - 'state' => 'TX', - 'postal_code' => '77001', - ]); - - $data = [ - 'date_range' => 'all', - 'report_keys' => [ - 'location.name', - 'location.address1', - 'location.city', - 'location.state', - 'location.postal_code', - 'client.name', - 'contact.email', - ], - 'send_email' => false, - ]; - - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); - - $reader = Reader::fromString($csv); - $reader->setHeaderOffset(0); $records = iterator_to_array($reader->getRecords()); - $this->assertCount(1, $records); $this->account->forceDelete(); @@ -313,34 +261,19 @@ class LocationExportTest extends TestCase 'location.city', 'location.state', 'location.postal_code', - 'client.name', - 'contact.first_name', - 'contact.email', ], 'send_email' => false, + 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); + $export = new LocationExport($this->company, $data); + $csv = $export->run(); $this->assertEquals('Main Office', $this->getFirstValueByColumn($csv, 'Location Name')); $this->assertEquals('100 Test Street', $this->getFirstValueByColumn($csv, 'Location Street')); $this->assertEquals('Denver', $this->getFirstValueByColumn($csv, 'Location City')); $this->assertEquals('CO', $this->getFirstValueByColumn($csv, 'Location State/Province')); $this->assertEquals('80201', $this->getFirstValueByColumn($csv, 'Location Postal Code')); - $this->assertEquals('Test Client', $this->getFirstValueByColumn($csv, 'Client Name')); - $this->assertEquals('John', $this->getFirstValueByColumn($csv, 'Contact First Name')); - $this->assertEquals('john@example.com', $this->getFirstValueByColumn($csv, 'Contact Email')); $this->account->forceDelete(); } @@ -364,22 +297,13 @@ class LocationExportTest extends TestCase $data = [ 'date_range' => 'all', - 'report_keys' => ['location.name', 'location.city', 'client.name'], + 'report_keys' => ['location.name', 'location.city'], 'send_email' => false, + 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); + $export = new LocationExport($this->company, $data); + $csv = $export->run(); $reader = Reader::fromString($csv); $reader->setHeaderOffset(0); @@ -392,11 +316,6 @@ class LocationExportTest extends TestCase $this->assertContains('CityB', $cities); $this->assertContains('CityC', $cities); - // All rows should reference the same client - $clientNames = array_unique(array_column($records, 'Client Name')); - $this->assertCount(1, $clientNames); - $this->assertEquals('Test Client', $clientNames[0]); - $this->account->forceDelete(); } @@ -422,22 +341,13 @@ class LocationExportTest extends TestCase $data = [ 'date_range' => 'all', - 'report_keys' => ['location.name', 'client.name'], + 'report_keys' => ['location.name'], 'send_email' => false, + 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); + $export = new LocationExport($this->company, $data); + $csv = $export->run(); $reader = Reader::fromString($csv); $reader->setHeaderOffset(0); @@ -460,20 +370,11 @@ class LocationExportTest extends TestCase 'date_range' => 'all', 'report_keys' => ['location.name', 'location.is_shipping_location'], 'send_email' => false, + 'user_id' => $this->user->id, ]; - $response = $this->withHeaders([ - 'X-API-SECRET' => config('ninja.api_secret'), - 'X-API-TOKEN' => $this->token, - ])->post('/api/v1/reports/locations', $data); - - $response->assertStatus(200); - - $arr = $response->json(); - $hash = $arr['message']; - - $response = $this->poll($hash); - $csv = $response->body(); + $export = new LocationExport($this->company, $data); + $csv = $export->run(); $reader = Reader::fromString($csv); $reader->setHeaderOffset(0); @@ -500,7 +401,6 @@ class LocationExportTest extends TestCase 'location.name', 'location.address1', 'location.city', - 'client.name', ], 'send_email' => false, 'user_id' => $this->user->id, @@ -519,7 +419,6 @@ class LocationExportTest extends TestCase $this->assertEquals('Direct Test Location', $row['Location Name']); $this->assertEquals('555 Export Ave', $row['Location Street']); $this->assertEquals('Portland', $row['Location City']); - $this->assertEquals('Test Client', $row['Client Name']); $this->account->forceDelete(); } @@ -536,7 +435,6 @@ class LocationExportTest extends TestCase 'report_keys' => [ 'location.name', 'location.city', - 'client.name', ], 'send_email' => false, 'user_id' => $this->user->id, @@ -546,9 +444,8 @@ class LocationExportTest extends TestCase $result = $export->returnJson(); $this->assertArrayHasKey('columns', $result); - $this->assertCount(3, $result['columns']); + $this->assertCount(2, $result['columns']); - // Result should have data rows (array items beyond 'columns') $dataRows = array_filter($result, fn($key) => $key !== 'columns', ARRAY_FILTER_USE_KEY); $this->assertNotEmpty($dataRows); @@ -557,12 +454,10 @@ class LocationExportTest extends TestCase public function testVendorLocationsAreExcluded() { - // Create a client location $this->createLocation([ 'name' => 'Client Location', ]); - // Create a vendor-only location (no client_id) Location::factory()->create([ 'company_id' => $this->company->id, 'user_id' => $this->user->id, @@ -593,11 +488,9 @@ class LocationExportTest extends TestCase public function testEmptyLocationExport() { - // No locations created - $data = [ 'date_range' => 'all', - 'report_keys' => ['location.name', 'client.name'], + 'report_keys' => ['location.name'], 'send_email' => false, 'user_id' => $this->user->id, ]; @@ -613,4 +506,80 @@ class LocationExportTest extends TestCase $this->account->forceDelete(); } + + public function testClientExportWithLocationFields() + { + $this->createLocation([ + 'name' => 'HQ Location', + 'address1' => '999 Location Blvd', + 'city' => 'Miami', + 'state' => 'FL', + 'postal_code' => '33101', + ]); + + $data = [ + 'date_range' => 'all', + 'report_keys' => [ + 'client.name', + 'location.name', + 'location.address1', + 'location.city', + 'location.state', + 'location.postal_code', + ], + 'send_email' => false, + 'include_deleted' => false, + 'user_id' => $this->user->id, + ]; + + $export = new ClientExport($this->company, $data); + $csv = $export->run(); + + $reader = Reader::fromString($csv); + $reader->setHeaderOffset(0); + $records = iterator_to_array($reader->getRecords()); + + $this->assertCount(1, $records); + + $row = reset($records); + $this->assertEquals('Test Client', $row['Name']); + $this->assertEquals('HQ Location', $row['Location Name']); + $this->assertEquals('999 Location Blvd', $row['Location Street']); + $this->assertEquals('Miami', $row['Location City']); + $this->assertEquals('FL', $row['Location State/Province']); + $this->assertEquals('33101', $row['Location Postal Code']); + + $this->account->forceDelete(); + } + + public function testClientExportWithNoLocationReturnsEmptyLocationFields() + { + $data = [ + 'date_range' => 'all', + 'report_keys' => [ + 'client.name', + 'location.name', + 'location.city', + ], + 'send_email' => false, + 'include_deleted' => false, + 'user_id' => $this->user->id, + ]; + + $export = new ClientExport($this->company, $data); + $csv = $export->run(); + + $reader = Reader::fromString($csv); + $reader->setHeaderOffset(0); + $records = iterator_to_array($reader->getRecords()); + + $this->assertCount(1, $records); + + $row = reset($records); + $this->assertEquals('Test Client', $row['Name']); + $this->assertEmpty($row['Location Name']); + $this->assertEmpty($row['Location City']); + + $this->account->forceDelete(); + } }