sergeybodin 2023-08-24 16:57:26 +03:00
commit c49a706c79
27 changed files with 252 additions and 61 deletions

View File

@ -0,0 +1,65 @@
<?php
namespace App\Imports;
use App\Models\Asset;
use App\Models\Registries\Registry;
use App\Models\Registries\RegistryType;
use App\Services\Documents\DocumentDownloadService;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\ToCollection;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
class NtdRegistryImport extends Import implements ToCollection, WithHeadingRow {
public Registry $registry;
public array $mapper = [
'name' => ['prop' => 'name', 'required' => true],
'link' => ['prop' => 'link'],
'active_since' => ['prop' => 'active_since', 'date' => true],
'active_till' => ['prop' => 'active_till', 'date' => true],
'category' => ['prop' => 'cat_name', 'required' => true],
'type' => ['prop' => 'type'],
'agency' => ['prop' => 'agency']
];
public function __construct() {
$this->registry = Registry::query()->where(['type' => RegistryType::NTD])->first();
}
public function collection(Collection $collection) {
$collection->each(function($row) use(&$data) {
if ($row = $this->mapData($row)) {
$this->importRow($row);
}
});
}
public function importRow($row) {
$category = $this->registry->categories()->firstOrCreate(['name' => $row['category']]);
$entry = $this->registry->entries()->firstOrCreate(['name' => $row['name'], 'category_id' => $category->id ?? 0]);
$active_since = (is_object($row['active_since'] ?? null)) ? $row['active_since'] : null;
$active_till = (is_object($row['active_till'] ?? null)) ? $row['active_till'] : null;
$asset = $this->download($row['link'] ?? null);
$link = $this->checkLink($row['link']);
$entry->update(['active_since' => $active_since, 'active_till' => $active_till, 'asset_id' => $asset->id ?? null, 'link' => $link]);
$entry->properties->setValues(['normative-document-type' => ['title' => $row['type'] ?? null], 'host-agency' => ['title' => $row['agency'] ?? null]]);
}
public function download($url): ?Asset {
return (new DocumentDownloadService())->download($url, 'registries/ntd');
}
public function checkLink($link) {
$ext = pathinfo($link, PATHINFO_EXTENSION);
$host = parse_url($link, PHP_URL_HOST);
$result = null;
if ($ext !== 'pdf') {
if ($host && !in_array($host, ['faufcc.ru', 'www.faufcc.ru'])) $result = $link;
}echo ("{$result}\n");
return $result;
}
}

View File

