diff --git a/app/Http/Controllers/Web/GroupController.php b/app/Http/Controllers/Web/GroupController.php index c3469a0..6c1e424 100644 --- a/app/Http/Controllers/Web/GroupController.php +++ b/app/Http/Controllers/Web/GroupController.php @@ -43,7 +43,7 @@ class GroupController extends Controller 'title.tm' => ['required', 'min:3'], 'title.ru' => ['required', 'min:3'], 'title.en' => ['required', 'min:3'], - 'type' => ['string', 'in:import,export'], + 'type' => ['string', 'in:import,export,trading'], 'is_default' => ['boolean'], ]); @@ -74,7 +74,7 @@ class GroupController extends Controller 'title.ru' => ['required', 'min:3'], 'title.en' => ['required', 'min:3'], 'is_default' => ['boolean'], - 'type' => ['string', 'in:import,export'], + 'type' => ['string', 'in:import,export,trading'], ]); info($request->all()); diff --git a/app/Http/Controllers/Web/TradingController.php b/app/Http/Controllers/Web/TradingController.php new file mode 100644 index 0000000..928ceb9 --- /dev/null +++ b/app/Http/Controllers/Web/TradingController.php @@ -0,0 +1,107 @@ +merge([ + 'group' => optional(Group::whereType('trading')->where('is_default', true)->first())->id + ]); + } + $subgroupsWithTradings = Subgroup::with(['tradings' => function ($query) { + $query + ->where('group_id', request('group')) + ->where('locale', app()->getLocale()); + }]) + ->where('group_id', request('group')) + ->where('locale', app()->getLocale()) + ->simplePaginate(50); + + $groups = Group::whereType('trading')->get(); + + $filters = array_filter(request()->all([ + 'group', + ])); + + if (array_key_exists('category', $filters)) { + $filters['category'] = intval($filters['category']); + } + + if (array_key_exists('group', $filters)) { + $filters['group'] = intval($filters['group']); + } + + return Inertia::render('Tradings', [ + 'text' => settings('text')[app()->getLocale()], + 'filters' => $filters, + 'subgroupsWithTradings' => SubgroupResource::collection($subgroupsWithTradings), + 'groups' => fn () => $groups, + ]); + } + + /** + * Show the form for creating a new resource. + * + * @return \Illuminate\Http\Response + */ + public function import() + { + request()->validate([ + 'group' => ['exists:groups,id'], + 'file' => ['required', 'mimes:xlsx'], + ]); + + if (!$group = Group::find(request('group'))) { + $group = Group::create([ + 'title' => 'New group', + 'type' => 'export', + 'is_default' => true + ]); + } + + $group->tradings()->whereLocale(app()->getLocale())->delete(); + + try { + $id = now()->unix(); + session(['import' => $id]); + + $file = request()->file('file')->storeAs('uploads', $filename = $group->filename); + $group->update(['file' => $filename]); + + Excel::queueImport(new TradingsImport($id, $group, app()->getLocale()), $file); + } catch (\Throwable $th) { + info('error here'); + info($th->getMessage()); + } + + return redirect()->route('tradings'); + } + + public function status() + { + $id = session('import'); + + return response([ + 'started' => filled(cache("start_date_$id")), + 'finished' => filled(cache("end_date_$id")), + 'current_row' => (int) cache("current_row_$id"), + 'total_rows' => (int) cache("total_rows_$id"), + ]); + } +} diff --git a/app/Http/Middleware/VerifyCsrfToken.php b/app/Http/Middleware/VerifyCsrfToken.php index 6d1b985..3417be8 100644 --- a/app/Http/Middleware/VerifyCsrfToken.php +++ b/app/Http/Middleware/VerifyCsrfToken.php @@ -14,5 +14,6 @@ class VerifyCsrfToken extends Middleware protected $except = [ '/exports/import', '/imports/import', + '/tradings/import', ]; } diff --git a/app/Http/Resources/SubgroupResource.php b/app/Http/Resources/SubgroupResource.php new file mode 100644 index 0000000..1c3e9bb --- /dev/null +++ b/app/Http/Resources/SubgroupResource.php @@ -0,0 +1,24 @@ + $this->id, + 'deals_count' => $this->deals_count, + 'total_sum' => $this->total_sum, + 'tradings' => TradingResource::collection($this->tradings) + ]; + } +} diff --git a/app/Http/Resources/TradingResource.php b/app/Http/Resources/TradingResource.php new file mode 100644 index 0000000..d0e64cf --- /dev/null +++ b/app/Http/Resources/TradingResource.php @@ -0,0 +1,19 @@ +id = $id; + $this->group = $group->id; + $this->locale = $locale; + $this->categories = Category::all(); + $this->tradingsInserts = []; + } + + public function sheets(): array + { + return [ + 0 => $this, + ]; + } + + public function startRow(): int + { + return 3; + } + + public function chunkSize(): int + { + return 1000; + } + + public function registerEvents(): array + { + return [ + BeforeImport::class => function (BeforeImport $event) { + $totalRows = $event->getReader()->getTotalRows(); + + if (filled($totalRows)) { + cache()->forever("total_rows_{$this->id}", array_values($totalRows)[0]); + cache()->forever("start_date_{$this->id}", now()->unix()); + } + }, + AfterImport::class => function (AfterImport $event) { + cache(["end_date_{$this->id}" => now()], now()->addMinute()); + cache()->forget("total_rows_{$this->id}"); + cache()->forget("start_date_{$this->id}"); + cache()->forget("current_row_{$this->id}"); + }, + ]; + } + + public function onRow(Row $row) + { + $rowIndex = $row->getIndex(); + $row = array_map('trim', $row->toArray()); + cache()->forever("current_row_{$this->id}", $rowIndex); + + if (!empty($row[0])) { + $this->setCategory($row); + $this->setCurrency($row); + $this->setType($row); + + $this->setTradingsInSubgroup($row); + + return; + } + + if(empty($row[0]) && empty($row[1]) && empty($row[3]) && !empty($row[4])) { + $this->setTotalSumInSubgroup($row); + return; + } + + $row['group'] = $this->group; + $row['category'] = $this->category; + $row['currency'] = $this->currency; + $row['locale'] = $this->locale; + $row['type'] = $this->type; + + /** + * At first tradings are saved in an array, then in DB when subgroup ends + */ + array_push($this->tradingsInserts, [ + 'locale' => $row['locale'], + 'category_id' => $row['category'], + 'group_id' => $row['group'], + 'subgroup_id' => null, + 'type' => $row['type'], + 'currency' => $row['currency'], + 'title' => $row[1], + 'unit' => $row[2], + 'amount' => $row[3], + 'price' => $row[4], + 'seller_country' => $row[7], + 'buyer_country' => $row[9], + 'point' => $row[10], + ]); + } + + protected function setTotalSumInSubgroup($row) + { + if(strripos($row[4], 'Итого сумма') !== false || strripos($row[4], 'Total sum') !== false || strripos($row[4], 'Jemi') !== false) { + + $this->subgroup->update([ + 'total_sum' => $row[4] . ' ' . $row[5] + ]); + } + } + + /** + * Save all rows with tradings for one subgroup. + * The function works, when excel parser reaches the row with `Заключено сделок` + */ + protected function setTradingsInSubgroup($row) + { + if(strripos($row[0], 'Заключено сделок') !== false || strripos($row[0], 'Deals count') !== false || strripos($row[0], 'Tm translation') !== false) { + + $this->subgroup = Subgroup::create([ + 'deals_count' => $row[0], + 'group_id' => $this->group, + 'locale' => $this->locale + ]); + + foreach ($this->tradingsInserts as &$item) { + $item['subgroup_id'] = $this->subgroup->id; + } + + $this->subgroup->tradings()->createMany($this->tradingsInserts); + $this->tradingsInserts = []; + } + } + + protected function setCategory($row) + { + if ($category = $this->categories->first(fn ($c) => data_get($c->getOriginal('title'), $this->locale) == $row[0])) { + $this->category = $category->id; + } + } + + protected function setCurrency($row) + { + if (in_array($row[0], [ + 'Доллар США', + 'ABŞ-nyň dollary', + 'US dollar', + 'in US dollars', + ])) { + $this->currency = 'USD'; + } + + if (in_array($row[0], [ + 'türkmen manady', + 'Туркменский манат', + 'turkmen manats', + ])) { + $this->currency = 'TMT'; + } + } + + protected function setType($row) + { + if (in_array($row[0], [ + 'External', + 'Foreign', + 'Внешний', + 'Daşarky', + ])) { + $this->type = 'external'; + } + + if (in_array($row[0], [ + 'Internal', + 'internal', + 'Внутренний', + 'Içerki' + ])) { + $this->type = 'internal'; + } + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php index 4385946..44e2963 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -25,4 +25,9 @@ class Category extends Model { return $this->hasMany(Export::class); } + + public function tradings() + { + return $this->hasMany(Trading::class); + } } diff --git a/app/Models/Group.php b/app/Models/Group.php index 591b8d7..e9ae5e2 100644 --- a/app/Models/Group.php +++ b/app/Models/Group.php @@ -29,6 +29,11 @@ class Group extends Model return $this->hasMany(Export::class); } + public function tradings() + { + return $this->hasMany(Trading::class); + } + public function imports() { return $this->hasMany(Import::class); diff --git a/app/Models/Subgroup.php b/app/Models/Subgroup.php new file mode 100644 index 0000000..08867f4 --- /dev/null +++ b/app/Models/Subgroup.php @@ -0,0 +1,23 @@ +hasMany(Trading::class); + } + + public function group() + { + return $this->belongsTo(Group::class); + } +} diff --git a/app/Models/Trading.php b/app/Models/Trading.php new file mode 100644 index 0000000..75ef262 --- /dev/null +++ b/app/Models/Trading.php @@ -0,0 +1,36 @@ + 'float', + 'total' => 'float', + 'amount' => 'float', + ]; + protected $appends = ['total']; + protected $with = ['category']; + + public function scopeLines($query) + { + return $query->where('is_line', true); + } + + public function category() + { + return $this->belongsTo(Category::class); + } + + public function getTotalAttribute() + { + return round($this->price * $this->amount, 2); + } +} diff --git a/database/migrations/2022_06_22_180158_change_type_column_in_groups_table.php b/database/migrations/2022_06_22_180158_change_type_column_in_groups_table.php new file mode 100644 index 0000000..64a9410 --- /dev/null +++ b/database/migrations/2022_06_22_180158_change_type_column_in_groups_table.php @@ -0,0 +1,33 @@ +id(); + $table->string('deals_count')->nullable(); + $table->string('total_sum')->nullable(); + + $table->unsignedBigInteger('group_id'); + $table->foreign('group_id')->references('id')->on('groups'); + + $table->enum('locale', ['en', 'ru', 'tm']) + ->default('tm') + ->index(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('subgroups'); + } +} diff --git a/database/migrations/2022_06_24_200944_create_tradings_table.php b/database/migrations/2022_06_24_200944_create_tradings_table.php new file mode 100644 index 0000000..3327548 --- /dev/null +++ b/database/migrations/2022_06_24_200944_create_tradings_table.php @@ -0,0 +1,50 @@ +id(); + $table->foreignId('group_id')->nullable()->constrained()->onDelete('cascade'); + + $table->unsignedBigInteger('subgroup_id'); + $table->foreign('subgroup_id')->references('id')->on('subgroups'); + + $table->foreignId('category_id')->nullable()->constrained()->onDelete('cascade'); + $table->enum('type', ['internal', 'external'])->nullable(); + $table->text('title'); + $table->string('unit')->nullable(); + $table->string('amount')->nullable(); + $table->string('currency')->nullable(); + $table->string('seller_country')->nullable(); + $table->string('buyer_country')->nullable(); + $table->string('point')->nullable(); + $table->string('price')->nullable(); + $table->boolean('is_line')->default(false); + $table->enum('locale', ['en', 'ru', 'tm']) + ->default('tm') + ->index(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('tradings'); + } +} diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue index aad4681..028131e 100644 --- a/resources/js/Layouts/AppLayout.vue +++ b/resources/js/Layouts/AppLayout.vue @@ -55,6 +55,10 @@ export default { label: this.trans("Exports"), path: "exports", }, + { + label: this.trans("Trading"), + path: "tradings", + }, { label: this.trans("Requests"), path: "requests", diff --git a/resources/js/Pages/Groups.vue b/resources/js/Pages/Groups.vue index 524493f..cacc8d0 100644 --- a/resources/js/Pages/Groups.vue +++ b/resources/js/Pages/Groups.vue @@ -12,6 +12,9 @@ {{ trans("Export") }} + + {{ trans("Trading") }} + {{ trans("Create") diff --git a/resources/js/Pages/Tradings.vue b/resources/js/Pages/Tradings.vue new file mode 100644 index 0000000..3be1a68 --- /dev/null +++ b/resources/js/Pages/Tradings.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/resources/js/data/trading-columns.json b/resources/js/data/trading-columns.json new file mode 100644 index 0000000..67d0efe --- /dev/null +++ b/resources/js/data/trading-columns.json @@ -0,0 +1,81 @@ +[ + { + "key": "id", + "title": "№", + "align": "center", + "visible": true, + "scopedSlots": { + "customRender": "id" + }, + "width": "1%", + "opacity": 0 + }, + { + "dataIndex": "title", + "key": "title", + "title": "Title", + "visible": true, + "width": "30%" + }, + { + "dataIndex": "category", + "key": "category", + "title": "Category", + "visible": true, + "scopedSlots": { + "customRender": "category" + }, + "width": "9%" + }, + { + "dataIndex": "amount", + "key": "amount", + "title": "Amount", + "scopedSlots": { + "customRender": "amount" + }, + "visible": true, + "width": "9%" + }, + { + "dataIndex": "price", + "key": "price", + "title": "Price", + "scopedSlots": { + "customRender": "price" + }, + "visible": true, + "width": "9%" + }, + { + "dataIndex": "total", + "key": "total", + "title": "Total", + "scopedSlots": { + "customRender": "price" + }, + "visible": true, + "width": "9%" + }, + { + "dataIndex": "seller_country", + "key": "seller_country", + "title": "Seller country", + "visible": true, + "width": "7%" + }, + { + "dataIndex": "buyer_country", + "key": "buyer_country", + "title": "Buyer country", + "visible": true, + "width": "7%" + }, + { + "dataIndex": "point", + "key": "point", + "title": "Point", + "visible": true, + "width": "14%" + } +] diff --git a/resources/lang/en.json b/resources/lang/en.json index 5d93117..acbe20c 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -69,5 +69,8 @@ "First name": "First name", "Last name": "Last name", "Organization type": "Organization type", - "Unauthorized": "Unauthorized" + "Unauthorized": "Unauthorized", + "Seller country": "Seller country", + "Buyer country": "Buyer country", + "Trading": "Trading" } diff --git a/resources/lang/ru.json b/resources/lang/ru.json index d2123e3..e612855 100644 --- a/resources/lang/ru.json +++ b/resources/lang/ru.json @@ -69,5 +69,8 @@ "First name": "Имя", "Last name": "Фамилия", "Organization type": "Тип организации", - "Unauthorized": "Неверные данные" + "Unauthorized": "Неверные данные", + "Seller country": "Страна продавца", + "Buyer country": "Страна покупателя", + "Trading": "Торги" } diff --git a/resources/lang/tm.json b/resources/lang/tm.json index 1ba1c38..052b102 100644 --- a/resources/lang/tm.json +++ b/resources/lang/tm.json @@ -69,5 +69,8 @@ "First name": "Adyňyz", "Last name": "Familiýaňyz", "Organization type": "Edaraň görnüşi", - "Unauthorized": "Nädogry maglumat" + "Unauthorized": "Nädogry maglumat", + "Seller country": "Satyjynyň yurdy", + "Buyer country": "Alyjynyň yurdy", + "Trading": "Söwda" } diff --git a/routes/web.php b/routes/web.php index 1b7f393..7349087 100644 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use App\Http\Controllers\Web\ImportController; use App\Http\Controllers\Web\RequestController; use App\Http\Controllers\Web\SettingController; use App\Http\Controllers\Web\CategoryController; +use App\Http\Controllers\Web\TradingController; use App\Models\Export; /* @@ -25,6 +26,7 @@ use App\Models\Export; // Route::get('/', [HomeController::class, 'index'])->name('home'); Route::group(['middleware' => 'check_october_session'], function () { Route::get('imports', [ImportController::class, 'index'])->name('imports'); + Route::get('tradings', [TradingController::class, 'index'])->name('tradings'); Route::get('/', [ExportController::class, 'index'])->name('exports'); Route::get('download/{group}', [GroupController::class, 'download'])->name('download'); Route::post('requests', [RequestController::class, 'store'])->name('requests.store'); @@ -35,6 +37,7 @@ Route::group(['middleware' => 'check_october_session'], function () { Route::group(['middleware' => 'auth:sanctum'], function () { Route::post('imports/import', [ImportController::class, 'import'])->name('imports.import'); Route::post('exports/import', [ExportController::class, 'import'])->name('exports.import'); + Route::post('tradings/import', [TradingController::class, 'import'])->name('tradings.import'); Route::get('import-status', [ExportController::class, 'status'])->name('import-status'); Route::get('lines', [LineController::class, 'index'])->name('lines');