Alexander Musikhin 1 неделя назад
Родитель
Сommit
adf1bc101e

+ 2 - 1
app/Events/SendWebSocketMessageEvent.php

@@ -29,7 +29,8 @@ class SendWebSocketMessageEvent implements ShouldBroadcastNow
      */
     public function broadcastOn(): Channel
     {
-        return new Channel(self::CHANNEL);    }
+        return new Channel(self::CHANNEL);
+    }
 
     public function broadcastWith(): array
     {

+ 12 - 0
app/Helpers/DateHelper.php

@@ -101,4 +101,16 @@ class DateHelper
         return $date->format('Y-m-d');
     }
 
+    /**
+     * @throws \DateMalformedStringException
+     */
+    public static function ISODateToExcelDate(string $isoDate): int
+    {
+        $date = new DateTime($isoDate);
+        $timestamp = $date->getTimestamp();
+        $excelDate = ($timestamp / 86400) + 25569;
+
+        return (int) round($excelDate);
+    }
+
 }

+ 1 - 1
app/Http/Controllers/ImportController.php

@@ -56,7 +56,7 @@ class ImportController extends Controller
     {
         // validate data
         $request->validate([
-            'type'        => 'required|in:orders,reclamations',
+            'type'        => 'required|in:orders,reclamations,mafs',
             'import_file' => 'required|file',
         ]);
 

+ 13 - 0
app/Http/Controllers/ProductSKUController.php

@@ -4,6 +4,7 @@ namespace App\Http\Controllers;
 
 use App\Helpers\DateHelper;
 use App\Http\Requests\ProductSKUStoreRequest;
+use App\Jobs\ExportMafJob;
 use App\Models\File;
 use App\Models\MafView;
 use App\Models\ProductSKU;
@@ -109,4 +110,16 @@ class ProductSKUController extends Controller
         $file->delete();
         return redirect()->route('product_sku.show', $product_sku);
     }
+
+    public function exportMaf(Request $request)
+    {
+        $filters['year'] = year();
+        ExportMafJob::dispatch($request->user()->id, $filters);
+        return redirect()->route('product_sku.index', session('gp_product_sku'))->with(['success' => 'Задача экспорта успешно создана!']);
+    }
+
+    public function importMaf()
+    {
+
+    }
 }

+ 42 - 0
app/Jobs/ExportMafJob.php

@@ -0,0 +1,42 @@
+<?php
+
+namespace App\Jobs;
+
+use App\Events\SendWebSocketMessageEvent;
+use App\Services\ExportMafService;
+use App\Services\ExportService;
+use Exception;
+use Illuminate\Contracts\Queue\ShouldQueue;
+use Illuminate\Foundation\Queue\Queueable;
+use Illuminate\Support\Facades\Log;
+
+class ExportMafJob implements ShouldQueue
+{
+    use Queueable;
+
+    /**
+     * Create a new job instance.
+     */
+    public function __construct(
+        private readonly int $userId,
+        private readonly array $filters
+    )
+    {}
+
+    /**
+     * Execute the job.
+     */
+    public function handle(): void
+    {
+        try {
+            $file = (new ExportMafService())->handle($this->userId);
+            Log::info('ExportMaf job done!');
+            Log::info($file);
+            event(new SendWebSocketMessageEvent('Экспорт завершён!', $this->userId, ['download' => $file]));
+        } catch (Exception $e) {
+            Log::info('ExportCatalog job failed!');
+            event(new SendWebSocketMessageEvent('Ошибка экспорта! ' . $e->getMessage(), $this->userId, ['error' => $e->getMessage()]));
+        }
+    }
+
+}

+ 4 - 0
app/Jobs/Import/ImportJob.php

@@ -4,6 +4,7 @@ namespace App\Jobs\Import;
 
 use App\Events\SendWebSocketMessageEvent;
 use App\Models\Import;
+use App\Services\ImportMafsService;
 use App\Services\ImportOrdersService;
 use App\Services\ImportReclamationsService;
 use Exception;