@ -31,6 +31,7 @@ class Entry extends Model {
'asset_id',
'number',
'name',
'link',
'active_since',
'active_till',
'suspended_since',

View File

@ -15,6 +15,7 @@ class RegistryType {
public const DISCUSSIONS = 'discussions';
public const RESEARCHES = 'researches';
public const TECHNICAL_CERTIFICATES = 'technical-certificates';
public const NTD = 'ntd';
public const TITLES = [
self::SIMPLE => 'Простой реестр',
@ -27,7 +28,8 @@ class RegistryType {
self::DEVELOPMENTS => 'Реестр планов разработки',
self::DISCUSSIONS => 'Реестр публичных обсуждений',
self::RESEARCHES => 'Реестр исследований',
self::TECHNICAL_CERTIFICATES => 'Реестр технических свидетельств'
self::TECHNICAL_CERTIFICATES => 'Реестр технических свидетельств',
self::NTD => 'Реестр нормативно-технической документации'
];
public const OPTIONS = [
@ -39,9 +41,10 @@ class RegistryType {
self::EXPERTS => ['categories' => true, 'states' => true],
self::CERTIFICATES => ['categories' => true, 'properties' => 'entry-properties-certificate', 'states' => true],
self::COMPANIES => ['properties' => 'entry-properties-company'],
self::DEVELOPMENTS => ['categorized' => true, 'properties' => 'entry-properties-development'],
self::DEVELOPMENTS => ['categories' => true, 'properties' => 'entry-properties-development'],
self::DISCUSSIONS => ['properties' => 'entry-properties-discussion'],
self::RESEARCHES => ['categories' => true, 'properties' => 'entry-properties-research'],
self::TECHNICAL_CERTIFICATES => ['properties' => 'entry-properties-technical-certificate', 'states' => true]
self::TECHNICAL_CERTIFICATES => ['properties' => 'entry-properties-technical-certificate', 'states' => true],
self::NTD => ['categories' => true, 'properties' => 'entry-properties-ntd', 'states' => true]
];
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Services\Documents;
use App\Models\Asset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
class DocumentDownloadService {
protected array $mimes = [
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'pdf' => 'application/pdf',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
public function __construct() {
}
public function download($url, $dir = null, $filename = null): ?Asset {
$info = pathinfo($url);
if (!empty($this->mimes[$info['extension'] ?? null])) {
$path = "public/documents";
$filename = $filename ? "{$filename}.{$info['extension']}" : $info['basename'];
$path = $dir ? "{$path}/{$dir}/{$filename}" : "{$path}/{$filename}";
$asset = Asset::query()->where(['path' => $path])->first();
if (!$asset && Storage::put($path, Http::get($url)->body())) $asset = $this->makeAsset($path);
elseif ($asset) var_dump($asset->path);
}
return $asset ?? null;
}
public function makeAsset($path, $name = null) {
$info = pathinfo($path);
return Asset::create([
'type' => 'document',
'path' => $path,
'mime' => $this->mimes[$info['extension']] ?? null,
'name' => $name ?? $info['basename'],
'filename' => $info['basename'],
'extension' => $info['extension'],
'user_id' => ($user = Auth::user()) ? $user->id : null
]);
}
}

View File

@ -2,8 +2,6 @@
namespace App\Services\Documents;
use App\Models\Asset;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@ -12,12 +10,6 @@ class DocumentGeneratorService {
public array $data;
public array $options = [];
public array $mimes = [
'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'pdf' => 'application/pdf',
'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
];
public static array $classes = [
'pdf' => GeneratePdfDocument::class,
'word' => GenerateWordDocument::class
@ -44,15 +36,6 @@ class DocumentGeneratorService {
}
public function makeAsset($path, $name) {
$info = pathinfo($path);
return Asset::create([
'type' => 'document',
'path' => $path,
'mime' => $this->mimes[$info['extension']] ?? null,
'name' => $name ?? $info['basename'],
'filename' => $info['basename'],
'extension' => $info['extension'],
'user_id' => ($user = Auth::user()) ? $user->id : null
]);
return (new DocumentDownloadService())->makeAsset($path, $name);
}
}

View File

@ -123,7 +123,7 @@ class EntryFilters extends FiltersService {
if ($exceptNative) $filters = $filters->filter(function($val, $prop) {
return Field::byName($prop)->exists();
});
return $filters->except('registry', 'category', 'types', 'state')->filter(function($val) {
return $filters->except('registry', 'category', 'types')->filter(function($val) {
return collect($val)->filter(function($val) {return $val;})->isNotEmpty();
})->isEmpty();
}

View File

@ -14,6 +14,7 @@ use App\Transformers\Objects\FieldTransformer;
use App\Transformers\Objects\ObjectPropertyTransformer;
use App\Transformers\Registries\EntryTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Str;
class EntryForms extends FormsService {
public array $formTitles = ['create' => 'Создание записи', 'update' => 'Редактирование записи'];
@ -34,7 +35,8 @@ class EntryForms extends FormsService {
[
'name' => 'name',
'title' => 'Наименование записи',
'type' => FieldType::STRING,
'type' => FieldType::TEXT,
'maxLength' => 750,
'required' => true,
'value' => $model->name ?? null
],
@ -49,6 +51,13 @@ class EntryForms extends FormsService {
'title' => 'Документ',
'type' => FieldType::DOCUMENT,
'value' => ($asset = $model->asset ?? null) ? fractal($asset, new AssetTransformer()) : null
],
[
'name' => 'link',
'title' => 'Ссылка',
'type' => FieldType::STRING,
'maxLength' => 750,
'value' => $model->link ?? null
]
];
return ['data' => $fields];
@ -106,6 +115,7 @@ class EntryForms extends FormsService {
$category = Category::byUuid($data['category'] ?? null)->first();
$data['asset_id'] = ($asset = Asset::byUuid($data['asset'] ?? null)->first()) ? $asset->id : null;
$data['category_id'] = $category->id ?? 0;
$data['link'] = !empty($data['link']) ? Str::replace(env('APP_URL'), '', $data['link']) : null;
$model = $registry->entries()->create($data);
if ($object = $model->properties ?? null) $object->setValues($data);
return fractal($model, new EntryTransformer())->respond();
@ -114,6 +124,7 @@ class EntryForms extends FormsService {
public function update(string $id, array $data): ?JsonResponse {
$model = Entry::byUuid($id)->firstOrFail();
$data['asset_id'] = ($asset = Asset::byUuid($data['asset'] ?? null)->first()) ? $asset->id : null;
$data['link'] = !empty($data['link']) ? Str::replace(env('APP_URL'), '', $data['link']) : null;
$model->update($data);
if ($object = $model->properties ?? null) $object->setValues($data);
return fractal($model->fresh(), new EntryTransformer())->respond();

View File

@ -0,0 +1,34 @@
<?php
namespace App\Services\Registries;
use Illuminate\Support\Str;
class DevelopmentsImportService extends RegistryImportService {
public function test() {
}
public function import() {
$nodes = $this->dom->find('#part2 tbody tr')->toArray();
foreach ($nodes as $node) {
list($num, $name, $year, $developer, $funding) = $node->find('td')->toArray();
$type = $this->getOperationType($name->text);
$category = $this->registry->addCategory(trim($year->text));
$entry = $category->entries()->firstOrCreate(['registry_id' => $category->registry_id, 'name' => trim($name->text)]);
$entry->properties->setValues(['operation-type' => ['title' => $type], 'primary-developer' => trim($developer->text),
'funding-source' => ['title' => trim($funding->text)], 'plan-year' => intval($year->text)]);
}
}
public function getOperationType($name) {
$types = ['Пересмотр', 'Изменение'];
$result = 'Разработка';
foreach ($types as $type) {
if (str_contains(Str::lower($name), Str::lower($type))) $result = $type;
}
return $result;
}
}

View File

@ -4,9 +4,7 @@ namespace App\Services\Registries;
use App\Models\Asset;
use App\Models\Registries\Registry;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use App\Services\Documents\DocumentDownloadService;
use PHPHtmlParser\Dom;
class RegistryImportService {
@ -30,34 +28,7 @@ class RegistryImportService {
public function download($url, $dir = null, $filename = null): ?Asset {
$urlInfo = parse_url($url);
if (empty($urlInfo['host'])) {
$url = str_replace('//', '/', "faufcc.ru/{$url}");
$url = "https://{$url}";
}
$info = pathinfo($url);
if ($info['extension'] ?? null) {
$path = "public/documents/registries";
$filename = $filename ? "{$filename}.{$info['extension']}" : $info['basename'];
$path = $dir ? "{$path}/{$dir}/{$filename}" : "{$path}/{$filename}";
$asset = Asset::query()->where(['path' => $path])->first();
if (!$asset && Storage::put($path, Http::get($url)->body())) $asset = $this->makeAsset($path);
elseif ($asset) var_dump($asset->path);
}
return $asset ?? null;
return (new DocumentDownloadService())->download($url, $dir, $filename);
}
public function makeAsset($path, $name = null) {
$info = pathinfo($path);
return Asset::create([
'type' => 'document',
'path' => $path,
'mime' => $this->mimes[$info['extension']] ?? null,
'name' => $name ?? $info['basename'],
'filename' => $info['basename'],
'extension' => $info['extension'],
'user_id' => ($user = Auth::user()) ? $user->id : null
]);
}
}

View File

@ -100,7 +100,7 @@ class RulesetImportService extends RegistryImportService {
$data['order-name'] = $orderName;
$data['order-date'] = $orderDate ? Date::create($orderDate) : null;
$filename = $orderName ? Str::slug("Приказ {$orderName} от {$orderDate}") : null;
$data['order-document'] = $this->download($link->href, 'ruleset', $filename);
$data['order-document'] = $this->download($link->href, 'registries/ruleset', $filename);
} else $data['listings'][] = ['name' => Str::replace('Постановление правительства №', 'pp', $link->text)];
}
} else {

View File

@ -25,6 +25,7 @@ class EntryTransformer extends TransformerAbstract {
'id' => $model->uuid,
'number' => $model->number,
'name' => $model->name,
'link' => $model->link,
'state' => $model->state,
'active_since' => $model->active_since ? $model->active_since->toIso8601String() : null,
'active_till' => $model->active_till ? $model->active_till->toIso8601String() : null,

View File

@ -21,6 +21,7 @@ class CreateRegistryEntriesTable extends Migration
$table->integer('asset_id')->index()->nullable();
$table->string('number')->index()->nullable();
$table->string('name', 750)->index()->nullable();
$table->string('link', 750)->nullable();
$table->date('active_since')->index()->nullable();
$table->date('active_till')->index()->nullable();
$table->date('suspended_since')->index()->nullable();

View File

@ -45,6 +45,25 @@ class DictionariesTableSeeder extends Seeder {
'title' => 'Виды исследовательских работ',
'items' => ['nir' => 'НИР', 'niokr' => 'НИОКР']
],
'funding-sources' => [
'title' => 'Источники финансирования',
'items' => ['budget' => 'Федеральный бюджет', 'non-budget' => 'Внебюджетные средства']
],
'normative-document-types' => [
'title' => 'Виды нормативно-техничесих документов',
'items' => ['sp' => 'СП', 'gost' => 'ГОСТ', 'sn' => 'СН', 'rk-eek' => 'Решение коллегии ЕЭК', 'gost-r' => 'ГОСТ Р',
'sanpin' => 'СанПиН', 'fnip' => 'ФНиП', 'foiv' => 'Приказ ФОИВа', 'pp-rf' => 'Постановление Правительства РФ',
'rp-rf' => 'Распоряжение Правительства РФ', 'tr-eaes' => 'Технический регламент ЕАЭС', 'fz' => 'Федеральный закон',
'sto' => 'СТО (ПБЯ)', 'rs-eek' => 'Решение Совета ЕЭК', 'st-sev' => 'СТ СЭВ']
],
'host-agencies' => [
'title' => 'Исполнительные органы',
'items' => ['minstroy' => 'Минстрой России', 'gov-rf' => 'Правительство РФ', 'kol-eek' => 'Коллегия ЕЭК', 'mintrans' => 'Минтранс России',
'mchs' => 'МЧС России', 'mincult' => 'Минкультуры России', 'minprirody' => 'Минприроды России', 'rospotreb' => 'Роспотребнадзор',
'rosstandart' => 'Росстандарт', 'rostechnadzor' => 'Ростехнадзор', 'rosatom' => 'ГК «Росатом»', 'president' => 'Президент Российской Федерации',
'minenergo' => 'Минэнерго России', 'sovet-eek' => 'Совет ЕЭК', 'minselhoz' => 'Минсельхоз России', 'mintrud' => 'Минтруд России',
'rosavia' => 'Росавиация']
],
'moderate-permissions' => [
'title' => 'Права',
'items' => ['applications' => 'Рассмотрение предварительных заявок']

View File

@ -234,7 +234,12 @@ class FieldsTableSeeder extends Seeder {
],
'funding-source' => [
'title' => 'Источник финансирования',
'type' => FieldType::STRING
'type' => FieldType::RELATION,
'params' => [
'appearance' => 'radio',
'related' => DictionaryItem::class, 'transformer' => DictionaryItemTransformer::class,
'options' => ['show' => true, 'whereHas' => ['dictionary' => ['name' => 'funding-sources']]]
]
],
'plan-year' => [
'title' => 'Год плана',
@ -282,6 +287,23 @@ class FieldsTableSeeder extends Seeder {
'type' => FieldType::STRING
],
'normative-document-type' => [
'title' => 'Тип документа',
'type' => FieldType::RELATION,
'params' => [
'related' => DictionaryItem::class, 'transformer' => DictionaryItemTransformer::class,
'options' => ['show' => true, 'whereHas' => ['dictionary' => ['name' => 'normative-document-types']]]
]
],
'host-agency' => [
'title' => 'Принявший орган',
'type' => FieldType::RELATION,
'params' => [
'related' => DictionaryItem::class, 'transformer' => DictionaryItemTransformer::class,
'options' => ['show' => true, 'whereHas' => ['dictionary' => ['name' => 'host-agencies']]]
]
],
'operation-type' => [
'title' => 'Вид работы',
'type' => FieldType::RELATION,

View File

@ -99,6 +99,11 @@ class ObjectTypeFieldsTableSeeder extends Seeder {
'company-email', 'company-phone', 'producer-name', 'producer-address']
]
],
'entry-properties-ntd' => [
'common' => [
'fields' => ['normative-document-type', 'host-agency']
]
],
'entry-operation-ruleset' => [
'common' => [

View File

@ -93,6 +93,9 @@ class ObjectTypesTableSeeder extends Seeder {
],
'entry-properties-technical-certificate' => [
'title' => 'Техническое свидетельство'
],
'entry-properties-ntd' => [
'title' => 'Нормативно-технический документ'
]
]
],

View File

@ -20,11 +20,13 @@ class PagesTableSeeder extends Seeder
'children' => [
'Документы об учреждении' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::SIMPLE],
'Нормативные правовые акты' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::SIMPLE],
'Наблюдательный совет' => [],
'Государственное задание' => [],
'Наблюдательный совет' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Государственное задание' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Закупки' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Бухгалтерская отчетность' => [],
'Антимонопольное законодательство' => [],
'Бухгалтерская отчетность' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Антимонопольное законодательство' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Приказы Минстроя России' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Результаты специальной оценки труда' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::SIMPLE]
]
],
'Противодействие коррупции' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
@ -54,7 +56,7 @@ class PagesTableSeeder extends Seeder
'Нормирование и стандартизация' => [
'children' => [
'Реестр сводов правил' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::RULESET],
'Реестр нормативно-технической документации' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::CATEGORIZED],
'Реестр нормативно-технической документации' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::NTD],
'Разработка сводов правил' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::DEVELOPMENTS],
'Прикладные исследования' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::RESEARCHES],
'Методические материалы' => ['type' => PageType::REGISTRY, 'registry_type' => RegistryType::SIMPLE]

View File

@ -1,10 +1,14 @@
<?php
use App\Imports\CompaniesImport;
use App\Models\Registries\Registry;
use App\Models\Registries\RegistryType;
use App\Models\User;
use App\Services\Registries\DevelopmentsImportService;
use App\Services\Registries\RulesetImportService;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Storage;
use Maatwebsite\Excel\Facades\Excel;
/*
|--------------------------------------------------------------------------
@ -33,4 +37,21 @@ Artisan::command('htmlparser:import-rulesets', function() {
$service->import();
});
Artisan::command('htmlparser:import-developments', function() {
$registry = Registry::query()->where(['type' => RegistryType::DEVELOPMENTS])->first();
$urls = [
"https://www.faufcc.ru/_deyatelnost/_tehnicheskoe_normirovanie/_razrabotka_svodov_pravil/",
"https://www.faufcc.ru/_deyatelnost/_tehnicheskoe_normirovanie/_razrabotka_svodov_pravil/?PAGEN_1=2",
"https://www.faufcc.ru/_deyatelnost/_tehnicheskoe_normirovanie/_razrabotka_svodov_pravil/?PAGEN_1=3"
];
foreach ($urls as $url) {
$service = new DevelopmentsImportService($registry, $url);
$service->import();
}
});
Artisan::command('dev:import-ntd', function() {
Excel::import(new \App\Imports\NtdRegistryImport(), Storage::path('import/registries/ntd.xlsx'));
});

Binary file not shown.

View File

@ -0,0 +1 @@
*