From f536996605c263c1077cdba53948e20dbc52fbeb Mon Sep 17 00:00:00 2001 From: Nadim Salloum Date: Fri, 11 Jun 2021 22:50:02 +0300 Subject: [PATCH] add documents functionality --- .gitignore | 2 + app/Enums/DocumentType.php | 19 - app/Http/Controllers/ContractController.php | 19 +- app/Http/Controllers/DocumentController.php | 41 +- app/Models/Car.php | 5 - app/Models/Contract.php | 2 +- app/Models/Document.php | 41 +- database/factories/DocumentFactory.php | 10 +- ...21_05_10_144114_create_documents_table.php | 12 +- ...05_10_144704_create_car_payments_table.php | 8 +- public/js/app.js | 4345 +++++++++++------ resources/js/Components/Documents/Item.vue | 29 + resources/js/Components/Documents/Upload.vue | 86 + resources/js/Pages/Contracts/Show.vue | 16 +- resources/js/app.js | 4 +- routes/web.php | 13 + 16 files changed, 3135 insertions(+), 1517 deletions(-) delete mode 100644 app/Enums/DocumentType.php create mode 100644 resources/js/Components/Documents/Item.vue create mode 100644 resources/js/Components/Documents/Upload.vue diff --git a/.gitignore b/.gitignore index 0ae59f0..532fce5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ Homestead.json Homestead.yaml npm-debug.log yarn-error.log +.DS_Store +public/documents/* diff --git a/app/Enums/DocumentType.php b/app/Enums/DocumentType.php deleted file mode 100644 index f263448..0000000 --- a/app/Enums/DocumentType.php +++ /dev/null @@ -1,19 +0,0 @@ - $contract->price->format(), 'type' => $contract->type, 'is_sell_contract' => $contract->isSellContract(), + 'documents' => $contract->documents()->get() + ->map(function ($document) { + return [ + 'id' => $document->id, + 'name' => $document->name, + 'size' => $document->size, + 'extension' => $document->extension, + 'link' => $document->link, + 'created_at' => $document->created_at, + ]; + }), 'insurance_type' => $contract->insurance_type ? InsuranceType::fromValue((int)$contract->insurance_type)->key : null, 'deleted_at' => $contract->deleted_at, 'contact' => [ @@ -230,11 +241,11 @@ class ContractController extends Controller { $contxt = stream_context_create([ 'ssl' => [ - 'verify_peer' => FALSE, - 'verify_peer_name' => FALSE, - 'allow_self_signed'=> TRUE + 'verify_peer' => FALSE, + 'verify_peer_name' => FALSE, + 'allow_self_signed'=> TRUE ] - ]); + ]); $pdf = PDF::setOptions(['isHtml5ParserEnabled' => true, 'isRemoteEnabled' => true])->loadView('contract', compact('contract'));//->setPaper('a4', 'portrait'); $pdf->getDomPDF()->setHttpContext($contxt); return $pdf->stream($contract->date . '_' . $contract->type_formatted . '.pdf'); diff --git a/app/Http/Controllers/DocumentController.php b/app/Http/Controllers/DocumentController.php index 8a6c371..aba9208 100644 --- a/app/Http/Controllers/DocumentController.php +++ b/app/Http/Controllers/DocumentController.php @@ -2,9 +2,48 @@ namespace App\Http\Controllers; +use App\Models\Contract; +use App\Models\Document; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Redirect; class DocumentController extends Controller { - // + public function show(Document $document) + { + header('Content-Disposition: filename="' . $document->name . '"'); + return response()->file($document->path); + } + + public function store(Request $request, Contract $contract) + { + $file = $request->file()['document']; + $internalName = date('Y-m-d-H-i-s') . '.' . $file->extension(); + $document = Document::create([ + 'name' => $file->getClientOriginalName(), + 'internal_name' => $internalName, + 'size' => $file->getSize(), + 'extension' => $file->extension(), + 'contract_id' => $contract->id, + ]); + $file->move(public_path("documents/contracts/{$contract->id}/"), $internalName); + + return [ + 'id' => $document->id, + 'name' => $document->name, + 'size' => $document->size, + 'extension' => $document->extension, + 'link' => $document->link, + 'created_at' => $document->created_at, + ]; + } + + public function destroy(Document $document) + { + unlink($document->path); + $document->delete(); + session()->flash('flash.banner', 'Dokument gelöscht.'); + return Redirect::back(); + } } diff --git a/app/Models/Car.php b/app/Models/Car.php index eba36c6..68e6273 100644 --- a/app/Models/Car.php +++ b/app/Models/Car.php @@ -90,11 +90,6 @@ class Car extends Model return $this->latestSellContract()->price->subtract($this->latestBuyContract()->price)->format(); } - public function documents() - { - return $this->morphMany(Document::class, 'documentable'); - } - public function contracts() { return $this->hasMany(Contract::class); diff --git a/app/Models/Contract.php b/app/Models/Contract.php index 92b4a23..3e2462d 100644 --- a/app/Models/Contract.php +++ b/app/Models/Contract.php @@ -77,7 +77,7 @@ class Contract extends Model public function documents() { - return $this->morphMany(Document::class, 'documentable'); + return $this->hasMany(Document::class); } public function contact() diff --git a/app/Models/Document.php b/app/Models/Document.php index 88f5e09..1543713 100644 --- a/app/Models/Document.php +++ b/app/Models/Document.php @@ -2,8 +2,9 @@ namespace App\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; +use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Factories\HasFactory; class Document extends Model { @@ -11,12 +12,42 @@ class Document extends Model protected $fillable = [ 'name', - 'document_type', - 'car_id', + 'internal_name', + 'extension', + 'size', + 'contract_id', ]; - public function documentable() + public function getCreatedAtAttribute($created_at) { - return $this->morphTo(); + return Carbon::parse($created_at)->format('d.m.Y'); + } + + public function getExtensionAttribute($extension) + { + return strtoupper($extension); + } + + public function getSizeAttribute($size) + { + if ($size / 1024 / 1024 < 1) { + return (string)floor($size / 1024) . " KB"; + } + return (string)floor($size / 1024 / 1024) . " MB"; + } + + public function getLinkAttribute() + { + return route('documents.show', $this->id); + } + + public function getPathAttribute() + { + return public_path("documents/contracts/{$this->contract->id}/{$this->internal_name}"); + } + + public function contract() + { + return $this->belongsTo(Contract::class)->withTrashed(); } } diff --git a/database/factories/DocumentFactory.php b/database/factories/DocumentFactory.php index 99e7546..023fe67 100644 --- a/database/factories/DocumentFactory.php +++ b/database/factories/DocumentFactory.php @@ -5,7 +5,6 @@ namespace Database\Factories; use App\Models\Document; use App\Models\Contract; use Illuminate\Database\Eloquent\Factories\Factory; -use App\Enums\DocumentType; class DocumentFactory extends Factory @@ -25,10 +24,11 @@ class DocumentFactory extends Factory public function definition() { return [ - 'name' => $this->faker->name(), - 'documentable_id' => $this->faker->numberBetween(1, Contract::count()), - 'documentable_type' => 'App\Models\Contract', - 'type' => (string)DocumentType::getRandomValue(), + 'name' => 'Vertrag.pdf', + 'internal_name' => '2021-06-11-13:11:12.pdf', + 'contract_id' => $this->faker->numberBetween(1, Contract::count()), + 'size' => $this->faker->numberBetween(1, 30000), + 'extension' => 'pdf', ]; } } diff --git a/database/migrations/2021_05_10_144114_create_documents_table.php b/database/migrations/2021_05_10_144114_create_documents_table.php index f09205b..bf64420 100644 --- a/database/migrations/2021_05_10_144114_create_documents_table.php +++ b/database/migrations/2021_05_10_144114_create_documents_table.php @@ -3,7 +3,6 @@ use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; -use App\Enums\DocumentType; class CreateDocumentsTable extends Migration { @@ -17,10 +16,13 @@ class CreateDocumentsTable extends Migration Schema::create('documents', function (Blueprint $table) { $table->id(); $table->string('name'); - $table->enum('type', DocumentType::getValues()) - ->default(DocumentType::Other); - $table->integer('documentable_id'); - $table->string('documentable_type'); + $table->string('internal_name'); + $table->integer('size'); + $table->string('extension'); + $table->foreignId('contract_id') + ->onUpdate('cascade') + ->onDelete('cascade') + ->constrained('contracts'); $table->timestamps(); }); } diff --git a/database/migrations/2021_05_10_144704_create_car_payments_table.php b/database/migrations/2021_05_10_144704_create_car_payments_table.php index 14f49e4..e08354c 100644 --- a/database/migrations/2021_05_10_144704_create_car_payments_table.php +++ b/database/migrations/2021_05_10_144704_create_car_payments_table.php @@ -19,11 +19,11 @@ class CreateCarPaymentsTable extends Migration $table->integer('amount'); $table->date('date'); $table->enum('type', PaymentType::getValues()) - ->default(PaymentType::Transaction); + ->default(PaymentType::Transaction); $table->foreignId('contract_id') - ->onUpdate('cascade') - ->onDelete('cascade') - ->constrained('contracts'); + ->onUpdate('cascade') + ->onDelete('cascade') + ->constrained('contracts'); $table->timestamps(); }); } diff --git a/public/js/app.js b/public/js/app.js index c8fc538..3103eaf 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -68,6 +68,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ "RESOLVE_COMPONENT": () => (/* binding */ RESOLVE_COMPONENT), /* harmony export */ "RESOLVE_DIRECTIVE": () => (/* binding */ RESOLVE_DIRECTIVE), /* harmony export */ "RESOLVE_DYNAMIC_COMPONENT": () => (/* binding */ RESOLVE_DYNAMIC_COMPONENT), +/* harmony export */ "RESOLVE_FILTER": () => (/* binding */ RESOLVE_FILTER), /* harmony export */ "SET_BLOCK_TRACKING": () => (/* binding */ SET_BLOCK_TRACKING), /* harmony export */ "SUSPENSE": () => (/* binding */ SUSPENSE), /* harmony export */ "TELEPORT": () => (/* binding */ TELEPORT), @@ -85,6 +86,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ "baseParse": () => (/* binding */ baseParse), /* harmony export */ "buildProps": () => (/* binding */ buildProps), /* harmony export */ "buildSlots": () => (/* binding */ buildSlots), +/* harmony export */ "checkCompatEnabled": () => (/* binding */ checkCompatEnabled), /* harmony export */ "createArrayExpression": () => (/* binding */ createArrayExpression), /* harmony export */ "createAssignmentExpression": () => (/* binding */ createAssignmentExpression), /* harmony export */ "createBlockStatement": () => (/* binding */ createBlockStatement), @@ -143,7 +145,8 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ "transformExpression": () => (/* binding */ transformExpression), /* harmony export */ "transformModel": () => (/* binding */ transformModel), /* harmony export */ "transformOn": () => (/* binding */ transformOn), -/* harmony export */ "traverseNode": () => (/* binding */ traverseNode) +/* harmony export */ "traverseNode": () => (/* binding */ traverseNode), +/* harmony export */ "warnDeprecation": () => (/* binding */ warnDeprecation) /* harmony export */ }); /* harmony import */ var _vue_shared__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @vue/shared */ "./node_modules/@vue/shared/dist/shared.esm-bundler.js"); @@ -152,6 +155,9 @@ __webpack_require__.r(__webpack_exports__); function defaultOnError(error) { throw error; } +function defaultOnWarn(msg) { + ( true) && console.warn(`[Vue warn] ${msg.message}`); +} function createCompilerError(code, loc, messages, additionalMessage) { const msg = true ? (messages || errorMessages)[code] + (additionalMessage || ``) @@ -184,6 +190,7 @@ const errorMessages = { [18 /* UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE */]: 'Unquoted attribute value cannot contain U+0022 ("), U+0027 (\'), U+003C (<), U+003D (=), and U+0060 (`).', [19 /* UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME */]: "Attribute name cannot start with '='.", [21 /* UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME */]: "' !nonIdentifierRE.test(name); -const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[[^\]]+\])*$/; +const memberExpRE = /^[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*(?:\s*\.\s*[A-Za-z_$\xA0-\uFFFF][\w$\xA0-\uFFFF]*|\[(.+)\])*$/; const isMemberExpression = (path) => { if (!path) return false; - return memberExpRE.test(path.trim()); + const matched = memberExpRE.exec(path.trim()); + if (!matched) + return false; + if (!matched[1]) + return true; + if (!/[\[\]]/.test(matched[1])) + return true; + return isMemberExpression(matched[1].trim()); }; function getInnerRange(loc, offset, length) { const source = loc.source.substr(offset, length); @@ -690,6 +708,103 @@ function hasScopeRef(node, ids) { } } +const deprecationData = { + ["COMPILER_IS_ON_ELEMENT" /* COMPILER_IS_ON_ELEMENT */]: { + message: `Platform-native elements with "is" prop will no longer be ` + + `treated as components in Vue 3 unless the "is" value is explicitly ` + + `prefixed with "vue:".`, + link: `https://v3.vuejs.org/guide/migration/custom-elements-interop.html` + }, + ["COMPILER_V_BIND_SYNC" /* COMPILER_V_BIND_SYNC */]: { + message: key => `.sync modifier for v-bind has been removed. Use v-model with ` + + `argument instead. \`v-bind:${key}.sync\` should be changed to ` + + `\`v-model:${key}\`.`, + link: `https://v3.vuejs.org/guide/migration/v-model.html` + }, + ["COMPILER_V_BIND_PROP" /* COMPILER_V_BIND_PROP */]: { + message: `.prop modifier for v-bind has been removed and no longer necessary. ` + + `Vue 3 will automatically set a binding as DOM property when appropriate.` + }, + ["COMPILER_V_BIND_OBJECT_ORDER" /* COMPILER_V_BIND_OBJECT_ORDER */]: { + message: `v-bind="obj" usage is now order sensitive and behaves like JavaScript ` + + `object spread: it will now overwrite an existing non-mergeable attribute ` + + `that appears before v-bind in the case of conflict. ` + + `To retain 2.x behavior, move v-bind to make it the first attribute. ` + + `You can also suppress this warning if the usage is intended.`, + link: `https://v3.vuejs.org/guide/migration/v-bind.html` + }, + ["COMPILER_V_ON_NATIVE" /* COMPILER_V_ON_NATIVE */]: { + message: `.native modifier for v-on has been removed as is no longer necessary.`, + link: `https://v3.vuejs.org/guide/migration/v-on-native-modifier-removed.html` + }, + ["COMPILER_V_IF_V_FOR_PRECEDENCE" /* COMPILER_V_IF_V_FOR_PRECEDENCE */]: { + message: `v-if / v-for precedence when used on the same element has changed ` + + `in Vue 3: v-if now takes higher precedence and will no longer have ` + + `access to v-for scope variables. It is best to avoid the ambiguity ` + + `with