|
|
@@ -2,27 +2,33 @@
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
-use App\Models\SparePartsView;
|
|
|
use App\Http\Requests\FilterRequest;
|
|
|
+use App\Models\CommonCatalogItem;
|
|
|
+use App\Models\SparePartsView;
|
|
|
+use App\Models\StockOrder;
|
|
|
+use Illuminate\Http\JsonResponse;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
|
|
class FilterController extends Controller
|
|
|
{
|
|
|
const DB_TABLES = [
|
|
|
- 'orders' => 'orders_view',
|
|
|
- 'product_sku' => 'mafs_view',
|
|
|
- 'products' => 'products',
|
|
|
- 'reclamations' => 'reclamations_view',
|
|
|
- 'maf_order' => 'maf_orders_view',
|
|
|
- 'import' => 'imports',
|
|
|
- 'responsibles' => 'responsibles_view',
|
|
|
- 'users' => 'users',
|
|
|
- 'contracts' => 'contracts',
|
|
|
- 'spare_parts' => 'spare_parts_view',
|
|
|
+ 'orders' => 'orders_view',
|
|
|
+ 'product_sku' => 'mafs_view',
|
|
|
+ 'products' => 'products',
|
|
|
+ 'reclamations' => 'reclamations_view',
|
|
|
+ 'maf_order' => 'maf_orders_view',
|
|
|
+ 'import' => 'imports',
|
|
|
+ 'responsibles' => 'responsibles_view',
|
|
|
+ 'users' => 'users',
|
|
|
+ 'contracts' => 'contracts',
|
|
|
+ 'spare_parts' => 'spare_parts_view',
|
|
|
'spare_part_orders' => 'spare_part_orders_view',
|
|
|
'notifications' => 'user_notifications',
|
|
|
'notification_logs' => 'notification_delivery_logs',
|
|
|
+ 'common_catalog_items' => 'common_catalog_items',
|
|
|
+ 'stock_availability' => 'common_catalog_items',
|
|
|
+ 'stock_orders' => 'stock_orders',
|
|
|
];
|
|
|
|
|
|
const SKIP_YEAR_FILTER = [
|
|
|
@@ -37,7 +43,7 @@ class FilterController extends Controller
|
|
|
*/
|
|
|
const COLUMN_MAP = [
|
|
|
'spare_part_orders' => [
|
|
|
- 'status_name' => 'status',
|
|
|
+ 'status_name' => 'status',
|
|
|
'with_documents_text' => 'with_documents',
|
|
|
],
|
|
|
'spare_parts' => [
|
|
|
@@ -53,6 +59,20 @@ class FilterController extends Controller
|
|
|
'responsibles' => [
|
|
|
'area-name' => 'area_name',
|
|
|
],
|
|
|
+ 'common_catalog_items' => [
|
|
|
+ 'calculator_enabled_txt' => 'calculator_enabled',
|
|
|
+ 'builders_price_txt' => 'builders_price',
|
|
|
+ 'wholesale_price_txt' => 'wholesale_price',
|
|
|
+ 'recommended_price_txt' => 'recommended_price',
|
|
|
+ 'retail_price_txt' => 'retail_price',
|
|
|
+ 'project_price_txt' => 'project_price',
|
|
|
+ 'project_with_installation_price_txt' => 'project_with_installation_price',
|
|
|
+ 'pik_price_txt' => 'pik_price',
|
|
|
+ 'recommended_plus_10_price_txt' => 'recommended_plus_10_price',
|
|
|
+ ],
|
|
|
+ 'stock_orders' => [
|
|
|
+ 'status_name' => 'status',
|
|
|
+ ],
|
|
|
];
|
|
|
|
|
|
/**
|
|
|
@@ -81,35 +101,62 @@ class FilterController extends Controller
|
|
|
1 => 'Да',
|
|
|
],
|
|
|
'status' => [
|
|
|
- 'ordered' => 'Заказано',
|
|
|
+ 'ordered' => 'Заказано',
|
|
|
'in_stock' => 'На складе',
|
|
|
- 'shipped' => 'Отгружено',
|
|
|
+ 'shipped' => 'Отгружено',
|
|
|
],
|
|
|
],
|
|
|
'notification_logs' => [
|
|
|
'channel' => [
|
|
|
- 'in_app' => 'Браузер',
|
|
|
+ 'in_app' => 'Браузер',
|
|
|
'browser' => 'Браузер',
|
|
|
- 'push' => 'Android/iOS',
|
|
|
- 'email' => 'Email',
|
|
|
+ 'push' => 'Android/iOS',
|
|
|
+ 'email' => 'Email',
|
|
|
],
|
|
|
'status' => [
|
|
|
- 'sent' => 'Отправлено',
|
|
|
- 'failed' => 'Ошибка',
|
|
|
- 'skipped' => 'Пропущено',
|
|
|
+ 'sent' => 'Отправлено',
|
|
|
+ 'failed' => 'Ошибка',
|
|
|
+ 'skipped' => 'Пропущено',
|
|
|
'dead_letter' => 'Dead letter',
|
|
|
],
|
|
|
],
|
|
|
+ 'common_catalog_items' => [
|
|
|
+ 'calculator_enabled' => [
|
|
|
+ 0 => 'нет',
|
|
|
+ 1 => 'да',
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ 'stock_orders' => [
|
|
|
+ 'status' => StockOrder::STATUS_NAMES,
|
|
|
+ ],
|
|
|
+ ];
|
|
|
+
|
|
|
+ private const FIELD_ACCESS_MODULES = [
|
|
|
+ 'common_catalog_items' => 'common-catalog',
|
|
|
+ ];
|
|
|
+
|
|
|
+ private const SESSION_KEYS = [
|
|
|
+ 'common_catalog_items' => 'gp_common_catalog',
|
|
|
];
|
|
|
|
|
|
- public function getFilters(FilterRequest $request)
|
|
|
+ public function getFilters(FilterRequest $request): JsonResponse
|
|
|
{
|
|
|
$table = $request->validated('table');
|
|
|
$column = $request->validated('column');
|
|
|
- if(!array_key_exists($table, self::DB_TABLES)) {
|
|
|
+ if (! array_key_exists($table, self::DB_TABLES)) {
|
|
|
abort(400, 'Table not found');
|
|
|
}
|
|
|
- $gp = session('gp_' . $table);
|
|
|
+
|
|
|
+ $this->assertCanViewColumn($request, $table, $column);
|
|
|
+
|
|
|
+ if ($table === 'stock_availability') {
|
|
|
+ return $this->stockAvailabilityFilters($column);
|
|
|
+ }
|
|
|
+ if ($table === 'stock_orders') {
|
|
|
+ return $this->stockOrderFilters($column);
|
|
|
+ }
|
|
|
+
|
|
|
+ $gp = session(self::SESSION_KEYS[$table] ?? 'gp_'.$table);
|
|
|
|
|
|
if ($table === 'spare_parts' && $column === 'pricing_codes_list') {
|
|
|
$result = DB::table('pricing_codes as pc')
|
|
|
@@ -128,7 +175,7 @@ class FilterController extends Controller
|
|
|
array_unshift($result, '-пусто-');
|
|
|
}
|
|
|
|
|
|
- return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
|
|
|
+ return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
}
|
|
|
|
|
|
$dbTable = self::DB_TABLES[$table];
|
|
|
@@ -138,22 +185,26 @@ class FilterController extends Controller
|
|
|
|
|
|
if ($dbColumn && Schema::hasColumn($dbTable, $dbColumn)) {
|
|
|
$normalizedColumn = self::normalizedSelectExpression($dbColumn);
|
|
|
- $q = DB::table($dbTable)->selectRaw($normalizedColumn . ' as filter_value')->distinct();
|
|
|
- if (!in_array($table, self::SKIP_YEAR_FILTER) && Schema::hasColumn($dbTable, 'year')) {
|
|
|
- $q->where('year' , year());
|
|
|
+ $q = DB::table($dbTable)->selectRaw($normalizedColumn.' as filter_value')->distinct();
|
|
|
+ if (! in_array($table, self::SKIP_YEAR_FILTER) && Schema::hasColumn($dbTable, 'year')) {
|
|
|
+ $q->where('year', year());
|
|
|
}
|
|
|
if (Schema::hasColumn($dbTable, 'deleted_at')) {
|
|
|
$q->whereNull('deleted_at');
|
|
|
}
|
|
|
|
|
|
- if(isset($gp['filters']) && is_array($gp['filters']) && count($gp['filters'])) {
|
|
|
+ if (isset($gp['filters']) && is_array($gp['filters']) && count($gp['filters'])) {
|
|
|
foreach ($gp['filters'] as $colName => $vals) {
|
|
|
- if ($colName === $column) continue;
|
|
|
+ if ($colName === $column) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
$filterDbColumn = self::resolveDbColumn($table, $dbTable, $colName);
|
|
|
- if (!$filterDbColumn || !Schema::hasColumn($dbTable, $filterDbColumn)) continue;
|
|
|
+ if (! $filterDbColumn || ! Schema::hasColumn($dbTable, $filterDbColumn)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
$q->where(function ($query) use ($filterDbColumn, $vals) {
|
|
|
foreach (explode('||', $vals) as $val) {
|
|
|
- if($val == '-пусто-') {
|
|
|
+ if ($val == '-пусто-') {
|
|
|
self::applyEmptyFilterConditionForFilterQuery($query, $filterDbColumn);
|
|
|
} else {
|
|
|
$query->orWhere($filterDbColumn, '=', $val);
|
|
|
@@ -171,6 +222,7 @@ class FilterController extends Controller
|
|
|
if ($val === null || $val === '-пусто-') {
|
|
|
return $val;
|
|
|
}
|
|
|
+
|
|
|
return $val / 100;
|
|
|
}, $result);
|
|
|
}
|
|
|
@@ -178,13 +230,13 @@ class FilterController extends Controller
|
|
|
// Применяем маппинг значений, если есть
|
|
|
if (isset(self::VALUE_MAP[$table][$dbColumn])) {
|
|
|
$map = self::VALUE_MAP[$table][$dbColumn];
|
|
|
- $result = array_map(fn($val) => $map[$val] ?? $val, $result);
|
|
|
+ $result = array_map(fn ($val) => $map[$val] ?? $val, $result);
|
|
|
}
|
|
|
} else {
|
|
|
$result = [];
|
|
|
}
|
|
|
|
|
|
- return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT);
|
|
|
+ return response()->json($result, 200, [], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
}
|
|
|
|
|
|
private static function normalizedSelectExpression(string $column): string
|
|
|
@@ -198,6 +250,75 @@ class FilterController extends Controller
|
|
|
->orWhereRaw("TRIM(CAST({$column} AS CHAR)) = ''");
|
|
|
}
|
|
|
|
|
|
+ private function assertCanViewColumn(FilterRequest $request, string $table, string $column): void
|
|
|
+ {
|
|
|
+ if ($table === 'stock_availability') {
|
|
|
+ abort_unless($request->user()->hasPermission('stock.view'), 403);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if ($table === 'stock_orders') {
|
|
|
+ abort_unless($request->user()->hasPermission('stock.orders.view'), 403);
|
|
|
+
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $module = self::FIELD_ACCESS_MODULES[$table] ?? null;
|
|
|
+ if ($module === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ $field = self::COLUMN_MAP[$table][$column] ?? $column;
|
|
|
+ abort_unless($request->user()->canViewField($module, $field), 403);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function stockAvailabilityFilters(string $column): JsonResponse
|
|
|
+ {
|
|
|
+ $values = match ($column) {
|
|
|
+ 'article', 'calculator_name', 'kind', 'unit' => CommonCatalogItem::query()
|
|
|
+ ->whereHas('stockOrders')
|
|
|
+ ->pluck($column),
|
|
|
+ 'latest_order_note' => CommonCatalogItem::query()
|
|
|
+ ->whereHas('stockOrders')
|
|
|
+ ->with('latestStockOrder:id,common_catalog_item_id,note')
|
|
|
+ ->get()
|
|
|
+ ->pluck('latestStockOrder.note'),
|
|
|
+ default => [],
|
|
|
+ };
|
|
|
+
|
|
|
+ return $this->filterValuesResponse($values);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function stockOrderFilters(string $column): JsonResponse
|
|
|
+ {
|
|
|
+ $values = match ($column) {
|
|
|
+ 'order_number', 'note' => StockOrder::query()->pluck($column),
|
|
|
+ 'item_article' => StockOrder::query()
|
|
|
+ ->join('common_catalog_items', 'common_catalog_items.id', '=', 'stock_orders.common_catalog_item_id')
|
|
|
+ ->pluck('common_catalog_items.article'),
|
|
|
+ 'status_name' => StockOrder::query()
|
|
|
+ ->pluck('status')
|
|
|
+ ->map(fn (string $status): string => StockOrder::STATUS_NAMES[$status] ?? $status),
|
|
|
+ default => [],
|
|
|
+ };
|
|
|
+
|
|
|
+ return $this->filterValuesResponse($values);
|
|
|
+ }
|
|
|
+
|
|
|
+ private function filterValuesResponse(iterable $values): JsonResponse
|
|
|
+ {
|
|
|
+ $normalized = collect($values)
|
|
|
+ ->map(static fn (mixed $value): string => $value === null || trim((string) $value) === ''
|
|
|
+ ? '-пусто-'
|
|
|
+ : (string) $value)
|
|
|
+ ->unique()
|
|
|
+ ->sort(static fn (string $left, string $right): int => strnatcasecmp($left, $right))
|
|
|
+ ->values()
|
|
|
+ ->all();
|
|
|
+
|
|
|
+ return response()->json($normalized, 200, [], JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Определяет реальный столбец БД по имени столбца из заголовка.
|
|
|
* Приоритет: прямое совпадение в БД → COLUMN_MAP для конкретной таблицы.
|