Repository: szentjozsefhackathon/szentiras.hu
Pull request #218: WIP: Start usx code rework
Author: login: gabre
Created: 2025-02-11T16:32:27Z | Merged: 2025-03-07T10:53:47Z

- [x] usx id enum, string based, https://ubsicap.github.io/usx/vocabularies.html#usx-vocab-bookcode
- [x] books / book_number -> order
- [x] books új oszlop: usx_code varchar(3)
- [x] az összes többi táblában: book_number -> usx_code
- [x] a Verse osztályban a books()-t módosítani kell, hogy az összes olyan Book-ot adja meg, ahol azonos az usx kód
- [x] a Verse osztályban a book() a book_id alapján fog működni, és az adott translation megfelelő, vershez tartozó könyvét adja meg



- [x] translations tábla: legyen az abbrev unique key.



- [x] book_abbrevs / book_id (igazából book number) -> usx_code
- [x] az usx_code valódi usx_code kell legyen, a jelenlegi "abbrev" oszlop alapján kell egy migrációval ezt beírni  (ez lesz az a pont, ahol PHP-ben fel kell sorolnunk, hogy melyik régi book_numberből melyik USX kód lesz)
- [x] book_abbrevs: új oszlop a translation id helyett: translation_abbrev nullable string, hogy egy abbrev + translation -höz pontosan egy usx_code tartozzék.
- [x] Ahol bookNumber vagy book_number van a kódban, ott át kell térni usxCode-ra vagy usx_code-ra, vagy az orderre. Ez utóbbi a keresésnél fog kelleni, mert az orderből tudjuk, hogy ószövetség vagy újszövetség.



- [ ] embedding vektorok: embedded_excerpts táblában a books-ra ne legyen foreign key megkötés


- [x] importer:
  - [ ] üríti a transalationhoz tartozó verses-t
  - [ ] üríti a translationhoz tartozó books-t
  - [ ] feltölti a books-t a Könyvek sheet-ből. A Könyvek-beli rov (rövidítés, abbrev) mappelése az usx_code -ra a book_abbrevs alapján történik. A sorrendet (order) a gepi első fele adja.
  - [ ] feltölti a verses-t. A vershez tartozó rov

-  [x] Sphinx Search book_number -> usx_code update (config, kód)

- [x] gepi -> `{$usx_code}_{$chapter}_{$numv}`


Diff:
diff --git a/app/Console/Commands/ImportScripture.php b/app/Console/Commands/ImportScripture.php
index f0ffa810..17b36f76 100644
--- a/app/Console/Commands/ImportScripture.php
+++ b/app/Console/Commands/ImportScripture.php
@@ -10,6 +10,7 @@
 use Exception;
 use Illuminate\Console\Command;
 use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Helper\ProgressBar;
 use Symfony\Component\Process\Exception\ProcessFailedException;
 use Symfony\Component\Process\Process;
 use SzentirasHu\Data\Repository\BookRepository;
@@ -19,11 +20,20 @@
 use SzentirasHu\Data\Entity\Translation;
 use OpenSpout\Reader\XLSX\RowIterator;
 use OpenSpout\Reader\XLSX\Reader;
+use OpenSpout\Reader\XLSX\Sheet;
 use PhpOffice\PhpSpreadsheet\IOFactory;
