101 javaslat egy kupacban túl sok egy kézi átnézéshez, ráadásul nagyon eltérő mérlegelést igényelnek: a "Békás Kft." és a "Békás Kft" között nincs mit eldönteni, a "Dénes Natura" és a "Dénes Natura Kft" között viszont van. - ProducerDeduplicator::differenceCategory(): a LEGKEVÉSBÉ agresszív átalakítás, ami már egy kulcsra hozza a csoport tagjait - csak írásjel / egybeírás / ékezet / cégforma / vegyes. - a riport ebben a sorrendben írja ki a csoportokat (elöl a gyakorlatilag eldöntött esetek), és a "csak írásjel" kategóriánál előre beírja a javaslatot; a többinél a végleges név üresen marad, a javaslat a Megjegyzés oszlopban. A d2d másolaton: 14 csak írásjel (előre kitöltve, azonnal végrehajtható), 7 egybeírás, 8 ékezet, 72 cégforma - utóbbi 87 vár a megrendelő döntésére. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
584 lines
21 KiB
PHP
584 lines
21 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Enums\DbStatusFieldEnum;
|
||
use App\Support\NameNormalizer;
|
||
use Illuminate\Support\Facades\DB;
|
||
|
||
/**
|
||
* Duplikált gyártó törzsadat felderítése és összevonása.
|
||
*
|
||
* A duplikátumok onnan származnak, hogy a legacy árlista import karakterre pontos
|
||
* egyezést követelt a gyártónévre, és találat híján újat vett fel a nyers Excel
|
||
* értékkel - így lett a "Danone" mellett "Danone " is. Két helyen fáj:
|
||
*
|
||
* - az árlista feldolgozó valódi változás nélkül is módosulást jelez, mert a termék
|
||
* az egyik, a névfeloldás a másik rekordra mutat,
|
||
* - a Termék mennyiség statisztika gyártóra szűrve a másik ID alá könyvelt tételeket
|
||
* kihagyja a riportból.
|
||
*
|
||
* A keletkezés útját a NameNormalizer bevezetése zárta le; ez az osztály a már
|
||
* felhalmozódott adatot takarítja.
|
||
*/
|
||
class ProducerDeduplicator
|
||
{
|
||
/** A hivatkozó táblák: tábla => oszlop. FK csak a pricelist_file_lines-on van. */
|
||
public const REFERENCING_TABLES = [
|
||
'products' => 'producer_id',
|
||
'order_archives_items' => 'producer_id',
|
||
'pricelist_file_lines' => 'producer_id',
|
||
];
|
||
|
||
private const UPDATE_CHUNK = 5000;
|
||
|
||
/** Az eltérés jellege egy laza jelölt-csoportban (lásd differenceCategory) */
|
||
public const DIFF_PUNCTUATION = 'csak írásjel';
|
||
|
||
public const DIFF_SPACING = 'egybeírás';
|
||
|
||
public const DIFF_ACCENT = 'ékezet';
|
||
|
||
public const DIFF_COMPANY_FORM = 'cégforma';
|
||
|
||
public const DIFF_MIXED = 'vegyes';
|
||
|
||
/**
|
||
* Duplikált névcsoportok, csoportonként a tagokkal és a hivatkozás-darabszámokkal.
|
||
* A tagok rendezettek: az első a javasolt megtartandó (legtöbb termék).
|
||
*
|
||
* @return array<string, array<int, array{producer: object, counts: array<string, int>}>>
|
||
*/
|
||
public function duplicateGroups(): array
|
||
{
|
||
$byNormalized = [];
|
||
foreach ($this->activeMembers() as $member) {
|
||
$byNormalized[NameNormalizer::normalize($member['producer']->name)][] = $member;
|
||
}
|
||
|
||
$groups = [];
|
||
foreach ($byNormalized as $normalized => $members) {
|
||
if (count($members) < 2) {
|
||
continue;
|
||
}
|
||
|
||
$groups[$normalized] = $this->sortByKeeperPriority($members);
|
||
}
|
||
|
||
ksort($groups);
|
||
|
||
return $groups;
|
||
}
|
||
|
||
/**
|
||
* Laza jelöltek: olyan gyártók, amelyeket a szigorú szabály NEM köt össze, de nagy
|
||
* eséllyel ugyanaz a cég - elírás, kötőjel, egybeírás, hiányzó ékezet miatt térnek el.
|
||
*
|
||
* Ezek SOSEM olvadnak össze maguktól: a riportba javaslatként kerülnek, a döntést a
|
||
* megrendelő hozza. Ezért lehet itt megengedőbb a szabály, mint az import-párosításnál,
|
||
* ahol egy téves egyezés csendben rossz gyártóhoz rendelne termékeket.
|
||
*
|
||
* Egy dolgot viszont itt is tiszteletben tartunk: a KÜLÖNBÖZŐ cégforma (Kft vs Zrt)
|
||
* valódi különbség, nem elírás - azokat nem javasoljuk összevonásra. A cégforma
|
||
* nélküli név viszont párba állhat egy cégformással, ha csak egyféle forma van.
|
||
*
|
||
* @return array<string, array<int, array{producer: object, counts: array<string, int>}>>
|
||
*/
|
||
public function looseCandidateGroups(): array
|
||
{
|
||
$byBase = [];
|
||
foreach ($this->activeMembers() as $member) {
|
||
[$base, $form] = $this->splitCompanyForm($member['producer']->name);
|
||
|
||
if ($base === '') {
|
||
continue;
|
||
}
|
||
|
||
$byBase[$base][$form][] = $member;
|
||
}
|
||
|
||
$candidates = [];
|
||
|
||
foreach ($byBase as $base => $byForm) {
|
||
$explicitForms = array_diff(array_keys($byForm), ['']);
|
||
|
||
if (count($explicitForms) > 1) {
|
||
// Kft ÉS Zrt is van ugyanarra a névre: ezek nem egymás elírásai. A
|
||
// formánként külön csoportok mehetnek, a forma nélküli viszont nem
|
||
// rendelhető egyértelműen egyikhez sem, ezért kimarad.
|
||
foreach ($explicitForms as $form) {
|
||
$candidates[$base . '|' . $form] = $byForm[$form];
|
||
}
|
||
|
||
continue;
|
||
}
|
||
|
||
$candidates[$base] = array_merge(...array_values($byForm));
|
||
}
|
||
|
||
// Csak az érdekes: ami több szigorú csoportot fog össze (vagy olyan rekordokat,
|
||
// amiket a szigorú szabály külön hagyott).
|
||
$strictKeys = [];
|
||
foreach ($candidates as $key => $members) {
|
||
$strictKeys[$key] = array_unique(array_map(
|
||
fn ($m) => NameNormalizer::normalize($m['producer']->name),
|
||
$members,
|
||
));
|
||
}
|
||
|
||
$result = [];
|
||
foreach ($candidates as $key => $members) {
|
||
if (count($members) < 2 || count($strictKeys[$key]) < 2) {
|
||
continue;
|
||
}
|
||
|
||
$result[$key] = $this->sortByKeeperPriority($members);
|
||
}
|
||
|
||
ksort($result);
|
||
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* Egy laza jelölt-csoport eltérésének jellege - a LEGKEVÉSBÉ agresszív átalakítás,
|
||
* ami már egy kulcsra hozza a tagokat.
|
||
*
|
||
* Ez adja meg a megrendelőnek, mennyi mérlegelést igényel a sor: a "csak írásjel"
|
||
* eseteknél gyakorlatilag nincs döntés, a "cégforma" és a "vegyes" viszont valódi
|
||
* ismeretet kíván a cégekről.
|
||
*
|
||
* @param array<int, array{producer: object, counts: array<string, int>}> $members
|
||
*/
|
||
public function differenceCategory(array $members): string
|
||
{
|
||
$names = array_map(fn ($m) => (string) $m['producer']->name, $members);
|
||
|
||
$levels = [
|
||
self::DIFF_PUNCTUATION => fn ($n) => $this->stripPunctuation(NameNormalizer::normalize($n)),
|
||
self::DIFF_SPACING => fn ($n) => preg_replace('/\s+/u', '',
|
||
$this->stripPunctuation(NameNormalizer::normalize($n))),
|
||
self::DIFF_ACCENT => fn ($n) => preg_replace('/\s+/u', '',
|
||
$this->stripAccents($this->stripPunctuation(NameNormalizer::normalize($n)))),
|
||
self::DIFF_COMPANY_FORM => fn ($n) => $this->splitCompanyForm($n)[0],
|
||
];
|
||
|
||
foreach ($levels as $category => $key) {
|
||
if (count(array_unique(array_map($key, $names))) === 1) {
|
||
return $category;
|
||
}
|
||
}
|
||
|
||
return self::DIFF_MIXED;
|
||
}
|
||
|
||
private function stripPunctuation(string $text): string
|
||
{
|
||
return trim(preg_replace('/\s+/u', ' ', preg_replace('/[.,\-\'"()]/u', ' ', $text) ?? $text) ?? $text);
|
||
}
|
||
|
||
private function stripAccents(string $text): string
|
||
{
|
||
return strtr($text, [
|
||
'Á' => 'A', 'É' => 'E', 'Í' => 'I', 'Ó' => 'O', 'Ö' => 'O',
|
||
'Ő' => 'O', 'Ú' => 'U', 'Ü' => 'U', 'Ű' => 'U',
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Név szétbontása alapnévre és cégforma-toldatra, a "csak elírás" eltérések
|
||
* kiszűrésével: írásjelek, ékezetek és szóközök elhagyásával.
|
||
*
|
||
* @return array{0: string, 1: string} [alapnév, cégforma]
|
||
*/
|
||
private function splitCompanyForm(string $name): array
|
||
{
|
||
$normalized = $this->stripAccents($this->stripPunctuation(NameNormalizer::normalize($name)));
|
||
|
||
$form = '';
|
||
$pattern = '/\b(KFT|ZRT|BT|NYRT|KKT|RT|GMBH|LTD|INC|SRL|NV|BV|AG|SA|SPA)\b/u';
|
||
|
||
if (preg_match($pattern, $normalized, $matches)) {
|
||
$form = $matches[1];
|
||
$normalized = preg_replace($pattern, ' ', $normalized) ?? $normalized;
|
||
}
|
||
|
||
// A szóközök teljes elhagyása fogja meg az egybeírást ("Alfölditej" = "Alföldi Tej")
|
||
return [preg_replace('/\s+/u', '', $normalized) ?? '', $form];
|
||
}
|
||
|
||
/**
|
||
* @return array<int, array{producer: object, counts: array<string, int>}>
|
||
*/
|
||
private function activeMembers(): array
|
||
{
|
||
$counts = $this->referenceCounts();
|
||
|
||
return DB::table('producers')
|
||
->whereNotIn('status', [DbStatusFieldEnum::archive, DbStatusFieldEnum::deleted])
|
||
->orderBy('id')
|
||
->get()
|
||
->map(fn ($producer) => [
|
||
'producer' => $producer,
|
||
'counts' => [
|
||
'products' => $counts['products'][$producer->id] ?? 0,
|
||
'order_archives_items' => $counts['order_archives_items'][$producer->id] ?? 0,
|
||
'pricelist_file_lines' => $counts['pricelist_file_lines'][$producer->id] ?? 0,
|
||
],
|
||
])
|
||
->all();
|
||
}
|
||
|
||
/**
|
||
* A megtartandó rekord kerül előre: a legtöbb TERMÉKKEL rendelkező, mert így mozdul
|
||
* a legkevesebb sor. Döntetlennél a több rendelési tétel, majd a régebbi rekord.
|
||
*
|
||
* @param array<int, array> $members
|
||
* @return array<int, array>
|
||
*/
|
||
private function sortByKeeperPriority(array $members): array
|
||
{
|
||
usort($members, function ($a, $b) {
|
||
return [$b['counts']['products'], $b['counts']['order_archives_items'], $a['producer']->id]
|
||
<=> [$a['counts']['products'], $a['counts']['order_archives_items'], $b['producer']->id];
|
||
});
|
||
|
||
return array_values($members);
|
||
}
|
||
|
||
/**
|
||
* @return array<string, array<int, int>> tábla => [producer_id => darab]
|
||
*/
|
||
private function referenceCounts(): array
|
||
{
|
||
$counts = [];
|
||
|
||
foreach (self::REFERENCING_TABLES as $table => $column) {
|
||
$counts[$table] = DB::table($table)
|
||
->selectRaw("{$column} as pid, COUNT(*) as c")
|
||
->whereNotNull($column)
|
||
->groupBy($column)
|
||
->pluck('c', 'pid')
|
||
->toArray();
|
||
}
|
||
|
||
return $counts;
|
||
}
|
||
|
||
/**
|
||
* Összevonási terv a döntési lap sorai alapján.
|
||
*
|
||
* A csoportosítás forrása a LAP "Csoport" oszlopa, nem az adatbázis normalizálása.
|
||
* Ez azért fontos, mert a megrendelő olyan gyártókat is egybe akarhat vonni, amiket a
|
||
* névnormalizálás nem tud összekötni: a "Kőröstej" és a "Kőröstej Kft" ugyanaz a cég,
|
||
* de a nevük érdemben különbözik. Ha a lap ezeket egy csoportba teszi, itt is egy
|
||
* összevonás lesz belőlük - így nem kell a végrehajtásba csoportokon átívelő logika.
|
||
*
|
||
* @param array<int, array{group: int|string, id: int, final_name: ?string}> $decisions
|
||
* @return array{merges: array<int, array>, errors: array<int, string>}
|
||
*/
|
||
public function buildPlan(array $decisions): array
|
||
{
|
||
$groups = $this->duplicateGroups();
|
||
|
||
$memberById = [];
|
||
foreach ($this->activeMembers() as $member) {
|
||
$memberById[$member['producer']->id] = $member;
|
||
}
|
||
|
||
$normalizedById = [];
|
||
foreach ($groups as $normalized => $members) {
|
||
foreach ($members as $member) {
|
||
$normalizedById[$member['producer']->id] = $normalized;
|
||
}
|
||
}
|
||
|
||
$sheetGroups = [];
|
||
$finalNames = [];
|
||
$skipped = [];
|
||
$errors = [];
|
||
|
||
foreach ($decisions as $row) {
|
||
$id = (int) ($row['id'] ?? 0);
|
||
|
||
if (! isset($memberById[$id])) {
|
||
$errors[] = "A(z) {$id} azonosítójú gyártó nem található (vagy már összevonták, "
|
||
. 'vagy más környezetből származik a lap).';
|
||
|
||
continue;
|
||
}
|
||
|
||
$key = trim((string) ($row['group'] ?? ''));
|
||
|
||
if ($key === '') {
|
||
$errors[] = "A(z) {$id} azonosítójú sorból hiányzik a csoport száma.";
|
||
|
||
continue;
|
||
}
|
||
|
||
$sheetGroups[$key][] = $id;
|
||
|
||
$name = trim((string) ($row['final_name'] ?? ''));
|
||
if ($name !== '') {
|
||
$finalNames[$key] ??= $name;
|
||
}
|
||
}
|
||
|
||
$validation = $this->validateGrouping($sheetGroups, $normalizedById, $groups);
|
||
$errors = array_merge($errors, $validation['errors']);
|
||
|
||
$merges = [];
|
||
|
||
foreach ($sheetGroups as $key => $ids) {
|
||
// A hibás csoportot NEM hajtjuk végre: egy hiányzó tag azt jelentené, hogy
|
||
// az összevonás után is maradna azonos nevű rekord.
|
||
if (isset($validation['invalid'][$key])) {
|
||
continue;
|
||
}
|
||
|
||
$members = array_values(array_map(fn ($id) => $memberById[$id], array_unique($ids)));
|
||
|
||
// Üres végleges név = a megrendelő NEM kéri az összevonást. Ez a laza
|
||
// javaslatok elutasításának módja, ezért nem hiba, hanem kihagyás - de
|
||
// jelentjük, hogy egy véletlen kihagyás se maradjon észrevétlen.
|
||
if (! isset($finalNames[$key])) {
|
||
$skipped[] = [
|
||
'group' => $key,
|
||
'names' => array_map(fn ($m) => (string) $m['producer']->name, $members),
|
||
];
|
||
|
||
continue;
|
||
}
|
||
|
||
$members = $this->sortByKeeperPriority($members);
|
||
$keeper = $members[0]['producer'];
|
||
$losers = array_slice($members, 1);
|
||
|
||
if ($losers === []) {
|
||
continue; // egytagú csoport: nincs mit összevonni
|
||
}
|
||
|
||
$merges[] = [
|
||
'group' => $key,
|
||
'keeper_id' => $keeper->id,
|
||
'keeper_name' => $keeper->name,
|
||
'final_name' => $finalNames[$key],
|
||
'rename' => trim((string) $keeper->name) !== $finalNames[$key],
|
||
'from' => array_map(fn ($m) => [
|
||
'id' => $m['producer']->id,
|
||
'name' => $m['producer']->name,
|
||
'counts' => $m['counts'],
|
||
], $losers),
|
||
];
|
||
}
|
||
|
||
return ['merges' => $merges, 'errors' => $errors, 'skipped' => $skipped];
|
||
}
|
||
|
||
/**
|
||
* A lap-csoportosítás ellenőrzése.
|
||
*
|
||
* A lap SZÉLESEBB csoportot csinálhat, mint a névnormalizálás (ez a cél), de
|
||
* SZŰKEBBET nem: ha az azonos nevű rekordok külön lap-csoportba kerülnének, a
|
||
* végén két azonos nevű gyártó maradna - vagyis pont a duplikációt állítanánk elő.
|
||
*
|
||
* @return array{errors: array<int, string>, invalid: array<string, true>}
|
||
*/
|
||
private function validateGrouping(array $sheetGroups, array $normalizedById, array $groups): array
|
||
{
|
||
$errors = [];
|
||
$invalid = [];
|
||
$sheetKeysByNormalized = [];
|
||
$idsByNormalized = [];
|
||
|
||
foreach ($sheetGroups as $key => $ids) {
|
||
foreach ($ids as $id) {
|
||
// A lapon szerepelhet olyan gyártó is, ami NEM tagja szigorú duplikátum
|
||
// csoportnak (a laza javaslatok ilyenek) - ott nincs mit ellenőrizni.
|
||
if (! isset($normalizedById[$id])) {
|
||
continue;
|
||
}
|
||
|
||
$normalized = $normalizedById[$id];
|
||
$sheetKeysByNormalized[$normalized][$key] = true;
|
||
$idsByNormalized[$normalized][$id] = true;
|
||
}
|
||
}
|
||
|
||
foreach ($sheetKeysByNormalized as $normalized => $keys) {
|
||
if (count($keys) > 1) {
|
||
$errors[] = 'Az azonos nevű gyártók ("' . $normalized . '") több csoportba kerültek a lapon ('
|
||
. implode(', ', array_keys($keys)) . '). Ezeket egy csoportba kell tenni, '
|
||
. 'különben duplikátum maradna utánuk.';
|
||
|
||
foreach (array_keys($keys) as $key) {
|
||
$invalid[$key] = true;
|
||
}
|
||
}
|
||
|
||
$expected = array_map(fn ($m) => $m['producer']->id, $groups[$normalized]);
|
||
$missing = array_diff($expected, array_keys($idsByNormalized[$normalized]));
|
||
|
||
if ($missing !== []) {
|
||
$errors[] = 'A döntési lap nem tartalmazza a csoport minden tagját (hiányzó azonosító: '
|
||
. implode(', ', $missing) . '). Generáld újra a lapot ebben a környezetben.';
|
||
|
||
foreach (array_keys($keys) as $key) {
|
||
$invalid[$key] = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
return ['errors' => $errors, 'invalid' => $invalid];
|
||
}
|
||
|
||
/**
|
||
* A terv végrehajtása. Előbb visszaállítási pontot ír, csak utána módosít.
|
||
*
|
||
* @return array a jelentés, ami egyben a visszagörgetés bemenete is
|
||
*/
|
||
public function apply(array $merges): array
|
||
{
|
||
$report = [
|
||
'created_at' => now()->toDateTimeString(),
|
||
'database' => DB::connection()->getDatabaseName(),
|
||
'merges' => [],
|
||
'restore' => [],
|
||
'totals' => array_fill_keys(array_keys(self::REFERENCING_TABLES), 0),
|
||
];
|
||
|
||
// producer_id leképezés: honnan => hova
|
||
$map = [];
|
||
foreach ($merges as $merge) {
|
||
foreach ($merge['from'] as $from) {
|
||
$map[$from['id']] = $merge['keeper_id'];
|
||
}
|
||
}
|
||
|
||
if ($map === []) {
|
||
return $report;
|
||
}
|
||
|
||
// 1. Visszaállítási pont: soronként a régi érték. Enélkül a művelet nem lenne
|
||
// visszafordítható, mert a fordított leképezés azokat a sorokat is átírná,
|
||
// amelyek eredetileg is a megtartott rekordra mutattak.
|
||
foreach (self::REFERENCING_TABLES as $table => $column) {
|
||
$report['restore'][$table] = DB::table($table)
|
||
->whereIn($column, array_keys($map))
|
||
->pluck($column, 'id')
|
||
->toArray();
|
||
}
|
||
|
||
$report['restore']['producers'] = DB::table('producers')
|
||
->whereIn('id', array_merge(array_keys($map), array_column($merges, 'keeper_id')))
|
||
->get(['id', 'name', 'status', 'canSee', 'note'])
|
||
->keyBy('id')
|
||
->toArray();
|
||
|
||
// 2. Hivatkozások átírása - táblánként EGY menetben, mert a producer_id-n a nagy
|
||
// táblákon nincs index, tehát minden külön WHERE teljes scant jelentene.
|
||
foreach (self::REFERENCING_TABLES as $table => $column) {
|
||
$report['totals'][$table] = $this->remap($table, $column, $map);
|
||
}
|
||
|
||
// 3. A gyártó rekordok rendezése
|
||
foreach ($merges as $merge) {
|
||
if ($merge['rename']) {
|
||
DB::table('producers')->where('id', $merge['keeper_id'])
|
||
->update(['name' => $merge['final_name'], 'updated_at' => now()]);
|
||
}
|
||
|
||
foreach ($merge['from'] as $from) {
|
||
DB::table('producers')->where('id', $from['id'])->update([
|
||
'status' => DbStatusFieldEnum::archive,
|
||
'canSee' => 0,
|
||
// Tartós nyom az adatbázisban: a jelentésfájl elveszhet, ez nem.
|
||
'note' => trim(sprintf(
|
||
'Összevonva ide: #%d (%s) – %s producers:dedupe',
|
||
$merge['keeper_id'],
|
||
$merge['final_name'],
|
||
now()->toDateString(),
|
||
)),
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
$report['merges'][] = [
|
||
'keeper_id' => $merge['keeper_id'],
|
||
'final_name' => $merge['final_name'],
|
||
'from' => array_map(fn ($f) => ['id' => $f['id'], 'name' => $f['name']], $merge['from']),
|
||
];
|
||
}
|
||
|
||
return $report;
|
||
}
|
||
|
||
/**
|
||
* Egyetlen UPDATE táblánként, CASE leképezéssel.
|
||
*/
|
||
private function remap(string $table, string $column, array $map): int
|
||
{
|
||
$ids = array_map('intval', array_keys($map));
|
||
$updated = 0;
|
||
|
||
foreach (array_chunk($ids, 200) as $chunk) {
|
||
$cases = '';
|
||
foreach ($chunk as $from) {
|
||
$cases .= sprintf(' WHEN %d THEN %d', $from, (int) $map[$from]);
|
||
}
|
||
|
||
$updated += DB::update(sprintf(
|
||
'UPDATE `%s` SET `%s` = CASE `%s`%s END WHERE `%s` IN (%s)',
|
||
$table,
|
||
$column,
|
||
$column,
|
||
$cases,
|
||
$column,
|
||
implode(',', $chunk),
|
||
));
|
||
}
|
||
|
||
return $updated;
|
||
}
|
||
|
||
/**
|
||
* Visszagörgetés a jelentésfájlból: minden érintett sor visszakapja a saját,
|
||
* eredeti producer_id-ját, a gyártó rekordok pedig az eredeti nevüket/státuszukat.
|
||
*/
|
||
public function rollback(array $report): array
|
||
{
|
||
$restored = array_fill_keys(array_keys(self::REFERENCING_TABLES), 0);
|
||
|
||
foreach (self::REFERENCING_TABLES as $table => $column) {
|
||
$rows = (array) ($report['restore'][$table] ?? []);
|
||
|
||
foreach (array_chunk($rows, self::UPDATE_CHUNK, true) as $chunk) {
|
||
$byValue = [];
|
||
foreach ($chunk as $rowId => $producerId) {
|
||
$byValue[(int) $producerId][] = (int) $rowId;
|
||
}
|
||
|
||
foreach ($byValue as $producerId => $rowIds) {
|
||
$restored[$table] += DB::table($table)
|
||
->whereIn('id', $rowIds)
|
||
->update([$column => $producerId]);
|
||
}
|
||
}
|
||
}
|
||
|
||
foreach ((array) ($report['restore']['producers'] ?? []) as $id => $producer) {
|
||
$producer = (array) $producer;
|
||
|
||
DB::table('producers')->where('id', (int) $id)->update([
|
||
'name' => $producer['name'],
|
||
'status' => $producer['status'],
|
||
'canSee' => $producer['canSee'],
|
||
'note' => $producer['note'],
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
return $restored;
|
||
}
|
||
}
|