diff --git a/app/Http/Controllers/Admin/PackageController.php b/app/Http/Controllers/Admin/PackageController.php index e510d20..f507c4f 100644 --- a/app/Http/Controllers/Admin/PackageController.php +++ b/app/Http/Controllers/Admin/PackageController.php @@ -25,7 +25,7 @@ public function index(Request $request): Response { $packages = Package::query() ->latest('id') - ->paginate(20); + ->paginate(25); // Minimal lookups for create form (active only) $profiles = \App\Models\SmsProfile::query() ->where('active', true) diff --git a/app/Http/Controllers/ClientCaseContoller.original b/app/Http/Controllers/ClientCaseContoller.original deleted file mode 100644 index 72cc781..0000000 Binary files a/app/Http/Controllers/ClientCaseContoller.original and /dev/null differ diff --git a/app/Http/Controllers/ImportTemplateController.php b/app/Http/Controllers/ImportTemplateController.php index dbca0e7..9341f8b 100644 --- a/app/Http/Controllers/ImportTemplateController.php +++ b/app/Http/Controllers/ImportTemplateController.php @@ -657,6 +657,7 @@ public function applyToImport(Request $request, ImportTemplate $template, Import $import->update([ 'import_template_id' => $template->id, + 'reactivate' => $template->reactivate, 'meta' => $merged, ]); }); diff --git a/app/Models/Contract.php b/app/Models/Contract.php index be845a7..ade64e3 100644 --- a/app/Models/Contract.php +++ b/app/Models/Contract.php @@ -29,6 +29,7 @@ class Contract extends Model 'end_date', 'client_case_id', 'type_id', + 'active', 'description', 'meta', ]; diff --git a/app/Services/Import/Handlers/ContractHandler.php b/app/Services/Import/Handlers/ContractHandler.php index caa7e69..e8e82d8 100644 --- a/app/Services/Import/Handlers/ContractHandler.php +++ b/app/Services/Import/Handlers/ContractHandler.php @@ -46,76 +46,35 @@ public function resolve(array $mapped, array $context = []): mixed public function process(Import $import, array $mapped, array $raw, array $context = []): array { - // PHASE 4: Check for existing Contract early to prevent duplicate creation - $reference = $mapped['reference'] ?? null; - - if ($reference) { - $existingContract = $this->resolutionService->getExistingContract( - $import->client_id, - $reference - ); - - if ($existingContract) { - Log::info('ContractHandler: Found existing Contract by reference', [ - 'contract_id' => $existingContract->id, - 'reference' => $reference, - ]); - - $mode = $this->getOption('update_mode', 'update'); - - if ($mode === 'skip') { - return [ - 'action' => 'skipped', - 'entity' => $existingContract, - 'message' => 'Contract already exists (skip mode)', - ]; - } - - // Update existing contract - $payload = $this->buildPayload($mapped, $existingContract); - $payload = $this->mergeJsonFields($payload, $existingContract); - $appliedFields = $this->trackAppliedFields($existingContract, $payload); - - if (empty($appliedFields)) { - return [ - 'action' => 'skipped', - 'entity' => $existingContract, - 'message' => 'No changes detected', - ]; - } - - $existingContract->fill($payload); - $existingContract->save(); - - return [ - 'action' => 'updated', - 'entity' => $existingContract, - 'applied_fields' => $appliedFields, - ]; - } - } - + // Check for existing contract (using resolve method which handles client scoping) $existing = $this->resolve($mapped, $context); - // Check for reactivation request - $reactivate = $this->shouldReactivate($context); - - // Handle reactivation if entity is soft-deleted or inactive - if ($existing && $reactivate && $this->needsReactivation($existing)) { - $reactivated = $this->attemptReactivation($existing, $context); - if ($reactivated) { - return [ - 'action' => 'reactivated', - 'entity' => $existing, - 'message' => 'Contract reactivated', - ]; - } - } - - // Determine if we should update or skip based on mode - $mode = $this->getOption('update_mode', 'update'); - if ($existing) { + + + // Check for reactivation FIRST (before update_mode check) + $reactivate = $this->shouldReactivate($context); + + Log::info('ContractHandler: Found existing Contract', [ + 'contract_id' => $existing->id, + 'reference' => $mapped['reference'] ?? null, + 'context' => $context['import'] + ]); + + if ($reactivate && $this->needsReactivation($existing)) { + $reactivated = $this->attemptReactivation($existing, $context); + Log::info('ContractHandler: Reactivate', ['reactivated' => $reactivated]); + if ($reactivated) { + return [ + 'action' => 'reactivated', + 'entity' => $existing, + 'message' => 'Contract reactivated', + ]; + } + } + + // Check update mode + $mode = $this->getOption('update_mode', 'update'); if ($mode === 'skip') { return [ 'action' => 'skipped', @@ -124,12 +83,9 @@ public function process(Import $import, array $mapped, array $raw, array $contex ]; } - // Update + // Update existing contract $payload = $this->buildPayload($mapped, $existing); - - // Merge JSON fields instead of overwriting $payload = $this->mergeJsonFields($payload, $existing); - $appliedFields = $this->trackAppliedFields($existing, $payload); if (empty($appliedFields)) { @@ -200,7 +156,6 @@ protected function buildPayload(array $mapped, $model): array // Map fields according to contract schema $fieldMap = [ 'reference' => 'reference', - 'title' => 'title', 'description' => 'description', 'amount' => 'amount', 'currency' => 'currency', @@ -286,7 +241,9 @@ protected function attemptReactivation(Contract $contract, array $context): bool $contract->restore(); } - $contract->update(['active' => 1]); + $contract->active = 1; + + $contract->save(); return true; } catch (\Throwable $e) { diff --git a/app/Services/Import/ImportServiceV2.php b/app/Services/Import/ImportServiceV2.php index 7e1910c..d58aad0 100644 --- a/app/Services/Import/ImportServiceV2.php +++ b/app/Services/Import/ImportServiceV2.php @@ -100,8 +100,6 @@ public function process(Import $import, ?Authenticatable $user = null): array $rowNum++; } - $isPg = DB::connection()->getDriverName() === 'pgsql'; - // If retry mode, only process failed/invalid rows if ($isRetry) { $failedRows = ImportRow::where('import_id', $import->id) diff --git a/database/seeders/ImportEntitiesV2Seeder.php b/database/seeders/ImportEntitiesV2Seeder.php index 32d5c48..21be11f 100644 --- a/database/seeders/ImportEntitiesV2Seeder.php +++ b/database/seeders/ImportEntitiesV2Seeder.php @@ -17,11 +17,11 @@ public function run(): void 'key' => 'contracts', 'canonical_root' => 'contract', 'label' => 'Pogodbe', - 'fields' => ['reference', 'title', 'description', 'amount', 'currency', 'start_date', 'end_date', 'active'], + 'fields' => ['reference', 'start_date', 'end_date', 'description', 'type_id', 'client_case_id', 'meta'], 'field_aliases' => [], 'aliases' => ['contract', 'contracts'], 'supports_multiple' => false, - 'meta' => false, + 'meta' => true, 'rules' => [], 'ui' => ['default_field' => 'reference', 'order' => 1], 'handler_class' => \App\Services\Import\Handlers\ContractHandler::class, @@ -45,7 +45,7 @@ public function run(): void 'key' => 'accounts', 'canonical_root' => 'account', 'label' => 'Računi', - 'fields' => ['contract_id', 'reference', 'title', 'description', 'balance_amount', 'currency'], + 'fields' => ['reference', 'initial_amount', 'balance_amount', 'contract_id', 'contract_reference', 'type_id', 'active', 'description'], 'field_aliases' => [], 'aliases' => ['account', 'accounts'], 'supports_multiple' => false, @@ -75,8 +75,26 @@ public function run(): void 'key' => 'payments', 'canonical_root' => 'payment', 'label' => 'Plačila', - 'fields' => ['account_id', 'reference', 'amount', 'currency', 'paid_at', 'payment_date'], - 'field_aliases' => ['payment_date' => 'paid_at'], + 'fields' => [ + 'reference', + 'payment_nu', + 'payment_date', + 'amount', + 'type_id', + 'active', + // optional helpers for mapping by related records + 'debt_id', + 'account_id', + 'account_reference', + 'contract_reference' + ], + 'field_aliases' => [ + 'datum' => 'payment_date', + 'paid_at' => 'payment_date', + 'number' => 'payment_nu', + 'znesek' => 'amount', + 'value' => 'amount' + ], 'aliases' => ['payment', 'payments'], 'supports_multiple' => false, 'meta' => false, diff --git a/package-lock.json b/package-lock.json index 9fb72b0..67ca1eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/vue-fontawesome": "^3.1.2", + "@guolao/vue-monaco-editor": "^1.6.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", "@internationalized/date": "^3.10.0", @@ -27,9 +28,10 @@ "lodash": "^4.17.21", "lucide-vue-next": "^0.552.0", "material-design-icons-iconfont": "^6.7.0", + "monaco-editor": "^0.55.1", "preline": "^2.7.0", "quill": "^1.3.7", - "reka-ui": "^2.6.1", + "reka-ui": "^2.7.0", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tailwindcss-inner-border": "^0.2.0", @@ -879,6 +881,52 @@ "vue": ">= 3.0.0 < 4" } }, + "node_modules/@guolao/vue-monaco-editor": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@guolao/vue-monaco-editor/-/vue-monaco-editor-1.6.0.tgz", + "integrity": "sha512-w2IiJ6eJGGeuIgCK6EKZOAfhHTTUB5aZwslzwGbZ5e89Hb4avx6++GkLTW8p84Sng/arFMjLPPxSBI56cFudyQ==", + "license": "MIT", + "dependencies": { + "@monaco-editor/loader": "^1.6.1", + "vue-demi": "latest" + }, + "peerDependencies": { + "@vue/composition-api": "^1.7.2", + "monaco-editor": ">=0.43.0", + "vue": "^2.6.14 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@guolao/vue-monaco-editor/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/@headlessui/vue": { "version": "1.7.23", "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", @@ -1069,6 +1117,15 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/@monaco-editor/loader": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", + "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", + "license": "MIT", + "dependencies": { + "state-local": "^1.0.6" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -2309,6 +2366,13 @@ "@types/geojson": "*" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", @@ -3616,6 +3680,15 @@ "node": ">=8" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4664,6 +4737,18 @@ "vt-pbf": "^3.1.3" } }, + "node_modules/marked": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", + "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/material-design-icons-iconfont": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/material-design-icons-iconfont/-/material-design-icons-iconfont-6.7.0.tgz", @@ -4736,6 +4821,16 @@ "node": ">=0.10.0" } }, + "node_modules/monaco-editor": { + "version": "0.55.1", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", + "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", + "license": "MIT", + "dependencies": { + "dompurify": "3.2.7", + "marked": "14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -5074,9 +5169,9 @@ } }, "node_modules/reka-ui": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.6.1.tgz", - "integrity": "sha512-XK7cJDQoNuGXfCNzBBo/81Yg/OgjPwvbabnlzXG2VsdSgNsT6iIkuPBPr+C0Shs+3bb0x0lbPvgQAhMSCKm5Ww==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.7.0.tgz", + "integrity": "sha512-m+XmxQN2xtFzBP3OAdIafKq7C8OETo2fqfxcIIxYmNN2Ch3r5oAf6yEYCIJg5tL/yJU2mHqF70dCCekUkrAnXA==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.6.13", @@ -5386,6 +5481,12 @@ "node": ">=0.10.0" } }, + "node_modules/state-local": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", + "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", + "license": "MIT" + }, "node_modules/striptags": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz", diff --git a/package.json b/package.json index b0da272..dbe40cc 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@fortawesome/free-regular-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", "@fortawesome/vue-fontawesome": "^3.1.2", + "@guolao/vue-monaco-editor": "^1.6.0", "@headlessui/vue": "^1.7.23", "@heroicons/vue": "^2.2.0", "@internationalized/date": "^3.10.0", @@ -47,9 +48,10 @@ "lodash": "^4.17.21", "lucide-vue-next": "^0.552.0", "material-design-icons-iconfont": "^6.7.0", + "monaco-editor": "^0.55.1", "preline": "^2.7.0", "quill": "^1.3.7", - "reka-ui": "^2.6.1", + "reka-ui": "^2.7.0", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "tailwindcss-inner-border": "^0.2.0", diff --git a/resources/js/Components/ui/progress/Progress.vue b/resources/js/Components/ui/progress/Progress.vue new file mode 100644 index 0000000..fa41c95 --- /dev/null +++ b/resources/js/Components/ui/progress/Progress.vue @@ -0,0 +1,34 @@ + + + diff --git a/resources/js/Components/ui/progress/index.js b/resources/js/Components/ui/progress/index.js new file mode 100644 index 0000000..e934a9b --- /dev/null +++ b/resources/js/Components/ui/progress/index.js @@ -0,0 +1 @@ +export { default as Progress } from "./Progress.vue"; diff --git a/resources/js/Layouts/AdminLayout.vue b/resources/js/Layouts/AdminLayout.vue index 0fbb090..cad7dc1 100644 --- a/resources/js/Layouts/AdminLayout.vue +++ b/resources/js/Layouts/AdminLayout.vue @@ -1,23 +1,23 @@ - - diff --git a/resources/js/Pages/Admin/EmailLogs/Show.vue b/resources/js/Pages/Admin/EmailLogs/Show.vue index ca27a30..db216c8 100644 --- a/resources/js/Pages/Admin/EmailLogs/Show.vue +++ b/resources/js/Pages/Admin/EmailLogs/Show.vue @@ -1,6 +1,11 @@ - + diff --git a/resources/js/Pages/Admin/SmsTemplates/Index.vue b/resources/js/Pages/Admin/SmsTemplates/Index.vue index 8fa29f3..dd8b9d7 100644 --- a/resources/js/Pages/Admin/SmsTemplates/Index.vue +++ b/resources/js/Pages/Admin/SmsTemplates/Index.vue @@ -1,10 +1,25 @@ diff --git a/resources/js/Pages/Imports/Import.vue b/resources/js/Pages/Imports/Import.vue index 28a46d9..83e18c1 100644 --- a/resources/js/Pages/Imports/Import.vue +++ b/resources/js/Pages/Imports/Import.vue @@ -751,14 +751,16 @@ async function fetchColumns() { async function applyTemplateToImport() { if (!importId.value || !form.value.import_template_id) return; - + // Find the selected template to get its UUID - const template = (props.templates || []).find((t) => t.id === form.value.import_template_id); + const template = (props.templates || []).find( + (t) => t.id === form.value.import_template_id + ); if (!template?.uuid) { - console.error('Template UUID not found'); + console.error("Template UUID not found"); return; } - + try { if (templateApplied.value) { const ok = window.confirm( @@ -1137,12 +1139,12 @@ async function fetchSimulation() { // V2 format paymentSimRows.value = Array.isArray(data?.rows) ? data.rows : []; paymentSimSummary.value = data?.summaries || null; - + // Extract unique entity types from rows for SimulationModal const entitySet = new Set(); for (const row of data?.rows || []) { - if (row.entities && typeof row.entities === 'object') { - Object.keys(row.entities).forEach(key => entitySet.add(key)); + if (row.entities && typeof row.entities === "object") { + Object.keys(row.entities).forEach((key) => entitySet.add(key)); } } paymentSimEntities.value = Array.from(entitySet); diff --git a/resources/js/Pages/Imports/Templates/Partials/BasicTemplateInfo.vue b/resources/js/Pages/Imports/Templates/Partials/BasicTemplateInfo.vue index eb71490..9508bf6 100644 --- a/resources/js/Pages/Imports/Templates/Partials/BasicTemplateInfo.vue +++ b/resources/js/Pages/Imports/Templates/Partials/BasicTemplateInfo.vue @@ -2,7 +2,13 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/Components/ui/card"; import { Label } from "@/Components/ui/label"; import { Input } from "@/Components/ui/input"; -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/Components/ui/select"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/Components/ui/select"; import { Checkbox } from "@/Components/ui/checkbox"; import { Button } from "@/Components/ui/button"; import AppMultiSelect from "@/Components/app/ui/AppMultiSelect.vue"; @@ -30,7 +36,7 @@ const emit = defineEmits(["save"]); - +