+use SzentirasHu\Data\Entity\Book;
+use SzentirasHu\Data\Entity\Verse;
+use SzentirasHu\Data\UsxCodes;
+
+define('BOOKCODE', 'gepi');
+define('BOOKABBREV', 'rov');
+define('BOOKNAME', 'nev');
+
+define('ROW_NUMBER_ESTIMATE', 159000);
 
 class ImportScripture extends Command
 {
-
     /**
      * The console command name.
      *
@@ -45,17 +55,17 @@ class ImportScripture extends Command
     private $sourceDirectory;
     private $importableTranslations = ['BD', 'KG', 'KNB', 'RUF', 'UF', 'SZIT', 'STL'];
     // Mapping: Database columns to Books Sheet header column numbers
-    private $dbToHeaderColNum = [
-        'SZIT' => ['gepi' => 0, 'rov' => 5],
-        'KNB' => ['gepi' => 0, 'rov' => 3],
-        'UF' => ['gepi' => 0, 'rov' => 4],
-        'KG' => ['gepi' => 0, 'rov' => 4],
-        'BD' => ['gepi' => 0, 'rov' => 1],
-        'RUF' => ['gepi' => 0, 'rov' => 5],
-        'STL' => ['gepi' => 0, 'rov' => 2],
+    private $headerNameToColNum = [
+        'SZIT' => [BOOKCODE => 0, BOOKABBREV => 5, BOOKNAME => 2],
+        'KNB' => [BOOKCODE => 0, BOOKABBREV => 3, BOOKNAME => 1],
+        'UF' => [BOOKCODE => 0, BOOKABBREV => 4, BOOKNAME => 1],
+        'KG' => [BOOKCODE => 0, BOOKABBREV => 4, BOOKNAME => 1],
+        'BD' => [BOOKCODE => 0, BOOKABBREV => 1, BOOKNAME => 2],
+        'RUF' => [BOOKCODE => 0, BOOKABBREV => 5, BOOKNAME => 1],
+        'STL' => [BOOKCODE => 0, BOOKABBREV => 2, BOOKNAME => 1],
     ];
     // Mapping: Database columns to Verses Sheet headers
-    private $defaultDbToHeaderMap = ['did' => 'Ssz', 'gepi' => 'hiv', 'tip' => 'jelstatusz', 'verse' => 'jel'];
+    private $defaultDbToHeaderMap = ['did' => 'Ssz', BOOKCODE => 'hiv', 'tip' => 'jelstatusz', 'verse' => 'jel'];
     private $descriptorspec = [
         0 => ["pipe", "r"], // stdin
         1 => ["pipe", "w"], // stdout
@@ -107,9 +117,9 @@ public function handle(): int
 
         $this->verifyTranslationBookColumns($transAbbrevToImport);
 
-        $inserts = $this->readInserts($filePath, $translation, $transAbbrevToImport);
-        if (count($inserts) > 0) {
-            $this->saveToDb($transAbbrevToImport, $translation, $inserts);
+        [$bookInserts, $verseInserts] = $this->readInserts($translation, $transAbbrevToImport, $filePath);
+        if (!empty($bookInserts) || !empty($verseInserts)) {
+            $this->storeInDb($translation, $bookInserts, $verseInserts);
         } else {
             $this->info("Nincs mit feltölteni.");
         }
@@ -126,33 +136,127 @@ protected function getOptions(): array
         ];
     }
 
-    private function readInserts(string $filePath, Translation $translation, string $transAbbrevToImport): array
+    private function storeInDb(Translation $translation, array $bookInserts, array $verseInserts): void
+    {
+        $this->info("Adatok mentése az adatbázisba...");
+        Artisan::call('down');
+        DB::transaction(function () use ($translation, $bookInserts, $verseInserts): void {
+            $this->info("Régi könyvek törlése...");
+            Book::where('translation_id', $translation->id)->delete();
+
+            $this->info("Régi versek törlése...");
+            Verse::whereHas('book', function ($query) use ($translation) {
+                $query->where('translation_id', $translation->id);
+            })->delete();
+
+            $this->info("Könyvek tárolása...");
+            $this->storeBooks($translation, $bookInserts);
+
+            $this->info("Versek tárolása...");
+            $this->storeVerses($translation, $verseInserts);
+        });
+        Artisan::call('up');
+        $this->info("Adatok sikeresen elmentve.");
+    }
+
+    private function storeBooks(Translation $translation, array $bookInserts): void
+    {
+        foreach ($bookInserts as $bookInsert) {
+            $book = new Book([
+                'name' => $bookInsert['name'],
+                'abbrev' => $bookInsert['abbrev'],
+                'link' => $bookInsert['link'],
+                'old_testament' => $bookInsert['old_testament'],
+                'order' => $bookInsert['order'],
+                'usx_code' => $bookInsert['usx_code'],
+            ]);
+            $book->translation()->associate($translation);
+            $book->save();
+
+            Cache::add(
+                $this->getBookCacheKey(
+                    $bookInsert['order'],
+                    $translation->name
+                ),
+                $book,
+                now()->addMinutes(10)
+            );
+        }
+    }
+
+    private function storeVerses(Translation $translation, array $verseInserts): void
+    {
+        $progressBar = $this->createProgressBar(count($verseInserts));
+        $verseNumber = 1;
+        foreach ($verseInserts as $verseInsert) {
+            $book = Cache::remember(
+                $this->getBookCacheKey($verseInsert['order'], $translation->name),
+                now()->addMinutes(10),
+                fn() => $this->fetchBook($verseInsert['order'], $translation)
+            );
+            $progressBar->setMessage(
+                "$verseNumber. vers mentése az adatbázisba... " .
+                "($translation->name/{$book->usx_code} " .
+                "{$verseInsert['chapter']}:{$verseInsert['numv']})"
+            );
+
+            if (!$book) {
+                App::abort(500, "Hiányzó Book az adatbázisban: {$translation->name}/{$verseInsert['order']}.");
+            }
+
+            $syntheticCode = $book->usx_code . "_" . $verseInsert['chapter'] . '_' . $verseInsert['numv'];
+            $verse = new Verse([
+                'usx_code' => $book->usx_code,
+                BOOKCODE => $syntheticCode,
+                'verse' => $verseInsert['verse'],
+                // 'order' => $verseInsert['order'], -- we don't have this yet
+                'chapter' => $verseInsert['chapter'],
+                'numv' => $verseInsert['numv'],
+                'tip' => $verseInsert['tip'],
+                'verseroot' => $verseInsert['verseroot'],
+                'ido' => $verseInsert['ido'] ?? null
+            ]);
+            $verse->translation()->associate($translation);
+            $verse->book()->associate($book);
+            $verse->save();
+            $progressBar->advance();
+            $verseNumber++;
+        }
+        $progressBar->finish();
+    }
+
+    private function fetchBook($order, Translation $translation): ?Book
+    {
+        return Book::where('order', $order)
+            ->where('translation_id', $translation->id)
+            ->first();
+    }
+
+    private function getBookCacheKey(int $order, string $translation): string
+    {
+        return "book_{$order}_{$translation}";
+    }
+
+    private function readInserts(Translation $translation, string $transAbbrevToImport, string $filePath): array
     {
         $this->info("A $filePath fájl betöltése...");
-        $reader = ReaderFactory::createFromFileByMimeType($filePath);
+        $reader = new Reader();
         $reader->open($filePath);
         $this->info("A $filePath fájl megnyitva...");
         $sheets = $this->getSheets($reader);
 
-        $bookNumberToId = $this->getBookNumberToIdMapping($sheets, $translation, $transAbbrevToImport);
+        $this->info("Könyvek lap ellenőrzése");
+        $bookInserts = $this->readBookSheetInserts(
+            $transAbbrevToImport,
+            $sheets['Könyvek']
+        );
 
         $this->info("A '$transAbbrevToImport' lap betöltése..");
-        $versesSheet = $sheets[$transAbbrevToImport];
-        $verseRowIterator = $versesSheet->getRowIterator();
-        $verseSheetHeaders = $this->getHeaders($verseRowIterator);
-
-        $dbToHeaderMap = $this->mapVerseSheetHeadersToDbColumns($verseSheetHeaders);
-    
-        $inserts = $this->readLines(
-            $verseRowIterator,
-            $verseSheetHeaders,
-            $dbToHeaderMap,
-            $translation,
-            $bookNumberToId
+        $verseInserts = $this->readVerseSheetInserts(
+            $sheets[$transAbbrevToImport],
         );
-      
         $reader->close();
-        return $inserts;
+        return [$bookInserts, $verseInserts];
     }
 
     private function downloadTranslation(string $transAbbrev, string $url): string
@@ -212,13 +316,13 @@ private function getSheets(Reader $reader): array
         return $sheets;
     }
 
-    private function readLines(RowIterator $verseRowIterator, array $verseSheetHeaders, array $dbToHeaderMap, Translation $translation, array $booksGepiToId): array
+    private function readVerseSheetInserts(Sheet $versesSheet): array
     {
-        $this->info("Beolvasás sorról sorra...\n");
-        $progressBar = $this->output->createProgressBar();
-        $progressBar->setRedrawFrequency(25);
-        $progressBar->setBarWidth(24);
-        $progressBar->setFormat("[%bar%] %message%");
+        $verseRowIterator = $versesSheet->getRowIterator();
+        $verseSheetHeaders = $this->getHeaders($verseRowIterator);
+        $dbToHeaderMap = $this->mapVerseSheetHeadersToDbColumns($verseSheetHeaders);
+        $this->info("Beolvasás sorról sorra...");
+        $progressBar = $this->createProgressBar(ROW_NUMBER_ESTIMATE);
         $rowNumber = 0;
         foreach ($verseRowIterator as $verseRow) {
             $rowNumber++;
@@ -226,141 +330,117 @@ private function readLines(RowIterator $verseRowIterator, array $verseSheetHeade
                 $this->info("Sor átugrása: $rowNumber. (fejlécnek tűnik)");
                 continue;
             }
-            if (empty($verseRow->getCellAtIndex($verseSheetHeaders[$dbToHeaderMap['gepi']])->getValue())) {
+            if (empty($verseRow->getCellAtIndex($verseSheetHeaders[$dbToHeaderMap[BOOKCODE]])->getValue())) {
                 break;
             }
-            $gepi = $verseRow->getCellAtIndex($verseSheetHeaders[$dbToHeaderMap['gepi']])->getValue();
-            if (!$this->option('filter') or preg_match('/' . $this->option('filter') . '/i', $gepi)) {
-                $newInsert = $this->toDbRow(
+            $originalBookCode = $verseRow->getCellAtIndex($verseSheetHeaders[$dbToHeaderMap[BOOKCODE]])->getValue();
+            if (!$this->option('filter') or $this->checkFilterMatch($originalBookCode)) {
+                $newVerseInsert = $this->toVerseInsert(
                     $verseRow,
                     $verseSheetHeaders,
-                    $translation,
-                    $gepi,
-                    $booksGepiToId
+                    $originalBookCode
                 );
-                $inserts[$rowNumber] = $newInsert;
-            
-            
+                $verseInserts[$rowNumber] = $newVerseInsert;
                 $rowNumber++;
-                $progressBar->setMessage("$rowNumber - {$newInsert['gepi']} - új szavak: {$this->newStems}");
+                $progressBar->setMessage(
+                    "$rowNumber. sor" .
+                    " - {$newVerseInsert['original_book_code']}" .
+                    " - új szavak: {$this->newStems}"
+                );
+                if ($rowNumber > $progressBar->getMaxSteps()) {
+                    $progressBar->setMaxSteps($rowNumber);
+                }
                 $progressBar->advance();
-            }            
+            }
         }
         $progressBar->finish();
-        return $inserts;
+        return $verseInserts;
     }
 
-    private function toDbRow(Row $row, array $verseSheetHeaders, Translation $translation, string $gepi, array $booksGepiToId): array
-    {
+    private function toVerseInsert(
+        Row $row,
+        array $verseSheetHeaders,
+        string $originalBookCode,
+    ): array {
         $pipes = [];
         if ($this->hunspellEnabled) {
-            $hunspellProcess = proc_open(
-                'stdbuf -oL hunspell -m -d hu_HU -i UTF-8',
-                $this->descriptorspec,
-                $pipes,
-                null,
-                null
-            );                    
+            $hunspellProcess = $this->startHunspell($pipes);
         }
 
-        $result['trans'] = $translation->id;
-        $result['gepi'] = $gepi;
-        $result['book_number'] = (int) substr($gepi, 0, 3);
-        $result['chapter'] = (int) substr($gepi, 3, 3);
-        $result['numv'] = (int) substr($gepi, 6, 3);
+        $result['original_book_code'] = $originalBookCode;
+        $result['order'] = (int) substr($originalBookCode, 0, 3);
+        $result['chapter'] = (int) substr($originalBookCode, 3, 3);
+        $result['numv'] = (int) substr($originalBookCode, 6, 3);
         $result['tip'] = $row->getCellAtIndex($verseSheetHeaders['jelstatusz'])->getValue();
         $result['verse'] = $row->getCellAtIndex($verseSheetHeaders['jel'])->getValue();
         $result['verseroot'] = null;
-
         if ($this->hunspellEnabled && in_array($result['tip'], [60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704])) {
             $result['verseroot'] = $this->executeStemming($result['verse'], $pipes);
         }
-
         if (isset($verseSheetHeaders['ido'])) {
             $idoValue = $row->getCellAtIndex($verseSheetHeaders['ido'])->getValue();
             $result['ido'] = $idoValue;
         }
 
-        if (!isset($booksGepiToId[$result['book_number']])) {
-            App::abort(500, 'Nincs meg a book number a gepi->id listában');
-        }
-        $result['book_id'] = $booksGepiToId[$result['book_number']];
-
         if ($this->hunspellEnabled) {
-            fclose($pipes[0]);
-            fclose($pipes[1]);
-            fclose($pipes[2]);
-            if (isset($hunspellProcess)) {
-                proc_close($hunspellProcess);
-            }
+            $this->closeHunspell($hunspellProcess, $pipes);
         }
 
         return $result;
     }
 
-    private function saveToDb(string $abbrev, Translation $translation, array $inserts): void
-    {
-        $this->info("\nFeldolgozott adatok mentése az adatbázisba...");
-        $progressBar = $this->output->createProgressBar(count($inserts));
-
-        Artisan::call('down');
-        $this->info("A tdveres tábla (részleges) ürítése...");
-        if (!$this->option('filter')) {
-            DB::table('tdverse')->where('trans', '=', $translation->id)->delete();
-        } else {
-            DB::table('tdverse')->where('trans', '=', $translation->id)->where('gepi', 'REGEXP', $this->option('filter'))->delete();
-        }
-        $this->info("A tdverse tábla feltöltése " . count($inserts) . " sorral...");
-        echo "\n";
-        for ($rowNumber = 0; $rowNumber < count($inserts); $rowNumber += 100) {
-            $slice = array_slice($inserts, $rowNumber, 100);
-            DB::table('tdverse')->insert($slice);
-            $progressBar->advance(100);
-        }
-        $progressBar->finish();
-        Artisan::call('up');
-    }
-
-    private function getBookNumberToIdMapping(array $sheets, Translation $translation, string $translationAbbrev): array
-    {
-        $this->info("Könyvek lap ellenőrzése");
-        $bookRowIterator = $sheets['Könyvek']->getRowIterator();
+    private function readBookSheetInserts(
+        string $translationAbbrev,
+        Sheet $bookSheet
+    ): array {
         $linesRead = 0;
-        $badAbbrevs = [];
-        foreach ($bookRowIterator as $bookRow) {
+        $bookInserts = [];
+
+        foreach ($bookSheet->getRowIterator() as $bookRow) {
             $valueInFirstCell = $bookRow->getCellAtIndex(0)?->getValue();
             $linesRead++;
+
             if (empty($valueInFirstCell)) {
                 $this->info("$linesRead sor beolvasva, kész.");
                 break;
             }
+
             if (!is_numeric($valueInFirstCell)) {
                 $this->info("Sor átugrása: $linesRead. (nem numerikus tartalom)");
                 continue;
             }
-            $dbBookAbbrevToId = $this->getAbbrevToIdFromDb($translation);
-            $bookNumber = $bookRow->getCellAtIndex($this->dbToHeaderColNum[$translationAbbrev]['gepi'])->getValue();
-            $bookAbbrev = $bookRow->getCellAtIndex($this->dbToHeaderColNum[$translationAbbrev]['rov'])->getValue();
-            $this->info("Könyv: $bookNumber, $bookAbbrev");
-            if ($this->isImportSourceBookAbbrevMissingFromDb($dbBookAbbrevToId, $bookAbbrev)) {
-                $book = $this->bookRepository->getByAbbrev($bookAbbrev, $translation->id); // look up the correct abbreviation
-                if ($book) {
-                    $this->info("Könyv ID az adatbázisban: {$book->id}");
-                    $this->info(1);
-                    $bookNumberToId[$bookNumber] = $book->id;
-                } else {
-                    $this->info(2);
-                    $badAbbrevs[] = $bookAbbrev;
-                }
-            } else if ($bookAbbrev != '-' && $bookAbbrev != '') {
-                $this->info(3);
-                $bookNumberToId[$bookNumber] = $dbBookAbbrevToId[$bookAbbrev];
-            }
+
+            $newBookInsert = $this->toBookInsert(
+                $bookRow,
+                $translationAbbrev
+            );
+            $bookInserts[] = $newBookInsert;
         }
-        $this->checkBadAbbrevs(badAbbrevs: $badAbbrevs);
-        return $bookNumberToId;
+
+        return $bookInserts;
     }
 
+    private function toBookInsert(
+        Row $bookRow,
+        string $translationAbbrev
+    ): array {
+        $bookOrder = $bookRow->getCellAtIndex($this->headerNameToColNum[$translationAbbrev][BOOKCODE])->getValue();
+        $bookAbbrev = $bookRow->getCellAtIndex($this->headerNameToColNum[$translationAbbrev][BOOKABBREV])->getValue();
+        $bookName = $bookRow->getCellAtIndex($this->headerNameToColNum[$translationAbbrev][BOOKNAME])->getValue();
+        $bookUsx = $this->bookAbbrevToUsxCode($bookAbbrev, $translationAbbrev);
+        $this->info("{$bookOrder}. könyv: {$bookAbbrev} (usx: {$bookUsx})");
+        return [
+            'order' => (int) $bookOrder,
+            'abbrev' => $bookAbbrev,
+            'usx_code' => $bookUsx,
+            'translation' => $translationAbbrev,
+            'name' => $bookName,
+            'link' => $this->removeAccents($bookAbbrev),
+            'old_testament' => $this->isOldTestament($bookUsx),
+        ];
+    }
+
+
     private function mapVerseSheetHeadersToDbColumns(array $headers): array
     {
         $this->info("Oszlopok ellenőrzése...");
@@ -374,7 +454,7 @@ private function mapVerseSheetHeadersToDbColumns(array $headers): array
         if (!empty($errors)) {
             foreach ($headers as $headerCol => $val) {
                 if (preg_match('/[A-Z]{3}_hiv/', $headerCol)) {
-                    $dbToHeaderMap['gepi'] = $headerCol;
+                    $dbToHeaderMap[BOOKCODE] = $headerCol;
                 }
                 if (preg_match('/[A-Z]{3}_old/', $headerCol)) {
                     $dbToHeaderMap['old'] = $headerCol;
@@ -436,24 +516,21 @@ private function executeStemming(string $verse, array $pipes): string
         return join(' ', $verseroots->unique()->toArray());
     }
 
-    private function getAbbrevToIdFromDb(Translation $translation): array
+    private function bookAbbrevToUsxCode(string $bookAbbrev, string $translation): string
     {
-        $books = $this->bookRepository->getBooksByTranslation($translation->id);
-        $booksAbbrevToId = [];
-        foreach ($books as $book) {
-            $booksAbbrevToId[$book->abbrev] = $book->id;
+        $result = UsxCodes::getUsxFromBookAbbrevAndTranslation(
+            $bookAbbrev,
+            $translation
+        );
+        if (is_null($result)) {
+            App::abort(500, "Nincs USX kód ehhez a könyvhöz: {$bookAbbrev} (fordítás: {$translation})");
         }
-        return $booksAbbrevToId;
+        return $result;
     }
 
-    private function checkBadAbbrevs(array $badAbbrevs): void
+    public static function isOldTestament(string $usxCode): int
     {
-        if (!empty($badAbbrevs)) {
-            $this->info("A következő rövidítések csak a szövegforrásban találhatóak meg, az adatbázisban nem!\n" . implode(', ', $badAbbrevs));
-            if (!$this->confirm('Folytassuk?')) {
-                App::abort(500, "Kilépés");
-            }
-        }
+        return in_array($usxCode, UsxCodes::oldTestamentUsx()) ? 1 : 0;
     }
 
     private function verifyTranslationAbbrev(string $abbrev): void
@@ -465,7 +542,7 @@ private function verifyTranslationAbbrev(string $abbrev): void
 
     private function verifyTranslationBookColumns(string $translationAbbrev): void
     {
-        if (!isset($this->dbToHeaderColNum[$translationAbbrev])) {
+        if (!isset($this->headerNameToColNum[$translationAbbrev])) {
             App::abort(
                 500,
                 'Ennél a szövegforrásnál (' . $translationAbbrev . ') ' .
@@ -482,7 +559,7 @@ private function getHeaders(RowIterator $verseRowIterator): array
         foreach ($verseRowIterator as $row) { // only go through the first row
             foreach ($row->getCells() as $cell) {
                 $cols[$cell->getValue()] = $i;
-                $this->info("$i.oszlop: {$cell->getValue()}");
+                $this->info("$i. oszlop: {$cell->getValue()}");
                 $i++;
             }
             break;
@@ -490,12 +567,6 @@ private function getHeaders(RowIterator $verseRowIterator): array
         return $cols;
     }
 
-    private function isImportSourceBookAbbrevMissingFromDb(array $dbBookAbbrevs, string $bookAbbrev): bool
-    {
-        return !isset($dbBookAbbrevs[$bookAbbrev]) &&
-            ($bookAbbrev != '-' && $bookAbbrev != '');
-    }
-
     private function isVerseHeaderRow(Row $row): bool
     {
         $firstCellValue = $row->getCellAtIndex(0)?->getValue();
@@ -527,4 +598,49 @@ private function ensureProperFile(string $originalFilePath): string
 
         App::abort(500, "A fájl nem Excel Sheet: $originalFilePath ($fileExtension)");
     }
+
+    private function createProgressBar(int $max): ProgressBar
+    {
+        $progressBar = $this->output->createProgressBar($max);
+        $progressBar->setRedrawFrequency(25);
+        $progressBar->setBarWidth(24);
+        $progressBar->setFormat("[%bar%] %message%\n");
+        return $progressBar;
+    }
+
+    private function startHunspell(array &$pipes)
+    {
+        return proc_open(
+            'stdbuf -oL hunspell -m -d hu_HU -i UTF-8',
+            $this->descriptorspec,
+            $pipes,
+            null,
+            null
+        );
+    }
+
+    private function closeHunspell($hunspellProcess, array $pipes): void
+    {
+        fclose($pipes[0]);
+        fclose($pipes[1]);
+        fclose($pipes[2]);
+        if (isset($hunspellProcess)) {
+            proc_close($hunspellProcess);
+        }
+    }
+
+    private function removeAccents($string): string
+    {
+        $accents = ['á', 'é', 'í', 'ó', 'ö', 'ő', 'ú', 'ü', 'ű', 'Á', 'É', 'Í', 'Ó', 'Ö', 'Ő', 'Ú', 'Ü', 'Ű'];
+        $replacements = ['a', 'e', 'i', 'o', 'o', 'o', 'u', 'u', 'u', 'A', 'E', 'I', 'O', 'O', 'O', 'U', 'U', 'U'];
+        return str_replace($accents, $replacements, $string);
+    }
+
+    private function checkFilterMatch(string $originalBookCode): bool
+    {
+        return (bool) preg_match(
+            '/' . $this->option('filter') . '/i',
+            $originalBookCode
+        );
+    }
 }
diff --git a/app/Data/Entity/Book.php b/app/Data/Entity/Book.php
index 0ddc5a05..674045f0 100644
--- a/app/Data/Entity/Book.php
+++ b/app/Data/Entity/Book.php
@@ -12,12 +12,14 @@
  * @author berti
  */
 class Book extends Eloquent {
-
-
-
-    public function abbrevs() {
-        return $this->hasMany('SzentirasHu\Data\Entity\BookAbbrev', 'number');
-    }
+    protected $fillable = [
+        'name',
+        'abbrev',
+        'link',
+        'old_testament',
+        'order',
+        'usx_code',
+    ];
 
     public function verses() {
         return $this->hasMany('SzentirasHu\Data\Entity\Verse', 'book_id');
diff --git a/app/Data/Entity/Verse.php b/app/Data/Entity/Verse.php
index e146fd61..b22eb18b 100644
--- a/app/Data/Entity/Verse.php
+++ b/app/Data/Entity/Verse.php
@@ -17,6 +17,18 @@ class Verse extends Eloquent {
     public $timestamps = false;
     protected $table = 'tdverse';
 
+    protected $fillable = [
+        'usx_code',
+        'gepi',
+        'verse',
+        'order',
+        'chapter',
+        'numv',
+        'tip',
+        'verseroot',
+        'ido',
+    ];
+
     private static $typeMap;
 
     public function book() {
@@ -24,7 +36,7 @@ public function book() {
     }
 
     public function books() {
-        return $this->belongsTo('SzentirasHu\Data\Entity\Book', 'book_number');
+        return $this->belongsTo('SzentirasHu\Data\Entity\Book', 'usx_code');
     }
 
     public function translation() {
diff --git a/app/Data/Repository/BookRepository.php b/app/Data/Repository/BookRepository.php
index c613153f..e9f6fed5 100644
--- a/app/Data/Repository/BookRepository.php
+++ b/app/Data/Repository/BookRepository.php
@@ -22,6 +22,6 @@ public function getByAbbrev($bookAbbrev, $translationId = null);
      */
     public function getByAbbrevForTranslation($abbrev, $translationId);
 
-    public function getByNumberForTranslation($number, $translationId);
+    public function getByUsxCodeForTranslation($usxCode, $translationId);
 
-} 
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/app/Data/Repository/BookRepositoryEloquent.php b/app/Data/Repository/BookRepositoryEloquent.php
index 27d077f3..1d788345 100644
--- a/app/Data/Repository/BookRepositoryEloquent.php
+++ b/app/Data/Repository/BookRepositoryEloquent.php
@@ -9,14 +9,16 @@
 use Cache;
 use SzentirasHu\Data\Entity\Book;
 use SzentirasHu\Data\Entity\BookAbbrev;
+use SzentirasHu\Data\UsxCodes;
 
-class BookRepositoryEloquent implements BookRepository {
+class BookRepositoryEloquent implements BookRepository
+{
 
 
     public function getBooksByTranslation($translationId)
     {
-        return Cache::remember("getBooksByTranslation_{$translationId}", 120, function() use ($translationId) {
-         return Book::where('translation_id', $translationId)->orderBy('id')->get();
+        return Cache::remember("getBooksByTranslation_{$translationId}", 120, function () use ($translationId) {
+            return Book::where('translation_id', $translationId)->orderBy('id')->get();
         });
     }
 
@@ -27,26 +29,28 @@ public function getBooksByTranslation($translationId)
      */
     public function getByAbbrev($bookAbbrev, $translationId = null)
     {
-        return Cache::remember("book_getByAbbrev_{$bookAbbrev}_{$translationId}", 120, function() use ($bookAbbrev, $translationId) {
-            $query = BookAbbrev::whereRaw('LOWER(abbrev) = ?', [mb_strtolower($bookAbbrev)]);
-            if ($translationId) {
-                $query = $query->where(function ($query) use ($translationId) {
-                    $query->where('translation_id', $translationId)->orWhere('translation_id', null);
-                });
-                $query = $query ->orderBy('translation_id', 'desc');
-            } else {
-                $query = $query ->orderBy('translation_id', 'asc');
+        return Cache::remember("book_getByAbbrev_{$bookAbbrev}_{$translationId}", 120, function () use ($bookAbbrev, $translationId) {
+            $usxCode = UsxCodes::getUsxFromBookAbbrevAndTranslation($bookAbbrev, $translationId ?? "default");
+            if (!$usxCode) {
+                return null;
             }
-            $abbrev = $query->first();
-            if ($abbrev) {
-                return $abbrev->books()->first();
+
+            if ($translationId) {
+                $books = Book::where('usx_code', $usxCode)
+                    ->where(function ($query) use ($translationId) {
+                        $query->where('translation_id', $translationId)
+                            ->orWhereNull('translation_id');
+                    })
+                    ->orderBy('translation_id', 'desc');
             } else {
-                return null;
+                $books = Book::where('usx_code', $usxCode)
+                    ->orderBy('translation_id', 'asc');
             }
+            return $books->first();
         });
-
     }
 
+
     /**
      * @param string $abbrev
      * @param int $translationId
@@ -54,18 +58,20 @@ public function getByAbbrev($bookAbbrev, $translationId = null)
      */
     public function getByAbbrevForTranslation($abbrev, $translationId)
     {
-        return Cache::remember("getBookByNumberForTranslation_{$abbrev}_{$translationId}", 120, function() use ($abbrev, $translationId) {
+        return Cache::remember("getBookByUsxCodeForTranslation_{$abbrev}_{$translationId}", 120, function () use ($abbrev, $translationId) {
             $book = $this->getByAbbrev($abbrev, $translationId);
             if ($book) {
-                return $this->getByNumberForTranslation($book->number, $translationId);
+                return $this->getByUsxCodeForTranslation($book->usx_code, $translationId);
             }
         });
     }
 
-    public function getByNumberForTranslation($number, $translationId)
+    public function getByUsxCodeForTranslation($usxCode, $translationId)
     {
-        return Cache::remember("getBookByNumberForTranslation_{$number}_{$translationId}", 120, function() use ($translationId, $number) {
-            $book = Book::where('number', $number)->where('translation_id', $translationId)->first();
+        return Cache::remember("getBookByUsxCodeForTranslation_{$usxCode}_{$translationId}", 120, function () use ($translationId, $usxCode) {
+            $book = Book::where('usx_code', $usxCode)
+                ->where('translation_id', $translationId)
+                ->first();
             if ($book == null) {
                 return false;
             } else {
diff --git a/app/Data/Repository/VerseRepository.php b/app/Data/Repository/VerseRepository.php
index 05ed084d..058a9456 100644
--- a/app/Data/Repository/VerseRepository.php
+++ b/app/Data/Repository/VerseRepository.php
@@ -22,5 +22,5 @@ public function getVersesInOrder($verseIds);
 
     public function getVerses($bookId);
 
-    public function getMaxChapterByBookNumber($bookNumber, $translationId);
+    public function getMaxChapterByBookUsxCode($usxCode, $translationId);
 } 
\ No newline at end of file
diff --git a/app/Data/Repository/VerseRepositoryEloquent.php b/app/Data/Repository/VerseRepositoryEloquent.php
index 2f3c6165..0bf4ecc6 100644
--- a/app/Data/Repository/VerseRepositoryEloquent.php
+++ b/app/Data/Repository/VerseRepositoryEloquent.php
@@ -41,15 +41,16 @@ public function getVersesInOrder($verseIds)
         $verses = Verse::whereIn('id', $verseIds)->with([
             'translation',
             'book'])->get();
+        $idVerseMap = [];
         foreach ($verses as $verse) {
             $idVerseMap[$verse->id] = $verse;
         }
         return array_replace(array_flip($verseIds), $idVerseMap);
     }
 
-    public function getMaxChapterByBookNumber($bookNumber, $translationId)
+    public function getMaxChapterByBookUsxCode($usxCode, $translationId)
     {
-        return Verse::where('book_number', $bookNumber)->where('trans', $translationId)->max('chapter');
+        return Verse::where('usx_code', $usxCode)->where('trans', $translationId)->max('chapter');
     }
 
 }
\ No newline at end of file
diff --git a/app/Data/UsxCodes.php b/app/Data/UsxCodes.php
new file mode 100644
index 00000000..c3fe4906
--- /dev/null
+++ b/app/Data/UsxCodes.php
@@ -0,0 +1,608 @@
+<?php
+
+namespace SzentirasHu\Data;
+
+class UsxCodes
+{
+    private const OLD_TESTAMENT = [
+        '1KI' => [
+            'default' => [
+                '1Kir',
+            ],
+        ],
+        '1CH' => [
+            'default' => [
+                '1Krón',
+            ],
+        ],
+        '1MA' => [
+            'default' => [
+                '1Mak',
+                '1Makk',
+            ],
+        ],
+        'GEN' => [
+            'default' => [
+                'Ter',
+                '1Móz',
+                '1Mozes',
+                '1Mózes',
+                '1Moz',
+                'Teremtes',
+                'Teremtés',
+            ],
+            'RUF' => [
+                '1Móz',
+            ],
+        ],
+        '1SA' => [
+            'default' => [
+                '1Sam',
+                '1Sám',
+                '1Samuel',
+                '1Sámuel',
+                '1Sámuél',
+                'Samuel1',
+                'Sámuel1',
+                'SamuelI',
+                'SámuelI',
+            ],
+        ],
+        '2KI' => [
+            'default' => [
+                '2Kir',
+            ],
+        ],
+        '2CH' => [
+            'default' => [
+                '2Krón',
+            ],
+        ],
+        '2MA' => [
+            'default' => [
+                '2Mak',
+                '2Makk',
+            ],
+        ],
+        'EXO' => [
+            'default' => [
+                '2Moz',
+                '2Móz',
+                '2Mozes',
+                '2Mózes',
+                'Kiv',
+                'Kivonulas',
+                'Kivonulás',
+            ],
+        ],
+        '2SA' => [
+            'default' => [
+                '2Sam',
+                '2Sám',
+                '2Samuel',
+                '2Sámuel',
+                '2Sámuél',
+                'Samuel2',
+                'Sámuel2',
+                'SamuelII',
+                'SámuelII',
+            ],
+        ],
+        'LEV' => [
+            'default' => [
+                '3Moz',
+                '3Móz',
+                '3Mozes',
+                '3Mózes',
+                'Lev',
+                'Leviták',
+            ],
+        ],
+        'NUM' => [
+            'default' => [
+                '4Moz',
+                '4Móz',
+                '4Mozes',
+                '4Mózes',
+                'Szam',
+                'Szám',
+                'Szamok',
+                'Számok',
+            ],
+        ],
+        'DEU' => [
+            'default' => [
+                '5Moz',
+                '5Móz',
+                '5Mozes',
+                '5Mózes',
+                'Mtorv',
+                'MTörv',
+            ],
+        ],
+        'OBA' => [
+            'default' => [
+                'Abd',
+            ],
+        ],
+        'HAG' => [
+            'default' => [
+                'Ag',
+                'Agg',
+                'Hag',
+            ],
+        ],
+        'AMO' => [
+            'default' => [
+                'Ám',
+                'Ámós',
+            ],
+        ],
+        'BAR' => [
+            'default' => [
+                'Bár',
+            ],
+        ],
+        'JDG' => [
+            'default' => [
+                'Bir',
+                'Bír',
+                'Birak',
+                'Birák',
+                'Bírák',
+            ],
+        ],
+        'WIS' => [
+            'default' => [
+                'Bölcs',
+            ],
+        ],
+        'DAN' => [
+            'default' => [
+                'Dán',
+            ],
+        ],
+        'SNG' => [
+            'default' => [
+                'En',
+                'Én',
+                'Ének.Én',
+                'Énekek',
+                'ÉnekÉn',
+            ],
+        ],
+        'ISA' => [
+            'default' => [
+                'Ésa',
+                'Ézs',
+                'Iz',
+            ],
+        ],
+        'EST' => [
+            'default' => [
+                'Esz',
+                'Eszt',
+            ],
+        ],
+        'EZR' => [
+            'default' => [
+                'Ez',
+                'Ezd',
+                'Ezdr',
+                'Ezsd',
+                'Ezsdr',
+            ],
+        ],
+        'EZK' => [
+            'default' => [
+                'Ezek',
+                'Ezék',
+                'Ezekias',
+                'Ezekiás',
+                'Ezékiás',
+            ],
+        ],
+        'HAB' => [
+            'default' => [
+                'Hab',
+            ],
+        ],
+        'HOS' => [
+            'default' => [
+                'Hós',
+                'Oz',
+                'Óz',
+            ],
+        ],
+        'JER' => [
+            'default' => [
+                'Jer',
+            ],
+        ],
+        'JOB' => [
+            'default' => [
+                'Jo',
+                'Jób',
+            ],
+        ],
+        'JOL' => [
+            'default' => [
+                'Joel',
+                'Jóel',
+            ],
+        ],
+        'JON' => [
+            'default' => [
+                'Jón',
+            ],
+        ],
+        'JOS' => [
+            'default' => [
+                'Jozs',
+                'Józs',
+                'Jozsue',
+                'Józsue',
+                'Józsué',
+            ],
+        ],
+        'LAM' => [
+            'default' => [
+                'Siralm',
+                'Jsir',
+                'Sír',
+                'Siral',
+            ],
+            'RUF' => [
+                'Sir'
+            ]
+        ],
+        'SIR' => [
+            'default' => [
+                'Sirák',
+                'Sirák fia',
+                'Ecclesiasticus',
+            ],
+            'SZIT' => [
+                'Sir'
+            ],
+            'RUF' => [
+            ],
+        ],
+        'JDT' => [
+            'default' => [
+                'Judit',
+            ],
+            'SZIT' => [
+                'Jud',
+            ]
+        ],
+        'MAL' => [
+            'default' => [
+                'Mal',
+                'Malak',
+            ],
+        ],
+        'MIC' => [
+            'default' => [
+                'Mik',
+            ],
+        ],
+        'NAM' => [
+            'default' => [
+                'Náh',
+            ],
+        ],
+        'NEH' => [
+            'default' => [
+                'Neh',
+            ],
+        ],
+        'PRO' => [
+            'default' => [
+                'Péld',
+            ],
+        ],
+        'ECC' => [
+            'default' => [
+                'Préd',
+            ],
+        ],
+        'RUT' => [
+            'default' => [
+                'Rut',
+                'Rút',
+                'Ruth',
+                'Rúth',
+            ],
+        ],
+        'ZEP' => [
+            'default' => [
+                'Sof',
+                'Szof',
+                'Zof',
+            ],
+        ],
+        'TOB' => [
+            'default' => [
+                'Tób',
+            ],
+        ],
+        'ZEC' => [
+            'default' => [
+                'Zak',
+            ],
+        ],
+        'PSA' => [
+            'default' => [
+                'Zsolt',
+                'Zsoltar',
+                'Zsoltár',
+                'Zsoltarok',
+                'Zsoltárok',
+            ],
+        ],
+    ];
+
+    private const NEW_TESTAMENT = [
+        '1JN' => [
+            'default' => [
+                '1Jan',
+                '1Ján',
+                '1Janos',
+                '1János',
+                '1Jn',
+            ],
+        ],
+        '2JN' => [
+            'default' => [
+                '2Jan',
+                '2Ján',
+                '2Janos',
+                '2János',
+                '2Jn',
+            ],
+        ],
+        '3JN' => [
+            'default' => [
+                '3Ján',
+                '3Jn',
+            ],
+        ],
+        'MAT' => [
+            'default' => [
+                'Mat',
+                'Mát',
+                'Mate',
+                'Máté',
+                'Mt',
+            ],
+        ],
+        'MRK' => [
+            'default' => [
+                'Mar',
+                'Már',
+                'Mark',
+                'Márk',
+                'Mk',
+            ],
+        ],
+        'LUK' => [
+            'default' => [
+                'Lk',
+                'Luk',
+                'Lukacs',
+                'Lukács',
+            ],
+        ],
+        'JHN' => [
+            'default' => [
+                'Jan',
+                'Ján',
+                'Janos',
+                'János',
+                'Jn',
+            ],
+        ],
+        'ACT' => [
+            'default' => [
+                'ApCsel',
+                'Csel',
+            ],
+        ],
+        'ROM' => [
+            'default' => [
+                'Rom',
+                'Róm',
+            ],
+        ],
+        '1CO' => [
+            'default' => [
+                '1Kor',
+            ],
+        ],
+        '2CO' => [
+            'default' => [
+                '2Kor',
+            ],
+        ],
+        'GAL' => [
+            'default' => [
+                'Gal',
+            ],
+        ],
+        'EPH' => [
+            'default' => [
+                'Ef',
+                'Eféz',
+            ],
+        ],
+        'PHP' => [
+            'default' => [
+                'Fil',
+            ],
+        ],
+        'COL' => [
+            'default' => [
+                'Kol',
+            ],
+        ],
+        '1TH' => [
+            'default' => [
+                '1Tessz',
+                '1Tesz',
+                '1Thess',
+                '1Thessz',
+            ],
+        ],
+        '2TH' => [
+            'default' => [
+                '2Tessz',
+                '2Tesz',
+                '2Thess',
+                '2Thessz',
+            ],
+        ],
+        '1TI' => [
+            'default' => [
+                '1Tim',
+            ],
+        ],
+        '2TI' => [
+            'default' => [
+                '2Tim',
+            ],
+        ],
+        'TIT' => [
+            'default' => [
+                'Tit',
+                'Tít',
+            ],
+        ],
+        'PHM' => [
+            'default' => [
+                'Filem',
+            ],
+        ],
+        'HEB' => [
+            'default' => [
+                'Zs',
+                'Zsid',
+            ],
+        ],
+        'JAS' => [
+            'default' => [
+                'Jak',
+            ],
+        ],
+        '1PE' => [
+            'default' => [
+                '1Pét',
+                '1Pt',
+            ],
+        ],
+        '2PE' => [
+            'default' => [
+                '2Pét',
+                '2Pt',
+            ],
+        ],
+        'JUD' => [
+            'default' => [
+                'Jud',
+                'Júd',
+                'Júdás',
+            ],
+        ],
+        'REV' => [
+            'default' => [
+                'Jel',
+            ],
+        ],
+    ];
+
+    public static function oldTestamentUsx(): array
+    {
+        return array_keys(UsxCodes::OLD_TESTAMENT);
+    }
+
+    public static function newTestamentUsx(): array
+    {
+        return array_keys(UsxCodes::NEW_TESTAMENT);
+    }
+
+    public static function allUsx(): array
+    {
+        return array_merge(UsxCodes::oldTestamentUsx(), UsxCodes::newTestamentUsx());
+    }
+
+    public static function getUsxFromBookAbbrevAndTranslation(string $bookAbbrev, string $translation = "default"): ?string
+    {
+        static $abbrevToUsxPerTranslation = null;
+        static $usxToTranslationToAbbrev = null;
+        if ($abbrevToUsxPerTranslation === null) {
+            $usxToTranslationToAbbrev = self::fullMapping();
+            $abbrevToUsxPerTranslation = self::abbrevToUsxPerTranslation($usxToTranslationToAbbrev);
+        }
+
+        if (isset($abbrevToUsxPerTranslation[$translation][$bookAbbrev])) {
+            return $abbrevToUsxPerTranslation[$translation][$bookAbbrev];
+        }
+
+        if (isset($abbrevToUsxPerTranslation["default"][$bookAbbrev])) {
+            $specificUsx = $abbrevToUsxPerTranslation["default"][$bookAbbrev];
+
+            if (
+                isset($usxToTranslationToAbbrev[$specificUsx][$translation]) &&
+                $usxToTranslationToAbbrev[$specificUsx][$translation] === []
+            ) {
+                return null; // no such book in this translation
+            }
+
+            return $specificUsx;
+        }
+
+        return null; // unknown book
+    }
+
+    public static function getAbbreviations(string $usxCode, string $translation): array
+    {
+        if (isset(UsxCodes::fullMapping()[$usxCode][$translation])) {
+            return UsxCodes::fullMapping()[$usxCode][$translation];
+        }
+        if (isset(UsxCodes::fullMapping()[$usxCode]["default"])) {
+            return UsxCodes::fullMapping()[$usxCode]["default"];
+        }
+        return [];
+    }
+
+    public static function getPreferredAbbreviation(string $usxCode, string $translation): ?string
+    {
+        $abbreviations = UsxCodes::getAbbreviations($usxCode, $translation);
+        if (count($abbreviations) > 0) {
+            return $abbreviations[0];
+        } else {
+            return null;
+        }
+    }
+
+    private static function fullMapping(): array
+    {
+        return array_merge(UsxCodes::OLD_TESTAMENT, UsxCodes::NEW_TESTAMENT);
+    }
+
+    private static function abbrevToUsxPerTranslation($inputMap): array
+    {
+        $outputMap = [];
+        foreach ($inputMap as $usx => $translations) {
+            foreach ($translations as $translationName => $abbrevs) {
+                if (!isset($outputMap[$translationName])) {
+                    $outputMap[$translationName] = [];
+                }
+                foreach ($abbrevs as $abbrev) {
+                    $outputMap[$translationName][$abbrev] = $usx;
+                }
+            }
+        }
+        return $outputMap;
+    }
+}
diff --git a/app/Http/Controllers/Display/TextDisplayController.php b/app/Http/Controllers/Display/TextDisplayController.php
index 164939f0..e4c43faf 100644
--- a/app/Http/Controllers/Display/TextDisplayController.php
+++ b/app/Http/Controllers/Display/TextDisplayController.php
@@ -72,9 +72,13 @@ public function showTranslation($translationAbbrev)
     {
         $translation = $this->translationRepository->getByAbbrev($translationAbbrev);
         $books = $this->translationRepository->getBooks($translation);
-        return View::make('textDisplay.translation',
-            ['translation' => $translation,
-                'books' => $books]);
+        return View::make(
+            'textDisplay.translation',
+            [
+                'translation' => $translation,
+                'books' => $books
+            ]
+        );
     }
 
     public function showReferenceText($reference)
@@ -103,7 +107,7 @@ public function showTranslatedReferenceText($translationAbbrev, $reference, $pre
                 'readingPlan' => $readingPlanDay ? $readingPlanDay->plan : null,
                 'readingPlanDay' => $readingPlanDay,
                 'nextDay' => $nextDay,
-				'canonicalRef' => str_replace(" ", "%20", $canonicalRef->toString()),
+                'canonicalRef' => str_replace(" ", "%20", $canonicalRef->toString()),
                 'verseContainers' => $verseContainers,
                 'translation' => $translation,
                 'translations' => $translations,
@@ -112,23 +116,23 @@ public function showTranslatedReferenceText($translationAbbrev, $reference, $pre
                 'teaser' => $this->textService->getTeaser($verseContainers),
                 'chapterLinks' => $chapterLinks,
                 'translationLinks' => $translations->map(
-                        function ($otherTranslation) use ($canonicalRef, $translation) {
-                            $allBooksExistInTranslation = true;
-                            foreach ($canonicalRef->bookRefs as $bookRef) {
-                                $book = $this->bookRepository->getByAbbrevForTranslation($bookRef->bookId, $translation->id);
-                                if (!$this->getAllBookTranslations($book->number)->contains($otherTranslation->id)) {
-                                    $allBooksExistInTranslation = false;
-                                    break;
-                                }
+                    function ($otherTranslation) use ($canonicalRef, $translation) {
+                        $allBooksExistInTranslation = true;
+                        foreach ($canonicalRef->bookRefs as $bookRef) {
+                            $book = $this->bookRepository->getByAbbrevForTranslation($bookRef->bookId, $translation->id);
+                            if (!$this->getAllBookTranslations($book->usx_code)->contains($otherTranslation->id)) {
+                                $allBooksExistInTranslation = false;
+                                break;
                             }
-                            return [
-                                'id' => $otherTranslation->id,
-                                'link' => $this->referenceService->getCanonicalUrl($canonicalRef, $otherTranslation->id, $translation->id),
-                                'abbrev' => $otherTranslation->abbrev,
-                                'enabled' => $allBooksExistInTranslation
-                            ];
                         }
-                    )
+                        return [
+                            'id' => $otherTranslation->id,
+                            'link' => $this->referenceService->getCanonicalUrl($canonicalRef, $otherTranslation->id, $translation->id),
+                            'abbrev' => $otherTranslation->abbrev,
+                            'enabled' => $allBooksExistInTranslation
+                        ];
+                    }
+                )
             ]);
         } catch (ParsingException $e) {
             // as this doesn't look like a valid reference
@@ -176,8 +180,8 @@ private function bookView($translationAbbrev, CanonicalReference $canonicalRef)
         $translation = $this->translationRepository->getByAbbrev($translationAbbrev ? $translationAbbrev : Config::get('settings.defaultTranslationAbbrev'));
         $translatedRef = $this->referenceService->translateReference($canonicalRef, $translation->id);
         $book = $this->bookRepository->getByAbbrevForTranslation($translatedRef->bookRefs[0]->bookId, $translation->id);
-        if ($book) {           
-            $chapters = [];                    
+        if ($book) {
+            $chapters = [];
             $verses = $this->verseRepository->getVerses($book->id);
             $groupedVerses = [];
             foreach ($verses as $verse) {
@@ -211,17 +215,17 @@ private function bookView($translationAbbrev, CanonicalReference $canonicalRef)
                 if ($type == 'text' || $type == 'poemLine') {
                     $verseContainer = new VerseContainer($book);
                     $verseContainer->addVerse($verse);
-                    $oldText = "";                    
-                    if(isset($chapters[$verse['chapter']]['leadVerses'])) {                   
+                    $oldText = "";
+                    if (isset($chapters[$verse['chapter']]['leadVerses'])) {
                         if (array_has($chapters[$verse['chapter']]['leadVerses'], $verse['numv'])) {
                             $oldText = $chapters[$verse['chapter']]['leadVerses'][$verse['numv']];
                         }
                     }
                     $chapters[$verse['chapter']]['leadVerses'][$verse['numv']] = $oldText . $this->textService->getTeaser([$verseContainer]);
                 }
-            }            
+            }
             $allTranslations = $this->translationRepository->getAllOrderedByDenom();
-            $bookTranslations = $this->getAllBookTranslations($book->number);
+            $bookTranslations = $this->getAllBookTranslations($book->usx_code);
             return View::make('textDisplay.book', [
                 'translation' => $translation,
                 'reference' => $translatedRef,
@@ -230,15 +234,16 @@ private function bookView($translationAbbrev, CanonicalReference $canonicalRef)
                 'headings' => $chapterHeadings,
                 'translations' => $allTranslations,
                 'translationLinks' => $allTranslations->map(
-                        function ($translation) use ($canonicalRef, $bookTranslations) {
-                            $bookExistsInTranslation = $bookTranslations->contains($translation->id);
-                            return [
-                                'id' => $translation->id,
-                                'link' => $this->referenceService->getCanonicalUrl($canonicalRef, $translation->id),
-                                'abbrev' => $translation->abbrev,
-                                'enabled' => $bookExistsInTranslation];
-                        }
-                    )
+                    function ($translation) use ($canonicalRef, $bookTranslations) {
+                        $bookExistsInTranslation = $bookTranslations->contains($translation->id);
+                        return [
+                            'id' => $translation->id,
+                            'link' => $this->referenceService->getCanonicalUrl($canonicalRef, $translation->id),
+                            'abbrev' => $translation->abbrev,
+                            'enabled' => $bookExistsInTranslation
+                        ];
+                    }
+                )
 
             ]);
 
@@ -289,12 +294,20 @@ private function createChapterLinks(CanonicalReference $canonicalReference, Tran
      * @param $book
      * @return mixed
      */
-    private function getAllBookTranslations($bookNumber)
+    private function getAllBookTranslations($usxCode)
     {
-        $translations = $this->translationRepository->getAllOrderedByDenom()->filter(function ($translation) use ($bookNumber) {
-                return $this->bookRepository->getByNumberForTranslation($bookNumber, $translation->id);
-            }
-        );
+        $translations = $this
+            ->translationRepository
+            ->getAllOrderedByDenom()
+            ->filter(
+                function ($translation) use ($usxCode) {
+                    return $this->bookRepository
+                        ->getByUsxCodeForTranslation(
+                            $usxCode,
+                            $translation->id
+                        );
+                }
+            );
         return $translations;
     }
 }
diff --git a/app/Http/Controllers/Search/SearchController.php b/app/Http/Controllers/Search/SearchController.php
index 19d57956..07e1e21d 100644
--- a/app/Http/Controllers/Search/SearchController.php
+++ b/app/Http/Controllers/Search/SearchController.php
@@ -6,6 +6,7 @@
 use Request;
 use Redirect;
 use Response;
+use SzentirasHu\Data\UsxCodes;
 use SzentirasHu\Http\Controllers\Controller;
 use SzentirasHu\Service\Reference\CanonicalReference;
 use SzentirasHu\Service\Reference\ParsingException;
@@ -171,21 +172,21 @@ private function searchFullText($form, $view)
         return $view;
     }
 
-    private function extractBookNumbers($form)
+    private function extractBookUsxCodes($form)
     {
-        $bookIds = [];
+        $bookUsxCodes = [];
         if ($form->book) {
             if ($form->book == 'old_testament') {
-                $bookIds = range(101, 146);
+                $bookUsxCodes = UsxCodes::OLD_TESTAMENT;
             } else if ($form->book == 'new_testament') {
-                $bookIds = range(201, 227);
+                $bookUsxCodes = UsxCodes::NEW_TESTAMENT;
             } else if ($form->book == 'all') {
-                $bookIds = [];
+                $bookUsxCodes = [];
             } else {
-                $bookIds = [$form->book];
+                $bookUsxCodes = [$form->book];
             }
         }
-        return $bookIds;
+        return $bookUsxCodes;
     }
 
     /**
@@ -219,7 +220,7 @@ private function createFullTextSearchParams($form)
         if ($form->translation) {
             $searchParams->translationId = $form->translation->id;
         }
-        $searchParams->bookNumbers = $this->extractBookNumbers($form);
+        $searchParams->usxCodes = $this->extractBookUsxCodes($form);
         $searchParams->synonyms = true;
         $searchParams->grouping = $form->grouping;        
         return $searchParams;
diff --git a/app/Service/Reference/ReferenceService.php b/app/Service/Reference/ReferenceService.php
index 90ca5f16..6267b323 100644
--- a/app/Service/Reference/ReferenceService.php
+++ b/app/Service/Reference/ReferenceService.php
@@ -64,7 +64,7 @@ private function findStoredBookRef($bookRef, $translationId, $refTranslationId =
         $result = null;
         $abbreviatedBook = $this->bookRepository->getByAbbrev($bookRef->bookId, $refTranslationId);
         if ($abbreviatedBook) {
-            $book = $this->bookRepository->getByNumberForTranslation($abbreviatedBook->number, $translationId);
+            $book = $this->bookRepository->getByUsxCodeForTranslation($abbreviatedBook->usx_code, $translationId);
             if ($book) {
                 $result = new BookRef($book->abbrev);
                 $result->chapterRanges = $bookRef->chapterRanges;
@@ -145,11 +145,11 @@ public function getPrevNextChapter($canonicalReference, $translationId)
         return [$prevRef, $nextRef];
     }
 
-    public function createReferenceFromNumbers($bookNumber, $chapterNumber, int $verseNumber, $translation = null) {
+    public function createReferenceFromNumbers($usxCode, $chapterNumber, int $verseNumber, $translation = null) {
         if ($translation == null) {
             $translation = $this->translationService->getDefaultTranslation();
         }
-        $book = $this->bookRepository->getByNumberForTranslation($bookNumber, $translation->id);
+        $book = $this->bookRepository->getByUsxCodeForTranslation($usxCode, $translation->id);
         $ref = CanonicalReference::fromBookChapterVerse($book->abbrev, $chapterNumber, $verseNumber);
         return $ref;
     }
diff --git a/app/Service/Search/FullTextSearchParams.php b/app/Service/Search/FullTextSearchParams.php
index fcd84781..ff4c6e62 100644
--- a/app/Service/Search/FullTextSearchParams.php
+++ b/app/Service/Search/FullTextSearchParams.php
@@ -10,7 +10,7 @@ class FullTextSearchParams {
 
     public $text;
     public $translationId;
-    public $bookNumbers;
+    public $usxCodes;
     public $limit;
     public $grouping;
     public $synonyms;
diff --git a/app/Service/Search/SearchService.php b/app/Service/Search/SearchService.php
index 27c00aa0..cbd9be6e 100644
--- a/app/Service/Search/SearchService.php
+++ b/app/Service/Search/SearchService.php
@@ -137,7 +137,6 @@ private function handleFullTextResults($sphinxResults, FullTextSearchParams $par
             }          
             $trans = $translations[$verse['attrs']['trans']];
             if(!array_key_exists($trans['abbrev'], $results[$key]['translations']) or $results[$key]['translations'][$trans['abbrev']] == array() ) {
-                //$book = $this->bookRepository->getByNumberForTranslation($verse['attrs']['book_number'],$verse['attrs']['trans']);
                 $results[$key]['translations'][$trans['abbrev']] = [                       
                        'verseIds' => [],
                        'verses' => [],
diff --git a/app/Service/Search/SphinxSearcher.php b/app/Service/Search/SphinxSearcher.php
index 40175712..592680f6 100644
--- a/app/Service/Search/SphinxSearcher.php
+++ b/app/Service/Search/SphinxSearcher.php
@@ -79,8 +79,8 @@ public function __construct(FullTextSearchParams $params)
         if ($params->translationId) {
             $this->sphinxClient->filter('trans', $params->translationId);
         }
-        if ($params->bookNumbers !== null && count($params->bookNumbers) > 0) {
-            $this->sphinxClient->filter('book_number', $params->bookNumbers);
+        if ($params->usxCodes !== null && count($params->usxCodes) > 0) {
+            $this->sphinxClient->filter('usx_code', $params->usxCodes);
         }
         $this->params = $params;
     }
diff --git a/app/Service/Text/BookService.php b/app/Service/Text/BookService.php
index 5ab6458c..1395a54a 100644
--- a/app/Service/Text/BookService.php
+++ b/app/Service/Text/BookService.php
@@ -20,7 +20,7 @@ public function getBooksForTranslation(Translation $translation) {
     }
 
     public function getChapterCount(Book $book, Translation $translation) {
-        return $this->verseRepository->getMaxChapterByBookNumber($book->number, $translation->id);
+        return $this->verseRepository->getMaxChapterByBookUsxCode($book->usx_code, $translation->id);
     }
 
 }
diff --git a/bible_import/UsxCodes.md b/bible_import/UsxCodes.md
new file mode 100644
index 00000000..b980dab7
--- /dev/null
+++ b/bible_import/UsxCodes.md
@@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude
+REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A, e.g. a hymnal
+XXB - Extra B
+XXC - Extra C
+XXD - Extra D
+XXE - Extra E
+XXF - Extra F
+XXG - Extra G
+FRT - Front Matter
+BAK - Back Matter
+OTH - Other Matter
+INT - Introduction
+CNC - Concordance
+GLO - Glossary
+TDX - Topical Index
+NDX - Names Index
\ No newline at end of file
diff --git a/database/migrations/2025_02_11_173803_add_usx_code.php b/database/migrations/2025_02_11_173803_add_usx_code.php
new file mode 100644
index 00000000..978b18f3
--- /dev/null
+++ b/database/migrations/2025_02_11_173803_add_usx_code.php
@@ -0,0 +1,150 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+use SzentirasHu\Data\Entity\Book;
+use SzentirasHu\Data\Entity\Translation;
+use SzentirasHu\Data\UsxCodes;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        $prefix = Config::get('database.connections.bible.prefix');
+
+        $bookNumberAndTranslationToUsxMapping =
+            $this->bookNumberAndTranslationToUsxMapping();
+
+        $this->addUsxCodeRelatedColumns(
+            $prefix,
+            $bookNumberAndTranslationToUsxMapping
+        );
+
+        Schema::table('translations', function (Blueprint $table): void {
+            $table->unique('abbrev');
+        });
+
+        DB::statement(
+            "UPDATE {$prefix}tdverse SET gepi = CONCAT(usx_code, '_', chapter, '_', numv);"
+        );
+
+        $this->dropUnnecessaryBookRelatedColumns();
+    }
+
+    public function down(): void
+    {
+        Schema::table('tdverse', function (Blueprint $table): void {
+            $table->dropColumn('usx_code');
+        });
+
+        Schema::table('translations', function (Blueprint $table): void {
+            $table->dropUnique('abbrev');
+        });
+
+        Schema::table('books', function (Blueprint $table): void {
+            $table->dropColumn('usx_code');
+            $table->renameColumn('order', 'number');
+        });
+
+        Schema::table('tdverse', function (Blueprint $table): void {
+            $table->integer('book_number');
+        });
+    }
+
+
+    private function dropUnnecessaryBookRelatedColumns(): void
+    {
+        Schema::table('tdverse', function (Blueprint $table): void {
+            $table->dropColumn('book_number');
+        });
+    }
+
+    private function addUsxCodeRelatedColumns(
+        $prefix,
+        $bookNumberAndTranslationToUsxMapping
+    ): void {
+        Schema::table('books', function (Blueprint $table): void {
+            $table->renameColumn('number', 'order');
+            $table->string('usx_code', 3);
+        });
+
+        Schema::table('tdverse', function (Blueprint $table): void {
+            $table->string('usx_code', 3);
+        });
+
+        $this->updateUsxCodeForBookNumberAndTranslation(
+            $prefix,
+            $bookNumberAndTranslationToUsxMapping,
+            'tdverse',
+            'book_number',
+            'trans'
+        );
+        $this->updateUsxCodeForBookNumberAndTranslation(
+            $prefix,
+            $bookNumberAndTranslationToUsxMapping,
+            'books',
+            'order',
+            'translation_id'
+        );
+    }
+
+    private function bookNumberAndTranslationToUsxMapping(): array
+    {
+        $result = [];
+        $books = Book::all();
+        foreach ($books as $book) {
+            $translationId = $book->translation->id;
+            $translationAbbrev = $book->translation->abbrev;
+            $bookAbbrev = $book->abbrev;
+            $usx = UsxCodes::getUsxFromBookAbbrevAndTranslation($bookAbbrev, $translationAbbrev);
+            $key = $this->encodeBookAndTranslation($book->number, $translationId);
+            $result[$key] = $usx;
+        }
+        return $result;
+    }
+
+    private function translationAbbrevToIdMapping(): array
+    {
+        $result = [];
+        $translations = Translation::all();
+        foreach ($translations as $translation) {
+            $result[$translation->abbrev] = $translation->id;
+        }
+        return $result;
+    }
+
+    private function updateUsxCodeForBookNumberAndTranslation(
+        string $prefix,
+        array $mapping,
+        string $tableName,
+        string $bookColumn,
+        string $transColumn
+    ): void {
+        $ids = [];
+        $caseStatement = "CASE CONCAT(`{$bookColumn}`, '|', `{$transColumn}`) ";
+
+        foreach ($mapping as $encodedBookAndTranslation => $usxCode) {
+            $ids[] = "'{$encodedBookAndTranslation}'";
+            $caseStatement .= "WHEN '{$encodedBookAndTranslation}' THEN '{$usxCode}' ";
+        }
+
+        $caseStatement .= "END";
+
+        $idsList = implode(',', $ids);
+
+        $statement = "UPDATE {$prefix}{$tableName} SET usx_code = {$caseStatement} WHERE CONCAT(`{$bookColumn}`, '|', `{$transColumn}`) IN ({$idsList})";
+        DB::statement($statement);
+    }
+
+    private function isInvalidRow(array $row): bool
+    {
+        return count($row) !== 2;
+    }
+
+    private function encodeBookAndTranslation(int $bookNumber, int $translation): string
+    {
+        return "{$bookNumber}|{$translation}";
+    }
+};
+
diff --git a/database/seeders/BookTableSeeder.php b/database/seeders/BookTableSeeder.php
index 08fa2ec0..705ddbb9 100644
--- a/database/seeders/BookTableSeeder.php
+++ b/database/seeders/BookTableSeeder.php
@@ -2,6 +2,8 @@
 
 namespace Database\Seeders;
 
+use SzentirasHu\Data\UsxCodes;
+
 
 /**
 
@@ -22,12 +24,13 @@ public function run()
         $this->addBook(99202, 102, "2Móz", $translation);
     }
 
-    private function addBook($id, $number, $abbrev, $translation)
+    private function addBook($id, $order, $abbrev, $translation)
     {
         $book = new \SzentirasHu\Data\Entity\Book();
         $book->id = $id;
-        $book->number = $number;
+        $book->order = $order;
         $book->abbrev = $abbrev;
+        $book->usx_code = UsxCodes::getUsxFromBookAbbrevAndTranslation($abbrev);
         $book->translation()->associate($translation);
         $book->save();
         return $book;
diff --git a/database/seeders/VersesTableSeeder.php b/database/seeders/VersesTableSeeder.php
index d650c910..babee35b 100644
--- a/database/seeders/VersesTableSeeder.php
+++ b/database/seeders/VersesTableSeeder.php
@@ -9,21 +9,21 @@ class VersesTableSeeder extends \Illuminate\Database\Seeder
 
     public function run()
     {
-        $this->addVerse(99101, 1001, 'Ter', 2, 3);
-        $this->addVerse(99101, 1001, 'Ter', 2, 4);
-        $this->addVerse(99101, 1001, 'Ter', 50, 9);
-        $this->addVerse(99102, 1002, 'Kiv', 1, 1);
-        $this->addVerse(99102, 1002, 'Kiv', 2, 3);
-        $this->addVerse(99102, 1002, 'Kiv', 3, 3);
-        $this->addVerse(99201, 1001, '1Móz', 2, 3);
+        $this->addVerse(99101, 1001, 'GEN', 2, 3);
+        $this->addVerse(99101, 1001, 'GEN', 2, 4);
+        $this->addVerse(99101, 1001, 'GEN', 50, 9);
+        $this->addVerse(99102, 1002, 'EXO', 1, 1);
+        $this->addVerse(99102, 1002, 'EXO', 2, 3);
+        $this->addVerse(99102, 1002, 'EXO', 3, 3);
+        $this->addVerse(99201, 1001, 'GEN', 2, 3);
     }
 
-    private function addVerse($bookId, $bookNumber, $bookAbbrev, $chapter, $numv, $opts = [])
+    private function addVerse($bookId, $bookNumber, $usxCode, $chapter, $numv, $opts = [])
     {
         $v = new Verse();
         $v->trans = 1001;
         $v->gepi = sprintf("%d%03d%03d00", $bookNumber, $chapter, $numv);
-        $v->book_number = $bookNumber;
+        $v->usx_code = $usxCode;
         $v->book_id = $bookId;
         $v->chapter = $chapter;
         $v->numv = $numv;
diff --git a/deploy/dev/sphinx/sphinx.conf.in b/deploy/dev/sphinx/sphinx.conf.in
index a92d47e6..d4d96b6b 100644
--- a/deploy/dev/sphinx/sphinx.conf.in
+++ b/deploy/dev/sphinx/sphinx.conf.in
@@ -12,12 +12,12 @@ source verses_source
 	sql_port		= 3306
 
 	sql_query		= \
-		SELECT id, gepi, trans, book_number, chapter, numv, tip, verse \
+		SELECT id, gepi, trans, usx_code, chapter, numv, tip, verse \
 		FROM kar_tdverse \
         WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
 	sql_attr_uint = trans
-	sql_attr_uint = book_number
+	sql_attr_string = usx_code
 	sql_attr_uint = chapter
 	sql_attr_bigint = gepi
 	sql_attr_uint = tip
@@ -31,7 +31,7 @@ source verses_source
 source verses_root_source : verses_source
 {
     sql_query		= \
-    SELECT id, gepi, trans, book_number, chapter, numv, tip, verseroot \
+    SELECT id, gepi, trans, usx_code, chapter, numv, tip, verseroot \
     FROM kar_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 }
diff --git a/deploy/local/sphinx/sphinx.conf.in b/deploy/local/sphinx/sphinx.conf.in
index b95eba20..4119377c 100644
--- a/deploy/local/sphinx/sphinx.conf.in
+++ b/deploy/local/sphinx/sphinx.conf.in
@@ -12,12 +12,12 @@ source verses_source
 	sql_port		= __DB_PORT__
 
 	sql_query		= \
-		SELECT id, gepi, trans, book_number, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
+		SELECT id, gepi, trans, usx_code, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
 		FROM kar_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
 	sql_attr_uint = trans
-	sql_attr_uint = book_number
+	sql_attr_string = usx_code
 	sql_attr_uint = chapter
 	sql_attr_bigint = gepi
 	sql_attr_uint = tip
@@ -29,7 +29,7 @@ source verses_source
 source verses_root_source : verses_source
 {
     sql_query		= \
-    SELECT id, gepi, trans, book_number, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
+    SELECT id, gepi, trans, usx_code, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
     FROM kar_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
diff --git a/deploy/production/sphinx/sphinx.conf.in b/deploy/production/sphinx/sphinx.conf.in
index a7ef8c46..75f5026d 100644
--- a/deploy/production/sphinx/sphinx.conf.in
+++ b/deploy/production/sphinx/sphinx.conf.in
@@ -12,13 +12,13 @@ source verses_source
 	sql_port		= __DB_PORT__
 
 	sql_query		= \
-		SELECT id, gepi, trans, book_number, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
+		SELECT id, gepi, trans, usx_code, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
 		FROM kar_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704) \
     AND trans IN (1,3,4,5,6,7)
 
 	sql_attr_uint = trans
-	sql_attr_uint = book_number
+	sql_attr_string = usx_code
 	sql_attr_uint = chapter
 	sql_attr_bigint = gepi
 	sql_attr_uint = tip
@@ -31,7 +31,7 @@ source verses_source
 source verses_root_source : verses_source
 {
     sql_query		= \
-    SELECT id, gepi, trans, book_number, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
+    SELECT id, gepi, trans, usx_code, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
     FROM kar_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
diff --git a/deploy/staging/sphinx/sphinx.conf.in b/deploy/staging/sphinx/sphinx.conf.in
index 0a849b2c..48d319d0 100644
--- a/deploy/staging/sphinx/sphinx.conf.in
+++ b/deploy/staging/sphinx/sphinx.conf.in
@@ -12,12 +12,12 @@ source verses_source
 	sql_port		= 3306
 
 	sql_query		= \
-		SELECT id, gepi, trans, book_number, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
+		SELECT id, gepi, trans, usx_code, chapter, numv, tip, verse, verse as verse2, verse as verse3 \
 		FROM staging_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
 	sql_attr_uint = trans
-	sql_attr_uint = book_number
+	sql_attr_string = usx_code
 	sql_attr_uint = chapter
 	sql_attr_bigint = gepi
 	sql_attr_uint = tip
@@ -31,7 +31,7 @@ source verses_source
 source verses_root_source : verses_source
 {
     sql_query		= \
-    SELECT id, gepi, trans, book_number, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
+    SELECT id, gepi, trans, usx_code, chapter, numv, tip, verseroot, verseroot as verse, verseroot as verse2, verseroot as verse3 \
     FROM staging_tdverse \
     WHERE tip IN (60, 6, 901, 5, 10, 20, 30, 1, 2, 3, 401, 501, 601, 701, 703, 704)
 
diff --git a/resources/views/search/search.twig b/resources/views/search/search.twig
index 39f0eb47..bcb8eef5 100644
--- a/resources/views/search/search.twig
+++ b/resources/views/search/search.twig
@@ -24,7 +24,7 @@ Keresés{% if form.textToSearch is defined %}: {{ form.textToSearch }}{% endif %
                             <option value="old_testament" {{ 'old_testament' == form.book ? 'selected' }}>Ószövetség</option>
                             <option value="new_testament" {{ 'new_testament' == form.book ? 'selected' }}>Újszövetség</option>
                             {% for book in books %}
-                                <option value="{{ book.number }}" {{ book.number == form.book ? 'selected' }}>{{ book.name }}</option>
+                                <option value="{{ book.usx_code }}" {{ book.usx_code == form.book ? 'selected' }}>{{ book.name }}</option>
                             {% endfor %}
                         </select>
                     </div>
diff --git a/tests/feature/UsxCodesTest.php b/tests/feature/UsxCodesTest.php
new file mode 100644
index 00000000..bd61106b
--- /dev/null
+++ b/tests/feature/UsxCodesTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace SzentirasHu\Test;
+
+use App;
+use Mockery;
+use SzentirasHu\Test\Common\TestCase;
+use SzentirasHu\Data\UsxCodes;
+
+class UsxCodesTest extends TestCase
+{
+
+    public function testNewTestament(): void
+    {
+        $newTestamentBooks = UsxCodes::newTestamentUsx();
+        $this->assertCount(
+            27,
+            $newTestamentBooks,
+            "The New Testament books count should be 27."
+        );
+    }
+
+    public function testOldTestament(): void
+    {
+        $oldTestamentBooks = UsxCodes::oldTestamentUsx();
+        $this->assertCount(
+            46,
+            $oldTestamentBooks,
+            "The Old Testament books count should be 46."
+        );
+    }
+
+    public function testAllUsx(): void
+    {
+        $allUsx = UsxCodes::allUsx();
+        $this->assertCount(
+            73,
+            $allUsx,
+            "All Holy Scripture books count should be 73."
+        );
+    }
+
+    public function testGetUsxFromBookAbbrevAndTranslation(): void
+    {
+        $this->checkReturnedkUsxCode('default', 'Ter', 'GEN');
+        $this->checkReturnedkUsxCode('SZIT', 'Ter', 'GEN');
+        $this->checkReturnedkUsxCode('default', '1Móz', 'GEN');
+        $this->checkReturnedkUsxCode('default', 'Sirák', 'SIR');
+        $this->checkReturnedkUsxCode('RUF', 'Sir', 'LAM');
+        $this->checkReturnedkUsxCode('RUF', 'Sirák', null);
+        $this->checkReturnedkUsxCode('SZIT', 'Sir', 'SIR');
+        $this->checkReturnedkUsxCode('default', 'Jud', 'JUD');
+        $this->checkReturnedkUsxCode('default', 'Júd', 'JUD');
+        $this->checkReturnedkUsxCode('default', 'Judit', 'JDT');
+        $this->checkReturnedkUsxCode('SZIT', 'Jud', 'JDT');
+        $this->checkReturnedkUsxCode('SZIT', 'Júd', 'JUD');
+    }
+
+    public function testGetUsxFromBookAbbrevAndTranslationEdgeCases(): void
+    {
+        $this->checkReturnedkUsxCode('noSuchTranslation', 'Ter', 'GEN');
+        $this->checkReturnedkUsxCode('default', 'noSuchAbbrev', null);
+        $this->checkReturnedkUsxCode('noSuchTranslation', 'noSuchAbbrev', null);
+    }
+
+    public function testGetPreferredAbbreviation(): void
+    {
+        $this->checkReturnedAbbrev('SZIT', 'GEN', 'Ter');
+        $this->checkReturnedAbbrev('RUF', 'GEN', '1Móz');
+
+        $this->checkReturnedAbbrev('default', 'SIR', 'Sirák');
+        $this->checkReturnedAbbrev('SZIT', 'SIR', 'Sir');
+        $this->checkReturnedAbbrev('RUF', 'SIR', null);
+        $this->checkReturnedAbbrev('KNB', 'SIR', 'Sirák');
+
+        $this->checkReturnedAbbrev('KNB', 'LAM', 'Siralm');
+        $this->checkReturnedAbbrev('RUF', 'LAM', 'Sir');
+        $this->checkReturnedAbbrev('SZIT', 'LAM', 'Siralm');
+    }
+
+    public function testGetPreferredAbbreviationEdgeCases(): void
+    {
+        $this->checkReturnedAbbrev('noSuchTranslation', 'GEN', 'Ter');
+        $this->checkReturnedAbbrev('default', 'noSuchUsx', null);
+        $this->checkReturnedAbbrev('noSuchTranslation', 'noSuchUsx', null);
+    }
+
+    private function checkReturnedkUsxCode(string $translation, string $bookAbbrev, ?string $expectedUsxCode): void
+    {
+        $this->assertEquals(
+            $expectedUsxCode,
+            UsxCodes::getUsxFromBookAbbrevAndTranslation($bookAbbrev, $translation),
+            "The book abbreviation $bookAbbrev should be mapped to $expectedUsxCode for $translation."
+        );
+    }
+
+    private function checkReturnedAbbrev(string $translation, string $usxCode, ?string $expectedAbbrev): void
+    {
+        $this->assertEquals(
+            $expectedAbbrev,
+            UsxCodes::getPreferredAbbreviation($usxCode, $translation),
+            "The usx $usxCode should have the preferred abbreviation $expectedAbbrev for $translation."
+        );
+    }
+}
diff --git a/tests/smoke/SmokeTest.php b/tests/smoke/SmokeTest.php
index d2409b42..101f7058 100644
--- a/tests/smoke/SmokeTest.php
+++ b/tests/smoke/SmokeTest.php
@@ -58,7 +58,6 @@ public function setUp() : void
                 'id' => 1001]
                 ]
             ));
-
     }
 
 
@@ -70,7 +69,6 @@ public function setUp() : void
     public function testBasicHomePage()
     {
         $this->get('/')->assertStatus(200);
-
     }
 
     public function testBasicTranslationPage()


Reviews:
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T21:05:19Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T21:10:06Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T21:18:45Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T22:10:37Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T22:16:05Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T22:18:25Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T22:37:36Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-13T23:39:46Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:16:19Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:19:26Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:20:47Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:22:26Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:23:19Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-02-14T12:25:56Z
user: login: briff
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-01T18:45:55Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T15:18:36Z
user: login: gabre
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T15:24:16Z
user: login: briff
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-05T15:31:06Z
user: login: briff
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-07T10:52:18Z

Review comments:
path: database/seeders/VersesTableSeeder.php
user: login: briff
body: Ezt majd szerintem másképp kell majd csinálni, a gepi kód generálása így nem jó :( az 1001 stb. helyett kéne az usx kód, a rövidítés meg nincs is használva... Majd kijavítom
created_at: 2025-02-13T21:04:35Z
author_association: COLLABORATOR
path: database/seeders/VersesTableSeeder.php
user: login: briff
body: Az a baj, hogy koncepcionálisan gond van a gepi-vel. Szerintem csináljuk meg most egy füst alatt ezt is. Szerintem nem lesz nagyon fájdalmas.
- change type to string(16)
- rename gepi to usx_id
- a tartalma: "{$usx_code} {$chapter}:{$numv}"
Magyarul redundáns mező, de rengeteg helyen van használva, hívhatjuk gepinek (mint gépi, a szép hagyományoknak megfelelően :) De így tiszta lenne itt is, hogy minek kell benne lennie ebben a seederben. És az XML-ben is ezek vannak használva.
Ez egyébként elég jól definiálva van ráadásul az usx-ben, hogy mit jelent. Az adott fordítás versification scheme-jének megfelelő beosztást jelenti, tehát nem ez a mező szolgál arra, hogy a különböző fordítások között váltsunk.
created_at: 2025-02-13T21:10:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,103 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('number', 'order');
+            $table->string('usx_code', 3);
+        });
+
+        Schema::table('translations', function (Blueprint $table) {
+            $table->unique('abbrev');
+        });
+
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('book_id', 'usx_code');
+        });
+
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('book_number', 'usx_code');
+        });
+
+        $this->updateUsxCodeForAbbrev(
+            $this->getUsxCodeMapping(),
+            ['books', 'book_abbrevs']
+        );
+    }
+
+    public function down(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('order', 'number');
+            $table->dropColumn('usx_code');
+        });
+    
+        Schema::table('translations', function (Blueprint $table) {
+            $table->dropUnique(['abbrev']);
+        });
+    
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_id');
+        });
+    
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_number');
+        });
+    }
+
+    private function updateUsxCodeForAbbrev(array $mapping, array $tables): void
+    {
+        $ids = [];
+        $caseStatement = "CASE abbrev ";
+        foreach ($mapping as $abbrev => $usxCode) {
+            $ids[] = "'{$abbrev}'";
+            $caseStatement .= "WHEN '{$abbrev}' THEN '{$usxCode}' ";
+        }
+        $caseStatement .= "END";
+
+        $idsList = implode(',', $ids);
+        foreach ($tables as $tableName) {
+            DB::statement("UPDATE {$tableName} SET usx_code = {$caseStatement} WHERE abbrev IN ({$idsList})");
+        }
+    }
+
+    private function getUsxCodeMapping(): array
+    {
+        $csvFile = database_path('mappings/book_usx_codes.csv');
path: database/migrations/2025_02_11_173803_add_usx_code.php
user: login: briff
body: ez a csv be van commitolva?
created_at: 2025-02-13T21:18:06Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,103 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('number', 'order');
+            $table->string('usx_code', 3);
+        });
+
+        Schema::table('translations', function (Blueprint $table) {
+            $table->unique('abbrev');
+        });
+
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('book_id', 'usx_code');
+        });
+
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('book_number', 'usx_code');
+        });
+
+        $this->updateUsxCodeForAbbrev(
+            $this->getUsxCodeMapping(),
+            ['books', 'book_abbrevs']
+        );
+    }
+
+    public function down(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('order', 'number');
+            $table->dropColumn('usx_code');
+        });
+    
+        Schema::table('translations', function (Blueprint $table) {
+            $table->dropUnique(['abbrev']);
+        });
+    
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_id');
+        });
+    
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_number');
+        });
+    }
+
+    private function updateUsxCodeForAbbrev(array $mapping, array $tables): void
+    {
+        $ids = [];
+        $caseStatement = "CASE abbrev ";
+        foreach ($mapping as $abbrev => $usxCode) {
+            $ids[] = "'{$abbrev}'";
+            $caseStatement .= "WHEN '{$abbrev}' THEN '{$usxCode}' ";
+        }
+        $caseStatement .= "END";
+
+        $idsList = implode(',', $ids);
+        foreach ($tables as $tableName) {
+            DB::statement("UPDATE {$tableName} SET usx_code = {$caseStatement} WHERE abbrev IN ({$idsList})");
+        }
+    }
+
+    private function getUsxCodeMapping(): array
+    {
+        $csvFile = database_path('mappings/book_usx_codes.csv');
path: database/migrations/2025_02_11_173803_add_usx_code.php
user: login: gabre
body: Nincs, mert még nem is létezik, de közben rájöttem, hogy inkább nem is CSV lesz.
created_at: 2025-02-13T22:10:37Z
author_association: COLLABORATOR
path: database/seeders/VersesTableSeeder.php
user: login: gabre
body: Akkor ez legyen a gepi-ben?
"{$usx_code} {$chapter}:{$numv}"

Eddig nem néztem a kódban még meg, de tulajdonképpen mire kellett ez? Az eredeti Excel-ekben ez egy kód volt, oké, de mi már akkor is book_numbert csináltunk belőle.

Ez a fenti formátumú usx_id hol van definiálva a szabványban?

Arra már rájöttem, hogy amit mi usx_code-nak hívunk, az a https://ubsicap.github.io/usx/vocabularies.html#usx-vocab-bookcode szerint book_code.
created_at: 2025-02-13T22:16:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,103 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration {
+    public function up(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('number', 'order');
+            $table->string('usx_code', 3);
+        });
+
+        Schema::table('translations', function (Blueprint $table) {
+            $table->unique('abbrev');
+        });
+
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('book_id', 'usx_code');
+        });
+
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('book_number', 'usx_code');
+        });
+
+        $this->updateUsxCodeForAbbrev(
+            $this->getUsxCodeMapping(),
+            ['books', 'book_abbrevs']
+        );
+    }
+
+    public function down(): void
+    {
+        Schema::table('books', function (Blueprint $table) {
+            $table->renameColumn('order', 'number');
+            $table->dropColumn('usx_code');
+        });
+    
+        Schema::table('translations', function (Blueprint $table) {
+            $table->dropUnique(['abbrev']);
+        });
+    
+        Schema::table('book_abbrevs', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_id');
+        });
+    
+        Schema::table('tdverse', function (Blueprint $table) {
+            $table->renameColumn('usx_code', 'book_number');
+        });
+    }
+
+    private function updateUsxCodeForAbbrev(array $mapping, array $tables): void
+    {
+        $ids = [];
+        $caseStatement = "CASE abbrev ";
+        foreach ($mapping as $abbrev => $usxCode) {
+            $ids[] = "'{$abbrev}'";
+            $caseStatement .= "WHEN '{$abbrev}' THEN '{$usxCode}' ";
+        }
+        $caseStatement .= "END";
+
+        $idsList = implode(',', $ids);
+        foreach ($tables as $tableName) {
+            DB::statement("UPDATE {$tableName} SET usx_code = {$caseStatement} WHERE abbrev IN ({$idsList})");
+        }
+    }
+
+    private function getUsxCodeMapping(): array
+    {
+        $csvFile = database_path('mappings/book_usx_codes.csv');
path: database/migrations/2025_02_11_173803_add_usx_code.php
user: login: gabre
body: Lesz két map, abbreviation -> usx_code, egy az új-, egy pedig az ószövetségre. Le lehet majd kérni a kettőt egyben (hogy egy olyat kapjunk, mint amit most a getUsxCodeMapping adna) és külön-külön (a kereséshez, extractBookNumbers helyett).
created_at: 2025-02-13T22:18:25Z
author_association: COLLABORATOR
path: database/seeders/VersesTableSeeder.php
user: login: briff
body: Igen! A gepi azonosít egy verset, ez a "gépi azonosító". Ez jelenti a benne levő címsorokat, kereszthivatkozásokat, lábjegyzeteket stb. Így tudjuk összegyűjteni, hogy amikor megjelentjük mondjuk a Mt4,5-öt, akkor pontosan mely adatbázisrekordok relevánsak (az, ahol a gepi 201004005 - ennek az usx megfelelője 'MAT 4:5') Egy kicsit az usx ennél rugalmasabb, ott egy folyamatos szövegben, ami bekezdésekből áll a vers azonosítók csupán "milestone"-ok, hogy "itt kezdődik a MAT 4:5" és "itt ér véget a MAT 4:5", illetve a bekezdésben (para) még lehet egy olyan tag, hogy "ez a bekezdés a MAT 4:5 folytatása". Ez itt van specifikálva: https://ubsicap.github.io/usx/elements.html#verse De nem kell nagyon megijedni ettől, nekünk annyi a fontos, hogy a gepi azonosítókról áttérhetünk az usx-esre. Az új XML-ek sem követik az USX-et ebben (sem).
created_at: 2025-02-13T22:37:36Z
author_association: COLLABORATOR
path: database/seeders/VersesTableSeeder.php
user: login: briff
body: Sok helyen van html id-nek használva. Legyen inkább `{$usx_code}_{$chapter}_{$numv}` és akkor nem tör el semmit
created_at: 2025-02-13T23:39:46Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: briff
body: ez nem usx kód :)
created_at: 2025-02-14T12:16:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: gabre
body: book code az usx-hez a https://ubsicap.github.io/usx/vocabularies.html#usx-vocab-bookcode oldalrol.
vagy arra celzol hogy el van csuszva az exempli gratia?
created_at: 2025-02-14T12:19:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: briff
body: az 'e.g. a hymnal' benn maradt. 
created_at: 2025-02-14T12:20:47Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: briff
body: de mindegy, azt hittem, ez egy felsorolás, sorry.
created_at: 2025-02-14T12:22:26Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: gabre
body: Szerintem az a sor az azt megelozo sorhoz, az extrakhoz tartozik, csak elcsuszott.
created_at: 2025-02-14T12:23:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,114 @@
+GEN - Genesis
+EXO - Exodus
+LEV - Leviticus
+NUM - Numbers
+DEU - Deuteronomy
+JOS - Joshua
+JDG - Judges
+RUT - Ruth
+1SA - 1 Samuel
+2SA - 2 Samuel
+1KI - 1 Kings
+2KI - 2 Kings
+1CH - 1 Chronicles
+2CH - 2 Chronicles
+EZR - Ezra
+NEH - Nehemiah
+EST - Esther (Hebrew)
+JOB - Job
+PSA - Psalms
+PRO - Proverbs
+ECC - Ecclesiastes
+SNG - Song of Songs
+ISA - Isaiah
+JER - Jeremiah
+LAM - Lamentations
+EZK - Ezekiel
+DAN - Daniel (Hebrew)
+HOS - Hosea
+JOL - Joel
+AMO - Amos
+OBA - Obadiah
+JON - Jonah
+MIC - Micah
+NAM - Nahum
+HAB - Habakkuk
+ZEP - Zephaniah
+HAG - Haggai
+ZEC - Zechariah
+MAL - Malachi
+MAT - Matthew
+MRK - Mark
+LUK - Luke
+JHN - John
+ACT - Acts
+ROM - Romans
+1CO - 1 Corinthians
+2CO - 2 Corinthians
+GAL - Galatians
+EPH - Ephesians
+PHP - Philippians
+COL - Colossians
+1TH - 1 Thessalonians
+2TH - 2 Thessalonians
+1TI - 1 Timothy
+2TI - 2 Timothy
+TIT - Titus
+PHM - Philemon
+HEB - Hebrews
+JAS - James
+1PE - 1 Peter
+2PE - 2 Peter
+1JN - 1 John
+2JN - 2 John
+3JN - 3 John
+JUD - Jude REV - Revelation
+TOB - Tobit
+JDT - Judith
+ESG - Esther Greek
+WIS - Wisdom of Solomon
+SIR - Sirach (Ecclesiasticus)
+BAR - Baruch
+LJE - Letter of Jeremiah
+S3Y - Song of 3 Young Men
+SUS - Susanna
+BEL - Bel and the Dragon
+1MA - 1 Maccabees
+2MA - 2 Maccabees
+3MA - 3 Maccabees
+4MA - 4 Maccabees
+1ES - 1 Esdras (Greek)
+2ES - 2 Esdras (Latin)
+MAN - Prayer of Manasseh
+PS2 - Psalm 151
+ODA - Odes
+PSS - Psalms of Solomon
+EZA - Apocalypse of Ezra
+5EZ - 5 Ezra
+6EZ - 6 Ezra
+DAG - Daniel Greek
+PS3 - Psalms 152-155
+2BA - 2 Baruch (Apocalypse)
+LBA - Letter of Baruch
+JUB - Jubilees
+ENO - Enoch 1MQ - 1 Meqabyan
+2MQ - 2 Meqabyan
+3MQ - 3 Meqabyan
+REP - Reproof 4BA - 4 Baruch
+LAO - Laodiceans
+XXA - Extra A
+e.g. a hymnal
path: bible_import/UsxCodes.md
user: login: gabre
body: most jobb?
created_at: 2025-02-14T12:25:55Z
author_association: COLLABORATOR
diff_hunk: @@ -64,7 +64,7 @@ private function findStoredBookRef($bookRef, $translationId, $refTranslationId =
         $result = null;
         $abbreviatedBook = $this->bookRepository->getByAbbrev($bookRef->bookId, $refTranslationId);
         if ($abbreviatedBook) {
-            $book = $this->bookRepository->getByNumberForTranslation($abbreviatedBook->number, $translationId);
+            $book = $this->bookRepository->getByOrderForTranslation($abbreviatedBook->order, $translationId);
path: app/Service/Reference/ReferenceService.php
user: login: briff
body: Ahol korábban a number volt használva, ott az usx code kell majd, nem az order.
created_at: 2025-03-01T18:44:15Z
author_association: COLLABORATOR
diff_hunk: @@ -22,6 +22,6 @@ public function getByAbbrev($bookAbbrev, $translationId = null);
      */
     public function getByAbbrevForTranslation($abbrev, $translationId);
 
-    public function getByNumberForTranslation($number, $translationId);
+    public function getByOrderForTranslation($order, $translationId);
path: app/Data/Repository/BookRepository.php
user: login: briff
body: ez getByUsxCodeForTranslation kell legyen (az order csak a sortinghoz kell legfeljebb)
created_at: 2025-03-01T18:45:40Z
author_association: COLLABORATOR
diff_hunk: @@ -22,6 +22,6 @@ public function getByAbbrev($bookAbbrev, $translationId = null);
      */
     public function getByAbbrevForTranslation($abbrev, $translationId);
 
-    public function getByNumberForTranslation($number, $translationId);
+    public function getByOrderForTranslation($order, $translationId);
path: app/Data/Repository/BookRepository.php
user: login: gabre
body: @briff most jobb lett?
created_at: 2025-03-05T15:18:35Z
author_association: COLLABORATOR
diff_hunk: @@ -64,7 +64,7 @@ private function findStoredBookRef($bookRef, $translationId, $refTranslationId =
         $result = null;
         $abbreviatedBook = $this->bookRepository->getByAbbrev($bookRef->bookId, $refTranslationId);
         if ($abbreviatedBook) {
-            $book = $this->bookRepository->getByNumberForTranslation($abbreviatedBook->number, $translationId);
+            $book = $this->bookRepository->getByOrderForTranslation($abbreviatedBook->order, $translationId);
path: app/Service/Reference/ReferenceService.php
user: login: gabre
body: Talán ezt sikerült javítanom.
created_at: 2025-03-05T15:24:16Z
author_association: COLLABORATOR
diff_hunk: @@ -22,6 +22,6 @@ public function getByAbbrev($bookAbbrev, $translationId = null);
      */
     public function getByAbbrevForTranslation($abbrev, $translationId);
 
-    public function getByNumberForTranslation($number, $translationId);
+    public function getByOrderForTranslation($order, $translationId);
path: app/Data/Repository/BookRepository.php
user: login: briff
body: Igen, pont ezekre a módosításokra gondoltam, ez így szuper lesz.
created_at: 2025-03-05T15:31:06Z
author_association: COLLABORATOR

PR comments:
user: login: gabre
created_at: 2025-02-15T14:10:52Z
body: @briff lehet, hogy valamit benéztem, de nem elég a verses-ben usx_code+order mint a books-ra vonatkozó idegen kulcs? (a book_id helyett)?
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-15T14:30:59Z
body: > @briff lehet, hogy valamit benéztem, de nem elég a verses-ben usx_code+order mint a books-ra vonatkozó idegen kulcs? (a book_id helyett)?

Alapvetően nem szeretem a naturális kulcsokat. A vers->book az több->egy kapcsolat, tehát egy olyan ID-ra van szükség, ami nem változik (attól, hogy mondjuk megváltoztatják a sorrendjét a könyveknek). Ilyen szempontból a verse-ben az usx_code és az order is fölösleges (ha van book_id) - csak performance improvement miatt van benne. 
Az ordert egyébként (ha jól néztem az import commitodat), én kihagynám a verse-ből, az a könyvek sorrendjét határozza meg. (Ráadásul nem is a versek sorrendjét). 
De munkamódszerként most azt javasolnám, hogy próbáld meg ezt végigvinni most minimális séma változtatással. Ha kb. megvagy, akkor be fogom merge-ölni az én branchembe, ott kijavítom a merge-conflictokat (amiből nem lesz kevés :)), és utána én fogom masterre merge-ölni. Mit szólsz?
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-15T14:39:47Z
body: > Az ordert egyébként (ha jól néztem az import commitodat), én kihagynám a verse-ből, az a könyvek sorrendjét határozza meg. (Ráadásul nem is a versek sorrendjét).

Igen, ez kihagyható valóban.

> A vers->book az több->egy kapcsolat, tehát egy olyan ID-ra van szükség, ami nem változik (attól, hogy mondjuk megváltoztatják a sorrendjét a könyveknek).

 Nekem úgy tűnik, hogy ez bár elméletben igaz, de gyakorlatban feltételezi, hogy az importer a könyveket (books) update-eli és nem törli + újra beszúrja, hiszen az utóbbi esetben (ha jól gondolom) úgy is új, autogenerated, autoincrement book_id keletkezik. Én a törlésre és újra beszúrásra készültem eddig. Ez nem jó?
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-15T14:48:16Z
body: > > Az ordert egyébként (ha jól néztem az import commitodat), én kihagynám a verse-ből, az a könyvek sorrendjét határozza meg. (Ráadásul nem is a versek sorrendjét).
> 
> Igen, ez kihagyható valóban.
> 
> > A vers->book az több->egy kapcsolat, tehát egy olyan ID-ra van szükség, ami nem változik (attól, hogy mondjuk megváltoztatják a sorrendjét a könyveknek).
> 
> Nekem úgy tűnik, hogy ez bár elméletben igaz, de gyakorlatban feltételezi, hogy az importer a könyveket (books) update-eli és nem törli + újra beszúrja, hiszen az utóbbi esetben (ha jól gondolom) úgy is új, autogenerated, autoincrement book_id keletkezik. Én a törlésre és újra beszúrásra készültem eddig. Ez nem jó?

De jó, csak mindenképpen maradjon a surrogate key, a book_id mint primary key.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-15T14:53:09Z
body: > De jó, csak mindenképpen maradjon a surrogate key, a book_id mint primary key.

Jó, csinálom így, de egyelőre nem látom, mit nyerünk ezzel. Kivéve a kézzel való átszámozást.

Először törölni fogom a books-t és a verse-t, utána beszúrom a books-t, majd megnézem, mik lettek a book_id-k. (Asszem a természetes kulccsal ez nem kellene.) Ezzel pedig be tudom szúrni a verse sorait.
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-15T16:34:02Z
body: > > De jó, csak mindenképpen maradjon a surrogate key, a book_id mint primary key.
> 
> Jó, csinálom így, de egyelőre nem látom, mit nyerünk ezzel. Kivéve a kézzel való átszámozást.
> 
> Először törölni fogom a books-t és a verse-t, utána beszúrom a books-t, majd megnézem, mik lettek a book_id-k. (Asszem a természetes kulccsal ez nem kellene.) Ezzel pedig be tudom szúrni a verse sorait.

Az "order" nem lehet id része. Ami _elvileg_ változhat, az nem lehet. Egyébként pedig a kapcsolatok kezelésében sokkal könnyebbek a surrogate keyek a véleményem szerint, ezért is tértünk át (meg még lehet, hogy valami más miatt is, nem annyira emlékszem) https://github.com/szentjozsefhackathon/szentiras.hu/blob/24708ec8af41a8df7c7a4910a0bc98c5e8be90c1/database/migrations/2014_05_23_081141_remove_composite_key_from_books.php
Az importernek entitásokkal kéne dolgoznia. Tehát ha jön az excelből egy book, azt létrehozod, mint Book objektum. Ezt pedig hozzácsatolod az új Verse objektumhoz mint objektum. Az id-kat egyebeket az Eloquent lekezeli.

author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-15T16:40:54Z
body: Ha már itt chatelünk, muszáj elmondanom @gabre hogy elmondhatatlanul hálás vagyok, hogy beküldted azt a dockeres pr-t annak idején. Számomra minden mostani előrelépésnek ez volt az alapja. 1000 thx.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-15T18:08:08Z
body: > Az importernek entitásokkal kéne dolgoznia. Tehát ha jön az excelből egy book, azt létrehozod, mint Book objektum. Ezt pedig hozzácsatolod az új Verse objektumhoz mint objektum. Az id-kat egyebeket az Eloquent lekezeli.

Lehet, hogy először létre kellene hozni a Book entitásokat és talán eltenni őket egy map-be, hogy `(translation, order) -> Book` meglegyen (mert így egyszerűnek tűnik a vershez társítás).
Utána versenként a Verse entitást "hozzáasszociálni" a Book-hoz (`$verse->book()->associate($book);` ahol egy lookuppal talán megvan a Book).
(Ezt lehet úgy is, hogy az adott Book-hoz tartozó Verse-eket createMany-vel létrehozni, de akkor lehet, hogy előre szétválogatnám a verseket `(translation, order)` szerint).

Vagy hogyan lehetne jobban @briff ? Én igazából nem ismerem a Laravelt és az Eloquent-et.
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-15T18:17:10Z
body: Egyből mehetnek a Book-ok a DB-be.A seeder osztályokban látsz példát.
new Book ... save hatására létre is jön a db-ben, megkapja az auto id-t.
Utána pedig a egyszerűen mehet versenként egy book lookup order meg translation alapján, aztán az így kapott bookra mehet az associate.
most hirtelen nem emlékszem a szintaxisra meg nem vagyok gépnél.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-15T18:48:08Z
body: > Egyből mehetnek a Book-ok a DB-be.A seeder osztályokban látsz példát.

Amikor a Bookok létrehozásáról írtam, erre gondoltam igazából, hogy a DB-be egyből beteszem (save talán).

> Utána pedig a egyszerűen mehet versenként egy book lookup order meg translation alapján, aztán az így kapott bookra mehet az associate.

A lookup helyett szerintem jobb lenne egy map-et használni, hogy optimalizáljuk a kereséseket. Mit gondolsz, érdemes lenne?

> most hirtelen nem emlékszem a szintaxisra meg nem vagyok gépnél.

A szintaxisra van példa a dokumentációban is és a ChatGPT is talán tud segíteni, de a framework konkrét lehetőségeit, korlátait nem ismerem sajnos és az AI is sokat képzelődik. Ezért kérdezem, hogy esetleg lehet-e totál más megközelítést alkalmazni, vagy van-e valami shortcut akár az Eloquent környékén.
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-16T09:48:24Z
body: Én is most tanulgatom újra a laravelt. Ha nagyon lassú a lookup, szerinrem a legegyszerűbb cache-t használni, Cache::remember és akkor ha megvan a cache-ben, akkor onnan adja. 
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-16T11:14:18Z
body: > Én is most tanulgatom újra a laravelt. Ha nagyon lassú a lookup, szerinrem a legegyszerűbb cache-t használni, Cache::remember és akkor ha megvan a cache-ben, akkor onnan adja.

Rendben, legyen így.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-27T14:19:21Z
body: @briff légyszi nézd meg az UsxCodes legutóbbi változatát.

Tényleg az alábbiakat kell tennem?

1. Book eloquent (ahogy írtad: "1. egy USX + Translation Abbrev párosból meg kell állapítanunk egy Book Abbreviation-t, ami az adott fordítás preferált rövidítése (Tehát páldául GEN + KNB = Ter, míg GEN + RUF = 1Móz)" és asszem a lenti függvény az összes abbreviation-t megadja, nem csak a preferáltat)
```
    public function abbrevs() {
        return $this->hasMany('SzentirasHu\Data\Entity\BookAbbrev', 'number');
    }
```
Ezt kellene úgy megváltoztatnom, hogy itt az UsxCodes-ból szedje ki az abbrev-eket translation és usx alapján a `getAbbreviations(string $usxCode, string $translation)` függvénnyel?

2. Book Repository (ahogy írtad: "2. Egy BookAbbreviation + Translation Abbrev párosból meg kell állapítanunk a USX code-ot." és ha jól látom az alábbi függvény még ehhez megadja a Book objektumot)
```
getByAbbrev($bookAbbrev, $translationId = null)
```
Itt a `getUsxFromBookAbbrevAndTranslation` függvénnyel kellene először a usx-et megkeresni és utána pedig a Book objektumot megtalálni usx és translation alapján?

3. A Book Abbrev mint class gondolom jobb, ha marad (ha nem marad, akkor hogyan fognak régebbi migrációk működni?) Feltételezem, akkor a mögöttes tábla is kell, hogy maradjon?
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-27T14:39:20Z
body: Először a 3.: A BookAbbrev maradjon, sajnos nem lett volna szabad migrációban Eloquentet használni, pont emiatt. @deprecated, a migrációkat meg majd javítjuk egyszer talán majd valamikor :)

1. 
```
    public function abbrevs() {
        return $this->hasMany('SzentirasHu\Data\Entity\BookAbbrev', 'number');
    }
```
Szerintem ez kuka, nincs is érdemben használva. Ha van is használva, ez így nem jó/értelmetlen, hiszen semmi értelme megadni egy USX kód alapján a potenciális rövidítéseket (amelyek csak fordítással együtt értelmesek amúgy).

Egy dologra van használva, az asszociáció másik oldala, a

2. BookRepository.getByAbbrev, amit viszont most a statikus fájl alapján kell implementálni, és igen, úgy kéne, ahogy írod!

4. A preferált rövidítést a Book táblában tartjuk nyilván, az abbrev mezőben, azt az importer update-elhetni a statikus adatok alapján. (Most ezt nem variálnám át.)


author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-27T14:49:48Z
body: - akkor a BookAbbrev marad és nem migrálok a tábláján semmit
- az `abbrevs()` valóban úgy tűnik, hogy nincsen használva
- a `BookRepository.getByAbbrev` -et megírom akkor.
- "A preferált rövidítést a Book táblában tartjuk nyilván, az abbrev mezőben, azt az importer update-elhetni a statikus adatok alapján." asszem ezt már implementáltam, de ellenőrzöm
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-28T09:13:03Z
body: @briff 

> "A preferált rövidítést a Book táblában tartjuk nyilván, az abbrev mezőben, azt az importer update-elhetni a statikus adatok alapján." asszem ezt már implementáltam, de ellenőrzöm

Az importer szerintem csak az Excel Sheet-ből tudja lekérni a bookAbbrev-et, hiszen onnan kéri az ordert is. A translationAbbrev (amely importkor adott) és a bookAbbrev a statikus adatok alapján meghatároznak egy usx kódot.
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-28T09:32:32Z
body: Igen, elvileg ugyanannak kell kijönnie: az UsxCodes-ban minden fordítás tudja a preferált rövidítést -> ennek kell bekerülnie a Book táblába.
De ugyanez a rövidítés szerepel az adott könyvhöz a "Könyvek" sheeten. Itt megint abba ütközünk, hogy a Ssz mező volt használva id-nak, csak megváltoztatta HG őket időnként. Ezért valójában valamelyik másik oszlop az id, ami alapján az USX kódot ki kell találnunk. Tehát nem ssz->USX mappinget kell csinálnunk, hanem Rövidítés+Translation -> USX mappinget, ami már elvileg szerepel is az UsxCodes-ban, tehát az Excelből a ssz információ az új, a rövidítés meg arra kell, hogy megtaláljuk, hogy mi a sorszám. (És meg is találjuk az rövidítés alapján az USX kódot, ami alapján meg megtaláljuk megint a rövidítést, aztán megint az USX kódot és így tovább :D) 

author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-28T09:40:15Z
body: > Igen, elvileg ugyanannak kell kijönnie: az UsxCodes-ban minden fordítás tudja a preferált rövidítést -> ennek kell bekerülnie a Book táblába.

Elvileg ugyanannak, igen.

> hanem Rövidítés+Translation -> USX mappinget

Hát pont azt írtam, hogy így találjuk ki az usx-et az importerben, nem? :) Az "ssz" pedig adja az ordert és szerintem ezzel kész van elméletben.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-28T13:03:50Z
body: @briff csak szembejött: lehetséges, hogy pl a getPureTextFromNumbers-t nem használjuk? Ha igen, akkor nem futtatunk valami analyser toolt és szedjük ki a unused függvényeket?
author_association: COLLABORATOR
user: login: briff
created_at: 2025-02-28T13:10:23Z
body: > @briff csak szembejött: lehetséges, hogy pl a getPureTextFromNumbers-t nem használjuk? Ha igen, akkor nem futtatunk valami analyser toolt és szedjük ki a unused függvényeket?

Igen, lehetséges. Ezt a from numberst még akkor csináltam, amikor nem emlékeztem, hogy a CanonicalReference osztály mennyire okos. Sokkal egyszerűbb a CanonicalReference::fromString-et használni mindenre. Hát jó lenne valami analyser túl. Annak idején én az IntelliJ Ultimate-tel fejlesztettem, az azért sokat segített, most VS Code (a PHPStormot emiatt az egy projekt miatt nem fogom előfizetni) ami közel sem annyira jó. Ha bármi jó kis analyser tool ötleted van, akkor jöhet.
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-28T13:13:24Z
body: En valami ilyet hasznalok: https://marketplace.visualstudio.com/items?itemName=bmewburn.vscode-intelephense-client
De inkabb keresek majd valami olyat, amit a konzolbol le lehet futtatni.

A unit tesztekhez az adatbazist hogy is kell elokesziteni?
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-02-28T13:14:53Z
body: Ahh asszem erre gondoltam:

```
php artisan migrate:refresh --env=testing
php artisan db:seed --env=testing
```
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-03-05T15:23:51Z
body: @briff PHPStan és/vagy Psalm, esetleg?

https://github.com/phpstan/phpstan/discussions/8986
author_association: COLLABORATOR
user: login: briff
created_at: 2025-03-05T15:33:09Z
body: > @briff PHPStan és/vagy Psalm, esetleg?
> 
> [phpstan/phpstan#8986](https://github.com/phpstan/phpstan/discussions/8986)

Ezt mindenképpen megnézem. Sőt van egy ilyen is... https://github.com/larastan/larastan
author_association: COLLABORATOR
user: login: gabre
created_at: 2025-03-07T06:35:16Z
body: - importer beimportálja a KNB-t
- artisan test lefut
author_association: COLLABORATOR