Add polymorphic relation update to migration script

This commit is contained in:
Burak Çakırel 2021-01-25 18:33:16 +03:00
parent 3181a058db
commit 30c6a22bf9
No known key found for this signature in database
GPG Key ID: 48FFBB7771B99C7C

View File

@ -9,6 +9,7 @@ use App\Models\Document\Document;
use App\Models\Setting\Category; use App\Models\Setting\Category;
use App\Utilities\Overrider; use App\Utilities\Overrider;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\File;
@ -22,7 +23,7 @@ class Version210 extends Listener
const VERSION = '2.1.0'; const VERSION = '2.1.0';
private $tables = [ private $tableRelations = [
Document::INVOICE_TYPE => [ Document::INVOICE_TYPE => [
'estimate_invoice', 'estimate_invoice',
'foriba_earchive_one_steps', 'foriba_earchive_one_steps',
@ -60,6 +61,11 @@ class Version210 extends Listener
private const CREDIT_NOTE_TYPE = 'credit-note'; private const CREDIT_NOTE_TYPE = 'credit-note';
private const DEBIT_NOTE_TYPE = 'debit-note'; private const DEBIT_NOTE_TYPE = 'debit-note';
/**
* @var Collection
*/
private $totals;
/** /**
* Handle the event. * Handle the event.
* *
@ -76,7 +82,7 @@ class Version210 extends Listener
Artisan::call('migrate', ['--force' => true]); Artisan::call('migrate', ['--force' => true]);
$this->copyDocuments(); $this->migrateDocuments();
#todo remove tax_id column #todo remove tax_id column
$this->copyItemTax(); $this->copyItemTax();
@ -84,28 +90,32 @@ class Version210 extends Listener
$this->deleteOldFiles(); $this->deleteOldFiles();
} }
private function copyDocuments() private function migrateDocuments()
{ {
try { try {
$this->removeAutoIncrements();
$this->addForeignKeys(); $this->addForeignKeys();
DB::transaction(function () { DB::transaction(function () {
$totals = collect($this->getTotals(['invoices', 'bills', 'estimates', 'credit_notes', 'debit_notes'])); $this->totals = collect($this->getTotals(['invoice', 'bill', 'estimate', 'credit_note', 'debit_note']));
// Sort table's count by ascending to improve performance. // Sort table's count by ascending to improve performance.
foreach ($totals->sort() as $table => $count) { foreach ($this->totals->sort() as $table => $count) {
$method = 'copy' . Str::studly($table); $method = 'copy' . Str::plural(Str::studly($table));
$this->$method(); $this->$method();
} }
$this->updateCreditNoteTransactionsTable(); $this->updateCreditNoteTransactionsTable();
$this->updateDocumentIds();
}); });
$this->renameTables();
} catch (\Exception $e) { } catch (\Exception $e) {
$this->revertTableRenaming(); $this->revertTableRenaming();
Log::error($e); Log::error($e);
} finally { } finally {
$this->addAutoIncrements();
$this->removeForeignKeys(); $this->removeForeignKeys();
foreach (['estimate', 'bill', 'invoice'] as $item) { foreach (['estimate', 'bill', 'invoice'] as $item) {
$this->removeDocumentIdForeignKeys($item); $this->removeDocumentIdForeignKeys($item);
@ -113,21 +123,333 @@ class Version210 extends Listener
} }
} }
public function updateCreditNoteTransactionsTable(): void private function updateInvoiceIds(): void
{
$sorted = $this->totals->sortDesc()->keys();
// Invoice ids do not changed
if ('invoice' === $sorted->first()) {
return;
}
$incrementAmount = $this->getIncrementAmount('invoice', 's');
if (0 === $incrementAmount) {
return;
}
DB::table('documents')
->where('type', 'invoice')
->whereNotNull('parent_id')
->where('parent_id', '<>', 0)
->increment('parent_id', $incrementAmount);
DB::table('transactions')
->where('type', 'income')
->whereNotNull('document_id')
->where('document_id', '<>', 0)
->increment('document_id', $incrementAmount);
}
private function updateBillIds(): void
{
$sorted = $this->totals->sortDesc()->keys();
// Bill ids do not changed
if ('bill' === $sorted->first()) {
return;
}
$incrementAmount = $this->getIncrementAmount('bill', 's');
if (0 === $incrementAmount) {
return;
}
DB::table('documents')
->where('type', 'bill')
->whereNotNull('parent_id')
->where('parent_id', '<>', 0)
->increment('parent_id', $incrementAmount);
DB::table('transactions')
->where('type', 'expense')
->whereNotNull('document_id')
->where('document_id', '<>', 0)
->increment('document_id', $incrementAmount);
}
private function updateDocumentIds()
{
$this->updateInvoiceIds();
$this->updateBillIds();
$tables = [
'recurring' => 'recurable',
'mediables' => 'mediable',
'project_activities' => 'activity',
'custom_fields_field_values' => 'model',
'double_entry_ledger' => 'ledgerable',
'inventory_histories' => 'type',
'magento_integrations' => 'item',
'opencart_integrations' => 'item',
'prestashop_integrations' => 'item',
'woocommerce_integrations' => 'item',
];
foreach ($tables as $table => $column) {
if (Schema::hasTable($table) === false || DB::table($table)->count() === 0) {
continue;
}
$classes = [
'invoices' => [
'sort_key' => 'invoice',
'table_suffix' => 's',
'search' => [
'App\Models\Sale\Invoice',
'App\Models\Income\Invoice',
],
'replacement' => 'App\Models\Document\Document',
],
'invoice_items' => [
'sort_key' => 'invoice',
'table_suffix' => '_items',
'search' => [
'App\Models\Sale\InvoiceItem',
'App\Models\Income\InvoiceItem',
],
'replacement' => 'App\Models\Document\DocumentItem',
],
'invoice_item_taxes' => [
'sort_key' => 'invoice',
'table_suffix' => '_item_taxes',
'search' => [
'App\Models\Sale\InvoiceItemTax',
'App\Models\Income\InvoiceItemTax',
],
'replacement' => 'App\Models\Document\DocumentItemTax',
],
'invoice_totals' => [
'sort_key' => 'invoice',
'table_suffix' => '_totals',
'search' => [
'App\Models\Sale\InvoiceTotal',
'App\Models\Income\InvoiceTotal',
],
'replacement' => 'App\Models\Document\DocumentTotal',
],
'invoice_histories' => [
'sort_key' => 'invoice',
'table_suffix' => '_histories',
'search' => [
'App\Models\Sale\InvoiceHistory',
'App\Models\Income\InvoiceHistory',
],
'replacement' => 'App\Models\Document\DocumentHistory',
],
'bills' => [
'sort_key' => 'bill',
'table_suffix' => 's',
'search' => [
'App\Models\Purchase\Bill',
'App\Models\Expense\Bill',
],
'replacement' => 'App\Models\Document\Document',
],
'bill_items' => [
'sort_key' => 'bill',
'table_suffix' => '_items',
'search' => [
'App\Models\Purchase\BillItem',
'App\Models\Expense\BillItem',
],
'replacement' => 'App\Models\Document\DocumentItem',
],
'bill_item_taxes' => [
'sort_key' => 'bill',
'table_suffix' => '_item_taxes',
'search' => [
'App\Models\Purchase\BillItemTax',
'App\Models\Expense\BillItemTax',
],
'replacement' => 'App\Models\Document\DocumentItemTax',
],
'bill_totals' => [
'sort_key' => 'bill',
'table_suffix' => '_totals',
'search' => [
'App\Models\Purchase\BillTotal',
'App\Models\Expense\BillTotal',
],
'replacement' => 'App\Models\Document\DocumentTotal',
],
'bill_histories' => [
'sort_key' => 'bill',
'table_suffix' => '_histories',
'search' => [
'App\Models\Purchase\BillHistory',
'App\Models\Expense\BillHistory',
],
'replacement' => 'App\Models\Document\DocumentHistory',
],
'estimates' => [
'sort_key' => 'estimate',
'table_suffix' => 's',
'search' => [
'Modules\Estimates\Models\Estimate',
],
],
'estimate_items' => [
'sort_key' => 'estimate',
'table_suffix' => '_items',
'search' => [
'Modules\Estimates\Models\EstimateItem',
],
],
'estimate_item_taxes' => [
'sort_key' => 'estimate',
'table_suffix' => '_item_taxes',
'search' => [
'Modules\Estimates\Models\EstimateItemTax',
],
],
'estimate_totals' => [
'sort_key' => 'estimate',
'table_suffix' => '_totals',
'search' => [
'Modules\Estimates\Models\EstimateTotal',
],
],
'estimate_histories' => [
'sort_key' => 'estimate',
'table_suffix' => '_histories',
'search' => [
'Modules\Estimates\Models\EstimateHistory',
],
],
'credit_notes' => [
'sort_key' => 'credit_note',
'table_suffix' => 's',
'search' => [
'Modules\CreditDebitNotes\Models\CreditNote',
],
],
'credit_note_items' => [
'sort_key' => 'credit_note',
'table_suffix' => '_items',
'search' => [
'Modules\CreditDebitNotes\Models\CreditNoteItem',
],
],
'credit_note_item_taxes' => [
'sort_key' => 'credit_note',
'table_suffix' => '_item_taxes',
'search' => [
'Modules\CreditDebitNotes\Models\CreditNoteItemTax',
],
],
'credit_note_totals' => [
'sort_key' => 'credit_note',
'table_suffix' => '_totals',
'search' => [
'Modules\CreditDebitNotes\Models\CreditNoteTotal',
],
],
'credit_note_histories' => [
'sort_key' => 'credit_note',
'table_suffix' => '_histories',
'search' => [
'Modules\CreditDebitNotes\Models\CreditNoteHistory',
],
],
'debit_notes' => [
'sort_key' => 'debit_note',
'table_suffix' => 's',
'search' => [
'Modules\CreditDebitNotes\Models\DebitNote',
],
],
'debit_note_items' => [
'sort_key' => 'debit_note',
'table_suffix' => '_items',
'search' => [
'Modules\CreditDebitNotes\Models\DebitNoteItem',
],
],
'debit_note_item_taxes' => [
'sort_key' => 'debit_note',
'table_suffix' => '_item_taxes',
'search' => [
'Modules\CreditDebitNotes\Models\DebitNoteItemTax',
],
],
'debit_note_totals' => [
'sort_key' => 'debit_note',
'table_suffix' => '_totals',
'search' => [
'Modules\CreditDebitNotes\Models\DebitNoteTotal',
],
],
'debit_note_histories' => [
'sort_key' => 'debit_note',
'table_suffix' => '_histories',
'search' => [
'Modules\CreditDebitNotes\Models\DebitNoteHistory',
],
],
];
foreach ($classes as $class) {
$incrementAmount = $this->getIncrementAmount($class['sort_key'], $class['table_suffix']);
$builder = DB::table($table)->where("{$column}_type", $class['search'][0]);
if (isset($class['search'][1])) {
$builder->orWhere("{$column}_type", $class['search'][1]);
}
if ($incrementAmount !== 0) {
$builder->increment("{$column}_id", $incrementAmount);
}
if (isset($class['replacement'])) {
$builder->update(["{$column}_type" => $class['replacement']]);
}
}
}
}
private function getIncrementAmount(string $key, string $suffix): int
{
$incrementAmount = 0;
foreach ($this->totals->sortDesc()->keys()->takeUntil($key) as $table) {
$incrementAmount += optional(
DB::table($table . $suffix)->orderByDesc('id')->first('id'),
function ($document) {
return $document->id;
}
);
}
return $incrementAmount;
}
private function updateCreditNoteTransactionsTable(): void
{ {
if (!Schema::hasTable('credits_transactions')) { if (!Schema::hasTable('credits_transactions')) {
return; return;
} }
$invoices = DB::table('credits_transactions') $invoices = DB::table('credits_transactions')
->join('invoices_v20', 'credits_transactions.document_id', '=', 'invoices_v20.id') ->join('invoices', 'credits_transactions.document_id', '=', 'invoices.id')
->where('credits_transactions.type', 'expense') ->where('credits_transactions.type', 'expense')
->get( ->get(
[ [
'credits_transactions.id as credits_transactions_id', 'credits_transactions.id as credits_transactions_id',
'invoices_v20.company_id', 'invoices.company_id',
'invoice_number', 'invoice_number',
'invoices_v20.deleted_at', 'invoices.deleted_at',
] ]
); );
@ -147,14 +469,14 @@ class Version210 extends Listener
} }
$credit_notes = DB::table('credits_transactions') $credit_notes = DB::table('credits_transactions')
->join('credit_notes_v20', 'credits_transactions.document_id', '=', 'credit_notes_v20.id') ->join('credit_notes', 'credits_transactions.document_id', '=', 'credit_notes.id')
->where('credits_transactions.type', 'income') ->where('credits_transactions.type', 'income')
->get( ->get(
[ [
'credits_transactions.id as credits_transactions_id', 'credits_transactions.id as credits_transactions_id',
'credit_notes_v20.company_id', 'credit_notes.company_id',
'credit_note_number', 'credit_note_number',
'credit_notes_v20.deleted_at', 'credit_notes.deleted_at',
] ]
); );
@ -171,6 +493,44 @@ class Version210 extends Listener
->where('id', $credit_note->credits_transactions_id) ->where('id', $credit_note->credits_transactions_id)
->update(['document_id' => $document->id]); ->update(['document_id' => $document->id]);
} }
}
}
private function renameTables(): void
{
$tables = [
'bill_histories',
'bill_item_taxes',
'bill_items',
'bill_totals',
'bills',
'credit_note_histories',
'credit_note_item_taxes',
'credit_note_items',
'credit_note_totals',
'credit_notes',
'debit_note_histories',
'debit_note_item_taxes',
'debit_note_items',
'debit_note_totals',
'debit_notes',
'estimate_histories',
'estimate_item_taxes',
'estimate_items',
'estimate_totals',
'estimates',
'invoice_histories',
'invoice_item_taxes',
'invoice_items',
'invoice_totals',
'invoices',
];
foreach ($tables as $table) {
if (Schema::hasTable($table)) {
Schema::rename($table, "{$table}_v20");
}
} }
} }
@ -215,11 +575,11 @@ class Version210 extends Listener
{ {
$counts = []; $counts = [];
foreach ($tables as $table) { foreach ($tables as $table) {
if (!Schema::hasTable($table)) { if (!Schema::hasTable(Str::plural($table))) {
continue; continue;
} }
$counts[$table] = DB::table($table)->count(); $counts[$table] = DB::table(Str::plural($table))->count();
} }
return $counts; return $counts;
@ -232,20 +592,13 @@ class Version210 extends Listener
$new_table = Str::replaceFirst(Str::replaceFirst('-', '_', $type), 'document', $table); $new_table = Str::replaceFirst(Str::replaceFirst('-', '_', $type), 'document', $table);
// To be able to update relation ids // To be able to update relation ids
if (in_array($new_table, ['document_items', 'documents']) && DB::table($new_table)->count() > 0) { if (DB::table($new_table)->count() > 0) {
// Delete document's items which are not found in documents table by document_id // Delete document's items which are not found in documents table by document_id
$builder = DB::table('document_items') $this->deleteOrphanedRecords();
->join('documents', 'documents.id', '=', 'document_items.document_id', 'left')
->whereNull('documents.id');
if ($builder->count()) {
$builder->delete();
}
// To be able to update TYPE_id relations
$document = DB::table($new_table)->orderBy('id')->first('type'); $document = DB::table($new_table)->orderBy('id')->first('type');
if ($document) { if ($document) {
$this->addDocumentIdForeignKeys($document->type); $this->addForeignKeysToRelationTables($document->type);
} }
// Update relation ids // Update relation ids
@ -258,11 +611,6 @@ class Version210 extends Listener
$insertColumns = collect(Schema::getColumnListing($new_table)); $insertColumns = collect(Schema::getColumnListing($new_table));
$insertColumns = $insertColumns->reject(function ($value) use ($new_table, $table) { $insertColumns = $insertColumns->reject(function ($value) use ($new_table, $table) {
// Remove only primary keys
if ($value === 'id' && !in_array($new_table, ['document_items', 'documents'])) {
return true;
}
if ($value === 'description' && $new_table === 'document_items') { if ($value === 'description' && $new_table === 'document_items') {
return true; return true;
} }
@ -337,8 +685,37 @@ class Version210 extends Listener
$offset += $limit; $offset += $limit;
$builder->limit($limit)->offset($offset); $builder->limit($limit)->offset($offset);
} }
}
Schema::rename($table, "{$table}_v20"); private function deleteOrphanedRecords(): void
{
$builder = DB::table('document_items')
->leftJoin(
'documents',
function ($join) {
$join->on('documents.id', '=', 'document_items.document_id')
->on('documents.type', '=', 'document_items.type');
}
)
->whereNull('documents.id');
if ($builder->count()) {
$builder->delete();
}
$builder = DB::table('document_item_taxes')
->leftJoin(
'document_items',
function ($join) {
$join->on('document_items.id', '=', 'document_item_taxes.document_item_id')
->on('document_items.type', '=', 'document_item_taxes.type');
}
)
->whereNull('document_items.id');
if ($builder->count()) {
$builder->delete();
}
} }
private function copyInvoices(): void private function copyInvoices(): void
@ -404,10 +781,31 @@ class Version210 extends Listener
$this->batchCopyRelations('debit_note_totals', self::DEBIT_NOTE_TYPE); $this->batchCopyRelations('debit_note_totals', self::DEBIT_NOTE_TYPE);
} }
private function addForeignKeys(): void // To keep original ids
private function removeAutoIncrements()
{ {
Schema::disableForeignKeyConstraints(); Schema::disableForeignKeyConstraints();
Schema::table(
'document_histories',
function (Blueprint $table) {
$table->unsignedInteger('id')->change();
}
);
Schema::table(
'document_totals',
function (Blueprint $table) {
$table->unsignedInteger('id')->change();
}
);
Schema::table(
'document_item_taxes',
function (Blueprint $table) {
$table->unsignedInteger('id')->change();
}
);
Schema::table( Schema::table(
'document_items', 'document_items',
function (Blueprint $table) { function (Blueprint $table) {
@ -422,6 +820,13 @@ class Version210 extends Listener
} }
); );
Schema::enableForeignKeyConstraints();
}
private function addForeignKeys(): void
{
Schema::disableForeignKeyConstraints();
Schema::table( Schema::table(
'document_histories', 'document_histories',
function (Blueprint $table) { function (Blueprint $table) {
@ -471,6 +876,48 @@ class Version210 extends Listener
Schema::enableForeignKeyConstraints(); Schema::enableForeignKeyConstraints();
} }
private function addAutoIncrements()
{
Schema::disableForeignKeyConstraints();
Schema::table(
'documents',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::table(
'document_items',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::table(
'document_item_taxes',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::table(
'document_totals',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::table(
'document_histories',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::enableForeignKeyConstraints();
}
private function removeForeignKeys(): void private function removeForeignKeys(): void
{ {
Schema::disableForeignKeyConstraints(); Schema::disableForeignKeyConstraints();
@ -504,28 +951,14 @@ class Version210 extends Listener
} }
); );
Schema::table(
'documents',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::table(
'document_items',
function (Blueprint $table) {
$table->increments('id')->change();
}
);
Schema::enableForeignKeyConstraints(); Schema::enableForeignKeyConstraints();
} }
private function addDocumentIdForeignKeys(string $type): void private function addForeignKeysToRelationTables(string $type): void
{ {
Schema::disableForeignKeyConstraints(); Schema::disableForeignKeyConstraints();
foreach ($this->tables[$type] as $table) { foreach ($this->tableRelations[$type] as $table) {
if (!Schema::hasTable($table)) { if (!Schema::hasTable($table)) {
continue; continue;
} }
@ -567,7 +1000,7 @@ class Version210 extends Listener
{ {
Schema::disableForeignKeyConstraints(); Schema::disableForeignKeyConstraints();
foreach ($this->tables[$type] as $table) { foreach ($this->tableRelations[$type] as $table) {
if (!Schema::hasTable($table)) { if (!Schema::hasTable($table)) {
continue; continue;
} }