diff --git a/app/Services/Report/EInvoiceReport.php b/app/Services/Report/EInvoiceReport.php new file mode 100644 index 0000000000..f85f2ea07c --- /dev/null +++ b/app/Services/Report/EInvoiceReport.php @@ -0,0 +1,155 @@ +company->db); + App::forgetInstance('translator'); + App::setLocale($this->company->locale()); + $t = app('translator'); + $t->replace(Ninja::transformTranslations($this->company->settings)); + + $this->csv = Writer::createFromString(); + \League\Csv\CharsetConverter::addTo($this->csv, 'UTF-8', 'UTF-8'); + + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([]); + $this->csv->insertOne([ctrans('texts.einvoice_report')]); + $this->csv->insertOne([ctrans('texts.created_on'), ' ', $this->translateDate(now()->format('Y-m-d'), $this->company->date_format(), $this->company->locale())]); + + if (count($this->input['report_keys']) == 0) { + $this->input['report_keys'] = $this->report_keys; + } + + $this->csv->insertOne($this->buildHeader()); + + // Get all invoices with e-invoice status + $query = Invoice::query() + ->where('company_id', $this->company->id) + ->where('is_deleted', 0) + ->with(['client']); + + $query = $this->addDateRange($query, 'invoices'); + + $invoices = $query->cursor(); + + // Process invoices + foreach ($invoices as $invoice) { + /** @var Invoice $invoice */ + $einvoiceStatus = $invoice->backup?->guid ? 'Sent via e-invoicing' : 'Not sent via e-invoicing'; + + $this->csv->insertOne([ + $invoice->number, + $invoice->client->present()->name(), + $invoice->client->number, + $this->translateDate($invoice->date, $this->company->date_format(), $this->company->locale()), + Number::formatMoney($invoice->amount, $this->company), + $einvoiceStatus, + '', // Empty for expenses + '', // Empty for expenses + '', // Empty for expenses + ]); + } + + $this->date_key = 'created_at'; + + // Get all expenses received via e-invoicing + $query = Activity::query() + ->where('company_id', $this->company->id) + ->where('activity_type_id', 148) // Received via e-invoicing + ->with('expense'); + + $query = $this->addDateRange($query, 'activities'); + + $expenseActivityIds = $query->pluck('expense_id') + ->toArray(); + + $expenses = Expense::query() + ->where('company_id', $this->company->id) + ->where('is_deleted', 0) + ->whereIn('id', $expenseActivityIds) + ->cursor(); + + // Process expenses + foreach ($expenses as $expense) { + $this->csv->insertOne([ + '', // Empty for invoices + '', // Empty for invoices + '', // Empty for invoices + '', // Empty for invoices + '', // Empty for invoices + '', // Empty for invoices + $expense->number, + $this->translateDate($expense->date, $this->company->date_format(), $this->company->locale()), + Number::formatMoney($expense->amount, $this->company), + ]); + } + + return $this->csv->toString(); + } + + public function buildHeader(): array + { + $header = []; + + foreach ($this->input['report_keys'] as $value) { + $header[] = ctrans("texts.{$value}"); + } + + return $header; + } +} diff --git a/openapi/api-docs.yaml b/openapi/api-docs.yaml index b52c3e3ade..d428eb44f4 100644 --- a/openapi/api-docs.yaml +++ b/openapi/api-docs.yaml @@ -8987,6 +8987,18 @@ paths: $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Credit" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -9194,6 +9206,9 @@ paths: type: array items: $ref: '#/components/schemas/Location' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -9600,7 +9615,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Location' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Location' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -10140,7 +10163,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/RecurringInvoice" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -10638,7 +10669,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Task" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Task" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -11410,7 +11449,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Quote" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Quote" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -12352,6 +12399,18 @@ paths: $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Invoice" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -12950,7 +13009,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Project" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Project" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -13353,7 +13420,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Vendor" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Vendor" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -13869,7 +13944,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Payment" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Payment" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -14221,7 +14304,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Client' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Client' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -14741,7 +14832,15 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/Client' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Client' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -15493,6 +15592,18 @@ paths: $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/PurchaseOrder" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: @@ -16180,7 +16291,15 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Product" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Product" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: diff --git a/openapi/paths/clients.yaml b/openapi/paths/clients.yaml index ef653a0f6f..c22001c689 100644 --- a/openapi/paths/clients.yaml +++ b/openapi/paths/clients.yaml @@ -220,7 +220,15 @@ content: application/json: schema: - $ref: '#/components/schemas/Client' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Client' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -740,7 +748,15 @@ content: application/json: schema: - $ref: '#/components/schemas/Client' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Client' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: diff --git a/openapi/paths/credits.yaml b/openapi/paths/credits.yaml index e4cb8a8822..7dafdf7cf3 100644 --- a/openapi/paths/credits.yaml +++ b/openapi/paths/credits.yaml @@ -329,6 +329,18 @@ $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Credit" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/invoices.yaml b/openapi/paths/invoices.yaml index 6215682601..5813457591 100644 --- a/openapi/paths/invoices.yaml +++ b/openapi/paths/invoices.yaml @@ -770,6 +770,18 @@ $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Invoice" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/locations.yaml b/openapi/paths/locations.yaml index 8e58372719..38b2c97792 100644 --- a/openapi/paths/locations.yaml +++ b/openapi/paths/locations.yaml @@ -88,6 +88,9 @@ type: array items: $ref: '#/components/schemas/Location' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: @@ -494,7 +497,15 @@ content: application/json: schema: - $ref: '#/components/schemas/Location' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Location' + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: diff --git a/openapi/paths/payments.yaml b/openapi/paths/payments.yaml index a568031fb8..b429788335 100644 --- a/openapi/paths/payments.yaml +++ b/openapi/paths/payments.yaml @@ -439,7 +439,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Payment" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Payment" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/products.yaml b/openapi/paths/products.yaml index cf47ee45d0..7f85848013 100644 --- a/openapi/paths/products.yaml +++ b/openapi/paths/products.yaml @@ -516,7 +516,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Product" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Product" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: '#/components/responses/401' 403: diff --git a/openapi/paths/projects.yaml b/openapi/paths/projects.yaml index b736daa834..350799fe4e 100644 --- a/openapi/paths/projects.yaml +++ b/openapi/paths/projects.yaml @@ -326,7 +326,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Project" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Project" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/purchase_orders.yaml b/openapi/paths/purchase_orders.yaml index 0dbf99a55d..ffd3c524d6 100644 --- a/openapi/paths/purchase_orders.yaml +++ b/openapi/paths/purchase_orders.yaml @@ -325,6 +325,18 @@ $ref: "#/components/headers/X-RateLimit-Remaining" X-RateLimit-Limit: $ref: "#/components/headers/X-RateLimit-Limit" + content: + application/json: + schema: + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/PurchaseOrder" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/quotes.yaml b/openapi/paths/quotes.yaml index afd08a7c7d..9576cea3f7 100644 --- a/openapi/paths/quotes.yaml +++ b/openapi/paths/quotes.yaml @@ -662,7 +662,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Quote" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Quote" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/recurring_invoices.yaml b/openapi/paths/recurring_invoices.yaml index 267e8d5282..06b1065b9c 100644 --- a/openapi/paths/recurring_invoices.yaml +++ b/openapi/paths/recurring_invoices.yaml @@ -526,7 +526,15 @@ content: application/json: schema: - $ref: "#/components/schemas/RecurringInvoice" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/RecurringInvoice" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/tasks.yaml b/openapi/paths/tasks.yaml index f85eab59ee..e44d33f426 100644 --- a/openapi/paths/tasks.yaml +++ b/openapi/paths/tasks.yaml @@ -326,7 +326,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Task" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Task" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/openapi/paths/vendors.yaml b/openapi/paths/vendors.yaml index d75a66763c..4383de7ae0 100644 --- a/openapi/paths/vendors.yaml +++ b/openapi/paths/vendors.yaml @@ -326,7 +326,15 @@ content: application/json: schema: - $ref: "#/components/schemas/Vendor" + type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/Vendor" + meta: + type: object + $ref: '#/components/schemas/Meta' 401: $ref: "#/components/responses/401" 403: diff --git a/package-lock.json b/package-lock.json index a57634311c..8cb2701bb1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "name": "@invoiceninja/invoiceninja", "dependencies": { + "@apidevtools/swagger-parser": "^10.1.1", "axios": "^0.25", "card-js": "^1.0.13", "card-validator": "^8.1.1", @@ -65,6 +66,92 @@ "node": ">=6.0.0" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.1.tgz", + "integrity": "sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.7.2", + "@apidevtools/openapi-schemas": "^2.1.0", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.2" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@apidevtools/swagger-parser/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@apidevtools/swagger-parser/node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@apidevtools/swagger-parser/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -2188,6 +2275,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", @@ -2490,9 +2583,10 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" }, "node_modules/@types/mime": { "version": "3.0.1", @@ -2969,6 +3063,12 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, "node_modules/array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -3541,6 +3641,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -6568,6 +6674,18 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -7726,6 +7844,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", @@ -11483,6 +11608,64 @@ "@jridgewell/trace-mapping": "^0.3.9" } }, + "@apidevtools/json-schema-ref-parser": { + "version": "11.7.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.2.tgz", + "integrity": "sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==", + "requires": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + } + }, + "@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==" + }, + "@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==" + }, + "@apidevtools/swagger-parser": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.1.1.tgz", + "integrity": "sha512-u/kozRnsPO/x8QtKYJOqoGtC4kH6yg1lfYkB9Au0WhYB0FNLpyFusttQtvhlwjtG3rOwiRz4D8DnnXa8iEpIKA==", + "requires": { + "@apidevtools/json-schema-ref-parser": "11.7.2", + "@apidevtools/openapi-schemas": "^2.1.0", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.2" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "requires": {} + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + } + } + }, "@babel/code-frame": { "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", @@ -12866,6 +13049,11 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" + }, "@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", @@ -13142,9 +13330,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "@types/mime": { "version": "3.0.1", @@ -13541,6 +13729,11 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -13981,6 +14174,11 @@ "get-intrinsic": "^1.0.2" } }, + "call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -16224,6 +16422,14 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -17085,6 +17291,12 @@ "is-wsl": "^2.2.0" } }, + "openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "peer": true + }, "os-browserify": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", diff --git a/package.json b/package.json index 0e2241108d..91d1d81966 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "vue-template-compiler": "^2.6.14" }, "dependencies": { + "@apidevtools/swagger-parser": "^10.1.1", "axios": "^0.25", "card-js": "^1.0.13", "card-validator": "^8.1.1", diff --git a/tests/Feature/Export/EInvoiceReportTest.php b/tests/Feature/Export/EInvoiceReportTest.php new file mode 100644 index 0000000000..54b046a37f --- /dev/null +++ b/tests/Feature/Export/EInvoiceReportTest.php @@ -0,0 +1,242 @@ +faker = \Faker\Factory::create(); + + $this->withoutMiddleware( + ThrottleRequests::class + ); + + $this->withoutExceptionHandling(); + + } + + public $company; + + public $user; + + public $payload; + + public $account; + + public $client; + + /** + * start_date - Y-m-d + end_date - Y-m-d + date_range - + all + last7 + last30 + this_month + last_month + this_quarter + last_quarter + this_year + custom + is_income_billed - true = Invoiced || false = Payments + expense_billed - true = Expensed || false = Expenses marked as paid + include_tax - true tax_included || false - tax_excluded + */ + private function buildData() + { + $this->account = Account::factory()->create([ + 'hosted_client_count' => 1000, + 'hosted_company_count' => 1000, + ]); + + $this->account->num_users = 3; + $this->account->save(); + + $this->user = User::factory()->create([ + 'account_id' => $this->account->id, + 'confirmation_code' => 'xyz123', + 'email' => $this->faker->unique()->safeEmail(), + ]); + + $settings = CompanySettings::defaults(); + $settings->client_online_payment_notification = false; + $settings->client_manual_payment_notification = false; + $settings->currency_id = '1'; + + $this->company = Company::factory()->create([ + 'account_id' => $this->account->id, + 'settings' => $settings, + ]); + + $this->company->settings = $settings; + $this->company->save(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + ]; + + $this->client = Client::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'is_deleted' => 0, + ]); + } + + public function testUserSalesInstance() + { + $this->buildData(); + + $pl = new EInvoiceReport($this->company, $this->payload); + + $this->assertInstanceOf(EInvoiceReport::class, $pl); + + $this->account->delete(); + } + + public function testSimpleReport() + { + $this->buildData(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'report_keys' => [] + ]; + + $guid = new \stdClass; + $guid->guid = '1234567890'; + + $i = Invoice::factory()->create([ + 'client_id' => $this->client->id, + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'backup' => $guid, + 'amount' => 0, + 'balance' => 0, + 'status_id' => 2, + 'total_taxes' => 1, + 'date' => now()->format('Y-m-d'), + 'terms' => 'nada', + 'discount' => 0, + 'tax_rate1' => 0, + 'tax_rate2' => 0, + 'tax_rate3' => 0, + 'tax_name1' => '', + 'tax_name2' => '', + 'tax_name3' => '', + 'uses_inclusive_taxes' => false, + 'line_items' => $this->buildLineItems(), + ]); + + $i = $i->calc()->getInvoice(); + + $pl = new EInvoiceReport($this->company, $this->payload); + $response = $pl->run(); + + $this->assertIsString($response); + + $this->account->delete(); + } + + public function testExpenseEInvoiceComponent() + { + + $this->buildData(); + + $this->payload = [ + 'start_date' => '2000-01-01', + 'end_date' => '2030-01-11', + 'date_range' => 'custom', + 'report_keys' => [] + ]; + + $e = Expense::factory()->create([ + 'user_id' => $this->user->id, + 'company_id' => $this->company->id, + 'amount' => 100, + 'public_notes' => 'Expensive Business!!', + 'should_be_invoiced' => true, + ]); + + $a = new Activity(); + $a->expense_id = $e->id; + $a->company_id = $this->company->id; + $a->user_id = $this->user->id; + $a->activity_type_id = 148; + $a->save(); + + $pl = new EInvoiceReport($this->company, $this->payload); + $response = $pl->run(); + + $this->assertIsString($response); + + $this->account->delete(); + } + private function buildLineItems() + { + $line_items = []; + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'test'; + $item->notes = 'test_product'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + $item = InvoiceItemFactory::create(); + $item->quantity = 1; + $item->cost = 10; + $item->product_key = 'pumpkin'; + $item->notes = 'test_pumpkin'; + // $item->task_id = $this->encodePrimaryKey($this->task->id); + // $item->expense_id = $this->encodePrimaryKey($this->expense->id); + + $line_items[] = $item; + + + return $line_items; + } +}