Files
autocrm/app/Console/Commands/ImportLaborCatalogCommand.php
T
Vasyka 5e923459f7 Three features: labor group headers, status picker, catalog import
- LaborResource: add titlePrefixedWithLabel(false) so group headers show
  just the localized category name (not "Categorie: Шины"); remove static
  cache from closure to avoid Octane cross-request locale bleed
- WorkOrderDashboard: add changeStatus() Livewire method + Alpine.js
  dropdown on the status badge so status can be changed directly from
  the dashboard without going to the full edit form
- New artisan command catalog:import-labors {company} that reads
  database/data/labor-catalog.json (386 operations, 45 leaf categories)
  and upserts labor_categories + labors for the given tenant; supports
  --force to update existing records by code
- Add "Status actualizat" translation to ru/en JSON

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 04:38:35 +00:00

153 lines
5.7 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Central\Company;
use App\Models\Scopes\TenantScope;
use App\Models\Tenant\Labor;
use App\Models\Tenant\LaborCategory;
use App\Tenancy\TenantManager;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
class ImportLaborCatalogCommand extends Command
{
protected $signature = 'catalog:import-labors {company : Company slug or numeric ID} {--file= : Path to JSON file (default: psauto-katalog-rabot-full.json)} {--force : Update existing records}';
protected $description = 'Import labor catalog from psauto-katalog-rabot-full.json into labor_categories and labors tables';
public function handle(): int
{
$companyArg = $this->argument('company');
$company = is_numeric($companyArg)
? Company::find((int) $companyArg)
: Company::where('slug', $companyArg)->first();
if (! $company) {
$this->error("Company '{$companyArg}' not found.");
return 1;
}
app(TenantManager::class)->setCurrent($company);
$file = $this->option('file') ?: database_path('data/labor-catalog.json');
if (! file_exists($file)) {
foreach ([
'/tmp/service/todo/psauto-katalog-rabot-full.json',
storage_path('app/labor-catalog.json'),
] as $path) {
if (file_exists($path)) { $file = $path; break; }
}
}
if (! file_exists($file)) {
$this->error("JSON file not found. Pass --file=/path/to/psauto-katalog-rabot-full.json");
return 1;
}
$data = json_decode(file_get_contents($file), true);
$allCats = $data['categories'] ?? [];
$operations = $data['operations'] ?? [];
$catMap = collect($allCats)->keyBy('id');
// Identify leaf categories (those that have operations assigned to them)
$usedCatIds = collect($operations)->pluck('service_category_id')->unique();
$leafCats = $catMap->only($usedCatIds->all());
$this->info("Company: {$company->name} (id={$company->id})");
$this->info("Categories to import: {$leafCats->count()}");
$this->info("Operations to import: " . count($operations));
$force = $this->option('force');
// ── 1. Upsert labor categories ────────────────────────────────────
$this->info('Importing categories...');
$catCodeToName = []; // leaf code → canonical name (= code itself)
$sortBase = 100;
foreach ($leafCats as $leaf) {
$parent = $catMap->get($leaf['parent_id']);
$nameRu = ($parent ? $parent['name'] . ' · ' : '') . $leaf['name'];
$code = $leaf['code']; // canonical key
$existing = LaborCategory::withoutGlobalScope(TenantScope::class)
->where('company_id', $company->id)
->where('name', $code)
->first();
if ($existing) {
if ($force) {
$existing->update(['name_ru' => $nameRu]);
$this->line(" Updated cat: {$code}");
}
} else {
LaborCategory::withoutGlobalScope(TenantScope::class)->create([
'company_id' => $company->id,
'name' => $code,
'name_ro' => $nameRu,
'name_ru' => $nameRu,
'sort_order' => $sortBase + ($leaf['sort_order'] ?? 0),
]);
$this->line(" Created cat: {$code} ({$nameRu})");
}
$catCodeToName[$leaf['id']] = $code;
$sortBase += 5;
}
// ── 2. Upsert labors ─────────────────────────────────────────────
$this->info('Importing operations...');
$created = 0;
$updated = 0;
$skipped = 0;
foreach ($operations as $op) {
$catId = $op['service_category_id'];
$catKey = $catCodeToName[$catId] ?? null;
if (! $catKey) {
$skipped++;
continue;
}
$hours = round(($op['norm_minutes'] ?? 0) / 60, 2);
$price = round(($op['base_price_minor'] ?? 0) / 100, 2);
$nameRu = $op['name'];
$code = $op['code'];
$existing = Labor::withoutGlobalScope(TenantScope::class)
->where('company_id', $company->id)
->where('code', $code)
->first();
if ($existing) {
if ($force) {
$existing->update([
'name_ru' => $nameRu,
'hours' => $hours,
'price' => $price,
]);
$updated++;
} else {
$skipped++;
}
} else {
Labor::withoutGlobalScope(TenantScope::class)->create([
'company_id' => $company->id,
'category' => $catKey,
'code' => $code,
'name_ro' => $nameRu,
'name_ru' => $nameRu,
'pricing_mode' => 'hourly',
'hours' => $hours,
'price' => $price,
'is_active' => (bool) ($op['is_active'] ?? true),
]);
$created++;
}
}
$this->info("Done. Created: {$created}, Updated: {$updated}, Skipped: {$skipped}");
return 0;
}
}