@@ -34,6 +35,9 @@ class ImportJob implements ShouldQueue
                 case 'orders':
                     (new ImportOrdersService($this->import))->handle();
                     break;
+                case 'mafs':
+                    (new ImportMafsService($this->import))->handle();
+                    break;
                 case 'reclamations':
                     (new ImportReclamationsService($this->import))->handle();
                     break;

+ 85 - 0
app/Services/ExportMafService.php

@@ -0,0 +1,85 @@
+<?php
+
+namespace App\Services;
+
+use App\Helpers\DateHelper;
+use App\Models\File;
+use App\Models\MafView;
+use Illuminate\Support\Facades\Storage;
+use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Style\Border;
+use PhpOffice\PhpSpreadsheet\Style\Color;
+use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
+use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
+use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
+
+class ExportMafService
+{
+    public function handle(int $userId): string
+    {
+
+        $inputFileType = 'Xlsx'; // Xlsx - Xml - Ods - Slk - Gnumeric - Csv
+        $inputFileName = './templates/Mafs.xlsx';
+
+        $reader = IOFactory::createReader($inputFileType);
+        $spreadsheet = $reader->load($inputFileName);
+        $sheet = $spreadsheet->getActiveSheet();
+        $sheet->getPageSetup()->setOrientation(PageSetup::ORIENTATION_LANDSCAPE);
+
+
+        $mafs = MafView::query()->get();
+        $i = 2;
+        foreach ($mafs as $maf) {
+            $sheet->setCellValue('A' . $i, $maf->id);
+            $sheet->setCellValue('B' . $i, $maf->year);
+            $sheet->setCellValue('C' . $i, $maf->district_name);
+            $sheet->setCellValue('D' . $i, $maf->area_name);
+            $sheet->setCellValue('E' . $i, $maf->object_address);
+            $sheet->setCellValue('F' . $i, $maf->order_number);
+            $sheet->setCellValue('G' . $i, $maf->status);
+            $sheet->setCellValue('H' . $i, $maf->rfid);
+            $sheet->setCellValue('I' . $i, $maf->factory_number);
+            $sheet->setCellValue('J' . $i, (is_string($maf->manufacture_date)) ? DateHelper::ISODateToExcelDate($maf->manufacture_date) : '');
+            $sheet->setCellValue('K' . $i, $maf->statement_number);
+            $sheet->setCellValue('L' . $i, (is_string($maf->statement_date)) ? DateHelper::ISODateToExcelDate($maf->statement_date) : '');
+            $sheet->setCellValue('M' . $i, $maf->upd_number);
+            $sheet->setCellValue('N' . $i, $maf->nomenclature_number);
+            $sheet->setCellValue('O' . $i, $maf->article);
+            $sheet->setCellValue('P' . $i, $maf->name_tz);
+            $sheet->setCellValue('Q' . $i, $maf->type_tz);
+            $sheet->setCellValue('R' . $i, $maf->type);
+            $sheet->setCellValue('S' . $i, $maf->manufacturer_name);
+            $sheet->setCellValue('T' . $i, $maf->comment);
+            $sheet->getStyle('J' . $i)
+                ->getNumberFormat()
+                ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
+            $sheet->getStyle('L' . $i)
+                ->getNumberFormat()
+                ->setFormatCode(NumberFormat::FORMAT_DATE_DDMMYYYY);
+            $i++;
+        }
+        $sheet->getStyle('A1:T' . $i - 1)->getBorders()->getAllBorders()->setBorderStyle(Border::BORDER_THIN)->setColor(new Color('777777'));
+
+        $fileName = 'export_mafs_' . date('Y-m-d_H-i-s') . '.xlsx';
+        $writer = new Xlsx($spreadsheet);
+        $fd = 'export';
+        Storage::disk('public')->makeDirectory($fd);
+        $fp = storage_path('app/public/export/') . $fileName;
+        Storage::disk('public')->delete($fd . '/' . $fileName);
+        $writer->save($fp);
+
+        // create zip archive
+        $fileModel = File::query()->create([
+            'link' => url('/storage/') . '/export/' . $fileName,
+            'path' => $fp,
+            'user_id' => $userId,
+            'original_name' => $fileName,
+            'mime_type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+        ]);
+
+
+        // return link
+        return $fileName;
+
+    }
+}

+ 1 - 1
app/Services/ExportOrdersService.php

@@ -48,7 +48,7 @@ class ExportOrdersService
 
             $sheet->setCellValue('A' . $i, $order->id);
             $sheet->setCellValue('B' . $i, $order->name);
-            $sheet->setCellValue('C' . $i, $order->user->name);
+            $sheet->setCellValue('C' . $i, $order?->user?->name);
             $sheet->setCellValue('D' . $i, $order->district->shortname);
             $sheet->setCellValue('E' . $i, $order->area->name);
             $sheet->setCellValue('F' . $i, $order->object_address);

+ 0 - 1
app/Services/ExportScheduleService.php

@@ -59,7 +59,6 @@ class ExportScheduleService
             $sheet->setCellValue('A' . $i, DateHelper::getHumanDayOfWeek($schedule->installation_date));
             $sheet->setCellValue('B' . $i, DateHelper::getHumanDate($schedule->installation_date, true));
 
-
             $sheet->setCellValue('C' . $i, $schedule->address_code);
             $sheet->setCellValue('D' . $i, $schedule->district->shortname);
             $sheet->setCellValue('E' . $i, $schedule->area->name);

+ 102 - 0
app/Services/ImportMafsService.php

@@ -0,0 +1,102 @@
+<?php
+
+namespace App\Services;
+
+use App\Helpers\DateHelper;
+use App\Models\Import;
+use App\Models\MafOrder;
+use App\Models\Order;
+use App\Models\Product;
+use App\Models\ProductSKU;
+use Exception;
+
+class ImportMafsService extends ImportBaseService
+{
+    const HEADERS = [
+        'ID'                            => 'products_sku.id',
+        'Год'                           => 'products_sku.year',
+        'Округ'                         => 'districts.shortname',
+        'Район'                         => 'areas.name',
+        'Адрес площадки'                => 'orders.object_address',
+        '№ заказа МАФ'                  => 'maf_orders.order_number',
+        'Статус'                        => 'products_sku.status',
+        'RFID'                          => 'products_sku.rfid',
+        'Номер фабрики'                 => 'products_sku.factory_number',
+        'Дата производства'             => 'products_sku.manufacture_date',
+        'Номер ведомости'               => 'products_sku.statement_number',
+        'Дата ведомости'                => 'products_sku.statement_date',
+        'Номер УПД'                     => 'products_sku.upd_number',
+        'Номер номенклатуры'            => 'products.nomenclature_number',
+        'Артикул'                       => 'products.article',
+        'Наименование по ТЗ'            => 'products.name_tz',
+        'Тип по ТЗ'                     => 'products.type_tz',
+        'Тип'                           => 'products.type',
+        'Наименование производителя'    => 'products.manufacturer_name',
+        'Примечания'                    => 'products_sku.comment',
+
+        // заголовки из файла для проверки и маппинга
+    ];
+
+
+    public function __construct(Import $import)
+    {
+        parent::__construct($import);
+        $this->headers = self::HEADERS;
+    }
+
+    public function handle(): bool
+    {
+        if(!$this->prepare()) {
+            return false;
+        }
+        $strNumber = 0;
+        $result = [
+            'mafsUpdated' => 0,
+        ];
+        foreach($this->rowIterator as $row){
+            $strNumber++;
+            $r = $this->rowToArray($row);
+            if($strNumber === 1) {
+                echo $this->import->log('Skip headers Row: ' . $strNumber);
+                continue;
+            }
+            try {
+
+                $logMessage = "Row $strNumber: " . $r['orders.object_address'] . '. ';
+                $id = (int) $r['products_sku.id'];
+                $data = [
+                    'rfid' => (string) $r['products_sku.rfid'],
+                    'factory_number'    => (string) $r['products_sku.factory_number'],
+                    'manufacture_date'  => (!is_null($r['products_sku.manufacture_date']) && DateHelper::isDate($r['products_sku.manufacture_date'])) ? DateHelper::getDateForDB($r['products_sku.manufacture_date']) : null,
+                    'statement_number'  => (string) $r['products_sku.statement_number'],
+                    'statement_date'    => (!is_null($r['products_sku.statement_date']) && DateHelper::isDate($r['products_sku.statement_date'])) ? DateHelper::getDateForDB((int)$r['products_sku.statement_date']) : null,
+                    'upd_number'        => (string) $r['products_sku.upd_number'],
+                    'comment'           => (string) $r['products_sku.comment'],
+                ];
+
+                $logMessage .= $r['products_sku.statement_date'] . ' ' . $r['products_sku.manufacture_date'] .' ';
+
+                $productSKU = ProductSKU::query()
+                    ->where('id', $id)
+                    ->first();
+                if ($productSKU) {
+                    $logMessage .= 'Found and updated product with id: ' . $productSKU->id;
+                    $productSKU->update($data);
+                    $productSKU->save();
+                } else {
+                    echo $this->import->log('Product sku with id ' . $id . ' NOT FOUND!', 'WARNING');
+                }
+
+                echo $this->import->log($logMessage);
+            } catch (\Exception $e) {
+                echo $this->import->log($e->getMessage(), 'WARNING');
+            }
+
+        }
+        echo $this->import->log(print_r($result, true));
+        $this->import->status = 'DONE';
+        $this->import->save();
+
+        return true;
+    }
+}

+ 1 - 1
resources/views/import/index.blade.php

@@ -36,7 +36,7 @@
                 <div class="modal-body">
                     <form action="{{ route('import.create') }}" method="post" enctype="multipart/form-data">
                         @csrf
-                        @include('partials.select', ['title' => 'Вкладка', 'name' => 'type', 'options' => ['orders' => 'Площадки', 'reclamations' => 'Рекламации']])
+                        @include('partials.select', ['title' => 'Вкладка', 'name' => 'type', 'options' => ['orders' => 'Площадки', 'reclamations' => 'Рекламации', 'mafs' => 'МАФы']])
                         @include('partials.input', ['title' => 'XLSX файл', 'name' => 'import_file', 'type' => 'file', 'required' => true])
                         @include('partials.submit', ['name' => 'Импорт'])
                     </form>

+ 55 - 38
resources/views/products_sku/index.blade.php

@@ -7,10 +7,14 @@
             <h3>МАФ</h3>
         </div>
         <div class="col-6 text-end">
-{{--            <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#addModal">--}}
-{{--                Добавить--}}
-{{--            </button>--}}
-
+            @if(hasRole('admin'))
+                <button type="button" class="btn btn-sm mb-1 btn-primary" data-bs-toggle="modal" data-bs-target="#importModal">
+                    Импорт
+                </button>
+                <button type="button" class="btn btn-sm mb-1 btn-primary" data-bs-toggle="modal" data-bs-target="#exportModal">
+                    Экспорт
+                </button>
+            @endif
         </div>
     </div>
 
@@ -26,40 +30,53 @@
     @include('partials.pagination', ['items' => $products_sku])
 
 
-    <!-- Модальное окно редактирования -->
-{{--    <div class="modal fade" id="addModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">--}}
-{{--        <div class="modal-dialog modal-fullscreen-sm-down modal-lg">--}}
-{{--            <div class="modal-content">--}}
-{{--                <div class="modal-header">--}}
-{{--                    <h1 class="modal-title fs-5" id="addModalLabel">Добавить заказ МАФ</h1>--}}
-{{--                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>--}}
-{{--                </div>--}}
-{{--                <div class="modal-body">--}}
-{{--                    <form action="{{ route('product_sku.store') }}" method="post">--}}
-{{--                        @csrf--}}
-{{--                        <div id="select_maf_form">--}}
-{{--                            <input type="text" class="form-control mb-2" placeholder="Поиск номенклатуры" id="search_maf">--}}
-{{--                            <select id="select_maf" class="form-select mb-3" multiple></select>--}}
-{{--                        </div>--}}
-{{--                        <div style="display:none;" id="sku_form">--}}
-{{--                            <a href="#" onclick="$('#sku_form').slideUp(); $('#select_maf_form').slideDown()">назад</a>--}}
-{{--                            <input type="hidden" id="product_id" name="product_id" value="">--}}
-{{--                            @include('partials.input', ['name' => 'product_name', 'title' => 'МАФ', 'disabled' => true])--}}
-{{--                            @include('partials.input', ['name' => 'rfid', 'title' => 'RFID', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'factory_number', 'title' => 'Номер фабрики', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'manufacture_date', 'title' => 'Дата производства', 'type' => 'date', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'service_life', 'title' => 'Срок службы', 'required' => true, 'type' => 'number'])--}}
-{{--                            @include('partials.input', ['name' => 'certificate_number', 'title' => 'Номер сертификата', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'certificate_date', 'title' => 'Дата сертификата', 'type' => 'date', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'certificate_issuer', 'title' => 'Орган сертификации', 'required' => true])--}}
-{{--                            @include('partials.input', ['name' => 'certificate_type', 'title' => 'Вид сертификации', 'required' => true])--}}
-{{--                            @include('partials.submit', ['name' => 'Добавить'])--}}
-{{--                        </div>--}}
-{{--                    </form>--}}
-{{--                </div>--}}
-{{--            </div>--}}
-{{--        </div>--}}
-{{--    </div>--}}
+    <!-- Модальное окно импорта-->
+    <div class="modal fade" id="importModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
+        <div class="modal-dialog modal-fullscreen-sm-down">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h1 class="modal-title fs-5" id="exampleModalLabel">Выберите файл для импорта</h1>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
+                </div>
+                <div class="modal-body">
+                    <form action="{{ route('import.create') }}" method="post" enctype="multipart/form-data">
+                        @csrf
+                        <input type="hidden" name="type" value="mafs">
+                        @include('partials.input', ['title' => 'XLSX файл', 'name' => 'import_file', 'type' => 'file', 'required' => true])
+                        @include('partials.submit', ['name' => 'Импорт'])
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
+
+    <!-- Модальное окно экспорта-->
+    <div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
+        <div class="modal-dialog modal-fullscreen-sm-down modal-lg">
+            <div class="modal-content">
+                <div class="modal-header">
+                    <h1 class="modal-title fs-5" id="exampleModalLabel">Экспорт</h1>
+                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
+                </div>
+                <div class="modal-body">
+                    <form action="{{ route('mafs.export') }}" method="post" enctype="multipart/form-data">
+                        @csrf
+                        <div class="d-none">
+{{--                            @if(request()->s)--}}
+{{--                                @include('partials.input', ['name' => 'filters[s]', 'title' => 'поиск', 'value' => request()->s])--}}
+{{--                            @endif--}}
+{{--                            @if(request()->filters)--}}
+{{--                                @foreach(request()->filters as $filterName => $filterValue)--}}
+{{--                                    @include('partials.input', ['name' => 'filters[' . $filterName .']', 'title' => $filterName, 'value' => $filterValue])--}}
+{{--                                @endforeach--}}
+{{--                            @endif--}}
+                        </div>
+                        @include('partials.submit', ['name' => 'Экспорт'])
+                    </form>
+                </div>
+            </div>
+        </div>
+    </div>
 
     @if($errors->count())
         @dump($errors)

+ 4 - 0
routes/web.php

@@ -142,6 +142,10 @@ Route::middleware('auth:web')->group(function () {
         Route::post('catalog-import', [ProductController::class, 'import'])->name('catalog.import');
         Route::post('catalog-export', [ProductController::class, 'export'])->name('catalog.export');
 
+        Route::post('mafs-import', [ProductSKUController::class, 'importMaf'])->name('mafs.import');
+        Route::post('mafs-export', [ProductSKUController::class, 'exportMaf'])->name('mafs.export');
+
+
 
         // Склад заказы МАФ
         Route::get('maf_orders', [MafOrderController::class, 'index'])->name('maf_order.index');

BIN
templates/Mafs.xlsx