diff --git a/app/Platform/Operations/Database/SchemaConsolidationGuard.php b/app/Platform/Operations/Database/SchemaConsolidationGuard.php new file mode 100644 index 00000000..027bc78f --- /dev/null +++ b/app/Platform/Operations/Database/SchemaConsolidationGuard.php @@ -0,0 +1,382 @@ + + */ + public const REPLACED_MIGRATIONS = [ + '2014_10_11_071840_create_companies_table', + '2014_10_11_125754_create_currencies_table', + '2014_10_12_000000_create_users_table', + '2014_10_12_100000_create_password_resets_table', + '2016_05_13_060834_create_settings_table', + '2017_04_11_064308_create_units_table', + '2017_04_11_081227_create_items_table', + '2017_04_12_090759_create_invoices_table', + '2017_04_12_091015_create_invoice_items_table', + '2017_05_05_055609_create_estimates_table', + '2017_05_05_073927_create_notifications_table', + '2017_05_06_173745_create_countries_table', + '2017_10_02_123501_create_estimate_items_table', + '2018_11_02_133825_create_ expense_categories_table', + '2018_11_02_133956_create_expenses_table', + '2019_08_30_072639_create_addresses_table', + '2019_09_02_053155_create_payment_methods_table', + '2019_09_03_135234_create_payments_table', + '2019_09_14_120124_create_media_table', + '2019_09_21_052540_create_tax_types_table', + '2019_09_21_052548_create_taxes_table', + '2019_09_26_145012_create_company_settings_table', + '2019_12_14_000001_create_personal_access_tokens_table', + '2020_02_01_063235_create_custom_fields_table', + '2020_02_01_063509_create_custom_field_values_table', + '2020_05_12_154129_add_user_id_to_expenses_table', + '2020_09_07_103054_create_file_disks_table', + '2020_09_22_153617_add_columns_to_media_table', + '2020_09_26_100951_create_user_settings_table', + '2020_10_01_102913_add_company_to_addresses_table', + '2020_10_17_074745_create_notes_table', + '2020_10_24_091934_change_value_column_to_text_on_company_settings_table', + '2020_11_23_050206_add_creator_in_invoices_table', + '2020_11_23_050252_add_creator_in_estimates_table', + '2020_11_23_050316_add_creator_in_payments_table', + '2020_11_23_050333_add_creator_in_expenses_table', + '2020_11_23_050406_add_creator_in_items_table', + '2020_11_23_065815_add_creator_in_users_table', + '2020_11_23_074154_create_email_logs_table', + '2020_12_02_064933_update_crater_version_320', + '2020_12_02_090527_update_crater_version_400', + '2020_12_08_065715_change_description_and_notes_column_type', + '2020_12_08_133131_update_crater_version_401', + '2020_12_14_044717_add_template_name_to_invoices_table', + '2020_12_14_045310_add_template_name_to_estimates_table', + '2020_12_14_051450_remove_template_id_from_invoices_and_estimates_table', + '2020_12_23_061302_update_crater_version_402', + '2020_12_31_100816_update_crater_version_403', + '2021_01_22_085644_update_crater_version_404', + '2021_03_03_155223_add_unit_name_to_pdf', + '2021_03_23_145012_add_number_length_setting', + '2021_05_05_063533_update_crater_version_410', + '2021_06_19_121939_update_crater_version_420', + '2021_06_28_105334_create_bouncer_tables', + '2021_06_28_111647_create_customers_table', + '2021_06_28_120010_add_customer_id_to_estimates_table', + '2021_06_28_120133_add_customer_id_to_expenses_table', + '2021_06_28_120208_add_customer_id_to_invoices_table', + '2021_06_28_120231_add_customer_id_to_payments_table', + '2021_06_29_052745_add_customer_id_to_addresses_table', + '2021_06_30_062411_update_customer_id_in_all_tables', + '2021_07_01_060700_create_user_company_table', + '2021_07_05_100256_change_relationship_of_company', + '2021_07_06_070204_add_owner_id_to_companies_table', + '2021_07_08_110940_add_company_to_notes_table', + '2021_07_09_063502_create_recurring_invoices_table', + '2021_07_09_063712_add_recurring_invoice_id_to_invoices_table', + '2021_07_09_063755_add_recurring_invoice_id_to_invoice_items_table', + '2021_07_15_054753_make_due_date_optional_in_invoices_table', + '2021_07_15_054929_make_expiry_date_optional_estimates_table', + '2021_07_16_072458_add_base_columns_into_invoices_table', + '2021_07_16_072925_add_base_columns_into_invoice_items_table', + '2021_07_16_073040_add_base_columns_into_estimates_table', + '2021_07_16_073441_add_base_columns_into_estimate_items_table', + '2021_07_16_074810_add_base_column_into_payments_table', + '2021_07_16_075100_add_base_values_into_taxes_table', + '2021_07_16_080253_add_currency_id_into_invoices_table', + '2021_07_16_080508_add_currency_id_into_payments_table', + '2021_07_16_080611_add_currency_id_into_items_table', + '2021_07_16_080702_add_currency_id_into_taxes_table', + '2021_07_16_112429_add_currency_id_into_estimates_table', + '2021_08_05_103535_create_exchange_rate_logs_table', + '2021_08_16_091413_add_tax_per_item_into_items_table', + '2021_08_19_063244_add_base_columns_to_expense_table', + '2021_09_28_081543_create_exchange_rate_providers_table', + '2021_09_28_130822_add_sequence_column', + '2021_10_06_100539_add_recurring_invoice_id_to_taxes_table', + '2021_11_13_051127_add_payment_method_to_expense_table', + '2021_11_13_114808_calculate_base_values_for_existing_data', + '2021_11_23_092111_add_new_company_settings', + '2021_11_23_093811_update_crater_version_500', + '2021_12_01_120956_update_crater_version_501', + '2021_12_02_063005_calculate_base_due_amount', + '2021_12_02_074516_migrate_templates_from_version_4', + '2021_12_02_123007_update_crater_version_502', + '2021_12_03_154423_update_crater_version_503', + '2021_12_04_122255_create_transactions_table', + '2021_12_04_123315_add_transaction_id_to_payments_table', + '2021_12_04_123415_add_type_to_payment_methods_table', + '2021_12_06_131201_update_crater_version_504', + '2021_12_09_054033_calculate_base_values_for_expenses', + '2021_12_09_062434_update_crater_version_505', + '2021_12_09_065718_drop_unique_email_on_customers_table', + '2021_12_10_121739_update_creater_version_506', + '2021_12_13_055813_calculate_base_amount_of_payments_table', + '2021_12_13_093701_add_fields_to_email_logs_table', + '2021_12_15_053223_create_modules_table', + '2021_12_21_102521_change_enable_portal_field_of_customers_table', + '2021_12_31_042453_add_type_to_tax_types_table', + '2022_01_05_101841_add_sales_tax_fields_to_invoices_table', + '2022_01_05_102538_add_sales_tax_fields_to_estimates_table', + '2022_01_05_103607_add_sales_tax_fields_to_recurring_invoices_table', + '2022_01_05_115423_update_crater_version_600', + '2022_01_06_103536_add_slug_to_companies', + '2022_01_12_132859_update_crater_version_601', + '2022_01_13_123829_update_crater_version_602', + '2022_02_15_113648_update_crater_version_603', + '2022_02_17_081723_update_crater_version_604', + '2022_02_23_130108_update_value_column_to_nullable_on_settings_table', + '2022_03_02_120210_add_overdue_to_invoices_table', + '2022_03_03_060121_crater_version_605', + '2022_03_03_063237_change_over_due_status_to_sent', + '2022_03_04_051438_calculate_base_values_for_invoice_items', + '2022_03_06_070829_update_crater_version_606', + '2024_01_28_114715_add_generated_conversions_to_media_table', + '2024_02_04_005632_update_version_100', + '2024_02_08_181804_taxes_amount_as_signed', + '2024_02_11_075831_update_version_110', + '2024_02_17_211900_add_expires_at_to_personal_access_tokens', + '2024_04_12_162703_add_tax_ids_to_companies', + '2024_04_14_173940_replace_crater_type', + '2024_05_04_110000_update_version_121', + '2024_05_04_110000_update_version_122', + '2024_07_12_235756_update_version_130', + '2024_07_13_000000_rename_password_resets_table', + '2024_07_13_000001_update_stored_namespace', + '2024_07_17_113642_create_cache_table', + '2024_07_17_113702_create_jobs_table', + '2024_08_08_173226_update_invoice_date_to_datetime_on_invoices_table', + '2024_10_04_093723_add_tax_id_field_to_customers_table', + '2024_10_09_103306_modify_invoices_to_allow_negative_values', + '2024_12_11_102055_update_taxes_to_handle_fixed_amount', + '2025_01_05_211404_add_is_default_to_notes_table', + '2025_03_15_160016_allow_unsigned_item_price', + '2025_05_04_152240_add_tax_included_to_invoices', + '2025_05_04_152522_add_tax_included_to_estimates', + '2025_05_04_152833_add_tax_included_to_recurring_invoices', + '2025_08_18_101343_add_new_currencies_to_currencies_table', + '2025_09_017_add_qar_currency_to_currencies_table', + '2025_09_02_add_expense_number_to_expenses_table', + ]; + + private function __construct( + private readonly string $mode, + private readonly int $recorded, + ) {} + + /** + * Read the database and decide, without writing anything. + * + * @param string|null $connection the connection to inspect, or null for + * the default one (which is what the + * framework points at a migration while + * its body runs) + */ + public static function inspect(?string $connection = null): self + { + return self::decide( + self::recordedMigrations($connection), + Schema::connection($connection)->hasTable(self::SENTINEL_TABLE), + ); + } + + /** + * The verdict an upgrade would meet, or null when there is nothing to meet. + * + * This is the entry point for anything that wants to know *before* running + * migrations whether the consolidation is going to refuse the database. A + * database that has already recorded the consolidation — every 3.x + * installation from its second upgrade onwards — gets null: the migration + * is not pending, so it cannot abort, and the decision table (which would + * read such a database as an inconsistency) does not apply to it. + */ + public static function preflight(?string $connection = null): ?self + { + $recorded = self::recordedMigrations($connection); + + if (in_array(self::CONSOLIDATION_MIGRATION, $recorded, true)) { + return null; + } + + return self::decide( + $recorded, + Schema::connection($connection)->hasTable(self::SENTINEL_TABLE), + ); + } + + /** + * The decision table itself, and the only place it is written down. + * + * @param list $recordedMigrations every name the framework has + * recorded as ran + * @param bool $sentinel whether the sentinel table exists + */ + private static function decide(array $recordedMigrations, bool $sentinel): self + { + $recorded = count(array_intersect(self::REPLACED_MIGRATIONS, $recordedMigrations)); + + if ($recorded === 0) { + return new self($sentinel ? self::ABORT_INCONSISTENT : self::BUILD, $recorded); + } + + if ($recorded < count(self::REPLACED_MIGRATIONS)) { + return new self(self::ABORT_FLOOR, $recorded); + } + + return new self($sentinel ? self::SKIP : self::ABORT_INCONSISTENT, $recorded); + } + + public function mode(): string + { + return $this->mode; + } + + /** + * How many of the replaced migrations this database has run. + */ + public function recordedCount(): int + { + return $this->recorded; + } + + public function isBuild(): bool + { + return $this->mode === self::BUILD; + } + + public function isSkip(): bool + { + return $this->mode === self::SKIP; + } + + public function isAbort(): bool + { + return $this->mode === self::ABORT_FLOOR || $this->mode === self::ABORT_INCONSISTENT; + } + + /** + * What to tell the operator, or null when there is nothing wrong. + */ + public function message(): ?string + { + return match ($this->mode) { + self::ABORT_FLOOR => 'Upgrading to this version requires the database to be fully migrated on the 2.x line first. ' + .'This database has run '.$this->recorded.' of the '.count(self::REPLACED_MIGRATIONS) + .' historical migrations this version consolidates. Install the latest InvoiceShelf 2.x release, ' + .'run its migrations to completion, then upgrade to this version again. No changes have been made.', + self::ABORT_INCONSISTENT => 'The database schema and the migration history do not match (the recorded history ' + .'and the `companies` table disagree). This usually means a partial restore. Restore a consistent backup ' + .'— schema and migration history together — and run the upgrade again. No changes have been made.', + default => null, + }; + } + + /** + * Stop the caller with this verdict's message. + * + * @throws RuntimeException always + */ + public function fail(): never + { + throw new RuntimeException((string) $this->message()); + } + + /** + * Every migration name the framework has recorded as ran. + * + * A database that has never been migrated has no repository table yet; that + * is an empty history, not an error. + * + * @return list + */ + private static function recordedMigrations(?string $connection): array + { + $table = self::repositoryTable(); + + if (! Schema::connection($connection)->hasTable($table)) { + return []; + } + + return DB::connection($connection)->table($table)->pluck('migration')->all(); + } + + /** + * The configured name of the framework's migration repository table. + */ + private static function repositoryTable(): string + { + $configured = config('database.migrations'); + + if (is_array($configured)) { + return $configured['table'] ?? 'migrations'; + } + + return is_string($configured) && $configured !== '' ? $configured : 'migrations'; + } +} diff --git a/app/Platform/Operations/Http/Admin/UpdateController.php b/app/Platform/Operations/Http/Admin/UpdateController.php index 24d2d92b..a5c6decb 100644 --- a/app/Platform/Operations/Http/Admin/UpdateController.php +++ b/app/Platform/Operations/Http/Admin/UpdateController.php @@ -95,11 +95,25 @@ class UpdateController extends Controller return response()->json(Updater::cleanStaleFiles()); } + /** + * Run the pending migrations, unless the schema guard refuses first. + * + * A refusal is the operator's problem to fix, not a crash: it comes back + * with the guard's explanation in the same shape the unzip step uses for + * its own failures. + */ public function migrate(Request $request): JsonResponse { $this->authorizeUpdates(); - Updater::migrateUpdate(); + try { + Updater::migrateUpdate(); + } catch (Exception $failure) { + return response()->json([ + 'success' => false, + 'error' => $failure->getMessage(), + ], 500); + } return response()->json(['success' => true]); } diff --git a/app/Platform/Operations/Update/Updater.php b/app/Platform/Operations/Update/Updater.php index e8abfcd7..ab9f593b 100644 --- a/app/Platform/Operations/Update/Updater.php +++ b/app/Platform/Operations/Update/Updater.php @@ -2,6 +2,7 @@ namespace App\Platform\Operations\Update; +use App\Platform\Operations\Database\SchemaConsolidationGuard; use App\Platform\Operations\Events\UpdateFinished; use App\Platform\Operations\Models\Setting; use GuzzleHttp\Exception\RequestException; @@ -203,9 +204,24 @@ class Updater /** * Bring the schema up to date with the freshly copied code. + * + * The base-schema consolidation refuses databases whose 2.x history is + * incomplete, or whose history and schema disagree. Asking it for that + * verdict first turns such a database into a refusal carrying the + * consolidation's own explanation, instead of an upgrade that copies the + * new release over the installation and only then stops on a failed + * migration. + * + * @throws \RuntimeException when the consolidation would refuse this database */ public static function migrateUpdate() { + $verdict = SchemaConsolidationGuard::preflight(); + + if ($verdict?->isAbort()) { + $verdict->fail(); + } + Artisan::call('migrate --force'); return true; diff --git a/database/migrations/2026_01_01_000000_consolidate_base_schema.php b/database/migrations/2026_01_01_000000_consolidate_base_schema.php new file mode 100644 index 00000000..83088b42 --- /dev/null +++ b/database/migrations/2026_01_01_000000_consolidate_base_schema.php @@ -0,0 +1,1101 @@ + table => the column it kept + */ + private const SQLITE_RETAINED_COLUMNS = [ + 'users' => 'company_id', + 'estimates' => 'user_id', + 'expenses' => 'user_id', + 'invoices' => 'user_id', + 'payments' => 'user_id', + ]; + + /** + * True when this database is one of the SQLite ones described above. + */ + private bool $retainsLegacyColumns = false; + + public function up(): void + { + $verdict = SchemaConsolidationGuard::inspect(); + + if ($verdict->isAbort()) { + $verdict->fail(); + } + + if (! $verdict->isBuild()) { + // SKIP. The framework records this file when the body returns, and + // that record is the only thing about this database that changes. + return; + } + + $this->retainsLegacyColumns = Schema::getConnection()->getDriverName() === 'sqlite'; + + $this->createStandaloneTables(); + $this->createAccountTables(); + $this->createCatalogTables(); + $this->createContactTables(); + $this->createSpendingTables(); + $this->createBillingTables(); + $this->createTaxTables(); + $this->createAuthorizationTables(); + $this->placeRelocatedColumns(); + + $this->seedFileDisks(); + $this->seedCurrencies(); + $this->seedVersion(); + } + + /** + * Consolidations are one-way. + * + * On an upgraded database the forward run did nothing at all, so a reverse + * run has no way to tell what it would be undoing — and on a fresh one it + * would mean dropping every table in the product. + */ + public function down(): void + { + throw new RuntimeException( + 'The base schema consolidation is irreversible: on an upgraded database its forward run ' + .'made no changes, so a rollback cannot know what to undo. Development environments should ' + .'rebuild the database from scratch instead of rolling this migration back.' + ); + } + + /** + * Tables that reference nothing, so nothing constrains their order. + */ + private function createStandaloneTables(): void + { + Schema::create('currencies', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('code'); + $table->string('symbol')->nullable(); + $table->integer('precision'); + $table->string('thousand_separator'); + $table->string('decimal_separator'); + $table->boolean('swap_currency_symbol')->default(false); + $table->timestamps(); + }); + + Schema::create('countries', function (Blueprint $table) { + $table->increments('id'); + $table->string('code'); + $table->string('name'); + $table->integer('phonecode'); + $table->index('id'); + }); + + Schema::create('settings', function (Blueprint $table) { + $table->increments('id'); + $table->string('option'); + $table->string('value')->nullable(); + $table->timestamps(); + }); + + Schema::create('file_disks', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('type')->default('REMOTE'); + $table->string('driver'); + $table->boolean('set_as_default')->default(false); + $table->json('credentials'); + $table->timestamps(); + }); + + Schema::create('modules', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('version'); + $table->boolean('installed')->default(false); + $table->boolean('enabled')->default(false); + $table->timestamps(); + }); + + Schema::create('media', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('model_type'); + $table->unsignedBigInteger('model_id'); + $table->string('collection_name'); + $table->string('name'); + $table->string('file_name'); + $table->string('mime_type')->nullable(); + $table->string('disk'); + $table->unsignedInteger('size'); + $table->text('manipulations'); + $table->text('custom_properties'); + $table->text('responsive_images'); + $table->unsignedInteger('order_column')->nullable(); + $table->timestamps(); + $table->uuid('uuid')->nullable(); + $table->string('conversions_disk')->nullable(); + $table->json('generated_conversions')->nullable(); + $table->index(['model_type', 'model_id']); + }); + + Schema::create('notifications', function (Blueprint $table) { + $table->uuid('id')->primary(); + $table->string('type'); + $table->string('notifiable_type'); + $table->unsignedBigInteger('notifiable_id'); + $table->text('data'); + $table->timestamp('read_at')->nullable(); + $table->timestamps(); + $table->index(['notifiable_type', 'notifiable_id']); + }); + + // Renamed from `password_resets` long after its index was named, and + // the index kept the old name. It has no primary key. + Schema::create('password_reset_tokens', function (Blueprint $table) { + $table->string('email'); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + $table->index('email', 'password_resets_email_index'); + }); + + Schema::create('cache', function (Blueprint $table) { + $table->string('key')->primary(); + $table->mediumText('value'); + $table->integer('expiration'); + }); + + Schema::create('cache_locks', function (Blueprint $table) { + $table->string('key')->primary(); + $table->string('owner'); + $table->integer('expiration'); + }); + + Schema::create('jobs', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + + Schema::create('personal_access_tokens', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('tokenable_type'); + $table->unsignedBigInteger('tokenable_id'); + $table->string('name'); + $table->string('token', 64)->unique(); + $table->text('abilities')->nullable(); + $table->timestamp('last_used_at')->nullable(); + $table->timestamps(); + $table->index(['tokenable_type', 'tokenable_id']); + }); + + Schema::create('email_logs', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('from'); + $table->string('to'); + $table->string('subject'); + $table->text('body'); + $table->string('mailable_type'); + $table->string('mailable_id'); + $table->timestamps(); + $table->string('token')->nullable()->unique(); + }); + } + + /** + * Users and companies, which reference each other in a loop. + * + * Users come first because `companies.owner_id` points at them. The other + * half of the loop only exists on SQLite, where a foreign key may name a + * table that does not exist yet — the constraint is resolved when rows are + * written, not when the table is declared. + */ + private function createAccountTables(): void + { + Schema::create('users', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('email')->nullable()->unique(); + $table->string('phone')->nullable(); + $table->string('password')->nullable(); + $table->string('role')->default('user'); + $table->rememberToken(); + $table->string('facebook_id')->nullable(); + $table->string('google_id')->nullable(); + $table->string('github_id')->nullable(); + $table->string('contact_name')->nullable(); + $table->string('company_name')->nullable(); + $table->string('website')->nullable(); + $table->boolean('enable_portal')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $this->addRetainedColumn($table, 'users'); + $table->timestamps(); + $table->unsignedInteger('creator_id')->nullable(); + + $table->foreign('currency_id')->references('id')->on('currencies')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::create('companies', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('logo')->nullable(); + $table->string('unique_hash')->nullable(); + $table->timestamps(); + $table->string('slug')->nullable(); + $table->unsignedInteger('owner_id')->nullable(); + + $table->foreign('owner_id')->references('id')->on('users'); + }); + + Schema::create('user_company', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedInteger('user_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('user_settings', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('key'); + $table->text('value'); + $table->unsignedInteger('user_id'); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + + Schema::create('company_settings', function (Blueprint $table) { + $table->increments('id'); + $table->string('option'); + $table->text('value'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies'); + }); + + Schema::create('custom_fields', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('slug'); + $table->string('label'); + $table->string('model_type'); + $table->string('type'); + $table->string('placeholder')->nullable(); + $table->json('options')->nullable(); + $table->boolean('boolean_answer')->nullable(); + $table->date('date_answer')->nullable(); + $table->time('time_answer')->nullable(); + $table->text('string_answer')->nullable(); + $table->unsignedBigInteger('number_answer')->nullable(); + $table->dateTime('date_time_answer')->nullable(); + $table->boolean('is_required')->default(false); + $table->unsignedBigInteger('order')->default(1); + $table->unsignedInteger('company_id'); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies'); + }); + + Schema::create('custom_field_values', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('custom_field_valuable_type'); + $table->unsignedInteger('custom_field_valuable_id'); + $table->string('type'); + $table->boolean('boolean_answer')->nullable(); + $table->date('date_answer')->nullable(); + $table->time('time_answer')->nullable(); + $table->text('string_answer')->nullable(); + $table->unsignedBigInteger('number_answer')->nullable(); + $table->dateTime('date_time_answer')->nullable(); + $table->unsignedBigInteger('custom_field_id'); + $table->unsignedInteger('company_id'); + $table->timestamps(); + + $table->foreign('custom_field_id')->references('id')->on('custom_fields'); + $table->foreign('company_id')->references('id')->on('companies'); + }); + + Schema::create('notes', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('type'); + $table->string('name'); + $table->text('notes'); + $table->timestamps(); + $table->unsignedInteger('company_id')->nullable(); + $table->boolean('is_default')->default(false); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('exchange_rate_logs', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('base_currency_id')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('base_currency_id')->references('id')->on('currencies'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + + Schema::create('exchange_rate_providers', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('driver'); + $table->string('key'); + $table->json('currencies')->nullable(); + $table->json('driver_config')->nullable(); + $table->boolean('active')->default(true); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies'); + }); + } + + /** + * What the business sells. + */ + private function createCatalogTables(): void + { + Schema::create('units', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('items', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->text('description')->nullable(); + $table->unsignedBigInteger('price'); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('unit_id')->nullable(); + $table->timestamps(); + $table->unsignedInteger('creator_id')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->boolean('tax_per_item')->default(false); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('unit_id')->references('id')->on('units')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + } + + /** + * Who the business sells to. + */ + private function createContactTables(): void + { + Schema::create('customers', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('email')->nullable(); + $table->string('phone')->nullable(); + $table->string('password')->nullable(); + $table->rememberToken(); + $table->string('facebook_id')->nullable(); + $table->string('google_id')->nullable(); + $table->string('github_id')->nullable(); + $table->string('contact_name')->nullable(); + $table->string('company_name')->nullable(); + $table->string('website')->nullable(); + $table->boolean('enable_portal')->default(false); + $table->unsignedInteger('currency_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('creator_id')->nullable(); + $table->timestamps(); + + $table->foreign('currency_id')->references('id')->on('currencies'); + $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('creator_id')->references('id')->on('users'); + }); + + Schema::create('addresses', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name')->nullable(); + $table->string('address_street_1')->nullable(); + $table->string('address_street_2')->nullable(); + $table->string('city')->nullable(); + $table->string('state')->nullable(); + $table->unsignedInteger('country_id')->nullable(); + $table->string('zip')->nullable(); + $table->string('phone')->nullable(); + $table->string('fax')->nullable(); + $table->string('type')->nullable(); + $table->unsignedInteger('user_id')->nullable(); + $table->timestamps(); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedBigInteger('customer_id')->nullable(); + + $table->foreign('country_id')->references('id')->on('countries'); + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('customer_id')->references('id')->on('customers'); + }); + } + + /** + * Money going out. + */ + private function createSpendingTables(): void + { + Schema::create('expense_categories', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->string('description')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('payment_methods', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + $table->string('driver')->nullable(); + $table->enum('type', ['GENERAL', 'MODULE'])->default('GENERAL'); + $table->json('settings')->nullable(); + $table->boolean('active')->default(false); + $table->boolean('use_test_env')->default(false); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + // `customer_id` and `currency_id` are plain indexed-free columns here: + // the relationship is enforced in application code, not by the database. + Schema::create('expenses', function (Blueprint $table) { + $table->increments('id'); + $table->date('expense_date'); + $table->string('attachment_receipt')->nullable(); + $table->unsignedBigInteger('amount'); + $table->text('notes')->nullable(); + $table->unsignedInteger('expense_category_id'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + $this->addRetainedColumn($table, 'expenses'); + $table->unsignedInteger('creator_id')->nullable(); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->unsignedBigInteger('base_amount')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->unsignedInteger('payment_method_id')->nullable(); + + $table->foreign('expense_category_id')->references('id')->on('expense_categories')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('payment_method_id')->references('id')->on('payment_methods'); + }); + } + + /** + * Money coming in. + */ + private function createBillingTables(): void + { + Schema::create('recurring_invoices', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->dateTime('starts_at'); + $table->boolean('send_automatically')->default(false); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->enum('status', ['COMPLETED', 'ON_HOLD', 'ACTIVE'])->default('ACTIVE'); + $table->dateTime('next_invoice_at')->nullable(); + $table->unsignedInteger('creator_id')->nullable(); + $table->string('frequency'); + $table->enum('limit_by', ['NONE', 'COUNT', 'DATE'])->default('NONE'); + $table->integer('limit_count')->nullable(); + $table->date('limit_date')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->string('tax_per_item'); + $table->string('discount_per_item'); + $table->text('notes')->nullable(); + $table->string('discount_type')->nullable(); + $table->decimal('discount', 15, 2)->nullable(); + $table->unsignedBigInteger('discount_val')->nullable(); + $table->unsignedBigInteger('sub_total'); + $table->unsignedBigInteger('total'); + $table->unsignedBigInteger('tax'); + $table->string('template_name')->nullable(); + $table->unsignedBigInteger('due_amount'); + $table->timestamps(); + $table->string('sales_tax_type')->nullable(); + $table->string('sales_tax_address_type')->nullable(); + $table->boolean('tax_included')->default(false); + + $table->foreign('customer_id')->references('id')->on('customers'); + $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('creator_id')->references('id')->on('users'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + + // The money columns here are signed: credit-style invoices are allowed + // to carry negative totals. + Schema::create('invoices', function (Blueprint $table) { + $table->increments('id'); + $table->dateTime('invoice_date'); + $table->date('due_date')->nullable(); + $table->string('invoice_number'); + $table->string('reference_number')->nullable(); + $table->string('status'); + $table->string('paid_status'); + $table->string('tax_per_item'); + $table->string('discount_per_item'); + $table->text('notes')->nullable(); + $table->string('discount_type')->nullable(); + $table->decimal('discount', 15, 2)->nullable(); + $table->bigInteger('discount_val')->nullable(); + $table->bigInteger('sub_total'); + $table->bigInteger('total'); + $table->bigInteger('tax'); + $table->bigInteger('due_amount'); + $table->boolean('sent')->default(false); + $table->boolean('viewed')->default(false); + $table->string('unique_hash')->nullable(); + $this->addRetainedColumn($table, 'invoices'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + $table->unsignedInteger('creator_id')->nullable(); + $table->string('template_name')->nullable(); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->unsignedBigInteger('recurring_invoice_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->bigInteger('base_discount_val')->nullable(); + $table->bigInteger('base_sub_total')->nullable(); + $table->bigInteger('base_total')->nullable(); + $table->bigInteger('base_tax')->nullable(); + $table->bigInteger('base_due_amount')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('customer_id')->references('id')->on('customers'); + $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + + Schema::create('invoice_items', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->text('description')->nullable(); + $table->string('discount_type'); + $table->bigInteger('price'); + $table->decimal('quantity', 15, 2); + $table->decimal('discount', 15, 2)->nullable(); + $table->bigInteger('discount_val'); + $table->bigInteger('tax'); + $table->bigInteger('total'); + $table->unsignedInteger('invoice_id')->nullable(); + $table->unsignedInteger('item_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('estimates', function (Blueprint $table) { + $table->increments('id'); + $table->date('estimate_date'); + $table->date('expiry_date')->nullable(); + $table->string('estimate_number'); + $table->string('status'); + $table->string('reference_number')->nullable(); + $table->string('tax_per_item'); + $table->string('discount_per_item'); + $table->text('notes')->nullable(); + $table->decimal('discount', 15, 2)->nullable(); + $table->string('discount_type')->nullable(); + $table->unsignedBigInteger('discount_val')->nullable(); + $table->unsignedBigInteger('sub_total'); + $table->unsignedBigInteger('total'); + $table->unsignedBigInteger('tax'); + $table->string('unique_hash')->nullable(); + $this->addRetainedColumn($table, 'estimates'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + $table->unsignedInteger('creator_id')->nullable(); + $table->string('template_name')->nullable(); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->unsignedBigInteger('base_discount_val')->nullable(); + $table->unsignedBigInteger('base_sub_total')->nullable(); + $table->unsignedBigInteger('base_total')->nullable(); + $table->unsignedBigInteger('base_tax')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('customer_id')->references('id')->on('customers'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + + Schema::create('estimate_items', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->text('description')->nullable(); + $table->string('discount_type'); + $table->decimal('quantity', 15, 2); + $table->decimal('discount', 15, 2)->nullable(); + $table->unsignedBigInteger('discount_val')->nullable(); + $table->unsignedBigInteger('price'); + $table->unsignedBigInteger('tax'); + $table->unsignedBigInteger('total'); + $table->unsignedInteger('item_id')->nullable(); + $table->unsignedInteger('estimate_id'); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + + $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); + $table->foreign('estimate_id')->references('id')->on('estimates')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + }); + + Schema::create('transactions', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('transaction_id')->nullable(); + $table->string('unique_hash')->nullable(); + $table->string('type')->nullable(); + $table->string('status'); + $table->dateTime('transaction_date'); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('invoice_id'); + $table->timestamps(); + + $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('invoice_id')->references('id')->on('invoices'); + }); + + Schema::create('payments', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('payment_number'); + $table->date('payment_date'); + $table->text('notes')->nullable(); + $table->unsignedBigInteger('amount'); + $table->string('unique_hash')->nullable(); + $this->addRetainedColumn($table, 'payments'); + $table->unsignedInteger('invoice_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->unsignedInteger('payment_method_id')->nullable(); + $table->timestamps(); + $table->unsignedInteger('creator_id')->nullable(); + $table->unsignedBigInteger('customer_id')->nullable(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->unsignedBigInteger('base_amount')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + + $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade'); + $table->foreign('payment_method_id')->references('id')->on('payment_methods')->onDelete('cascade'); + $table->foreign('creator_id')->references('id')->on('users')->onDelete('cascade'); + $table->foreign('customer_id')->references('id')->on('customers'); + $table->foreign('currency_id')->references('id')->on('currencies'); + }); + } + + /** + * Tax definitions and the tax lines they produce. + */ + private function createTaxTables(): void + { + Schema::create('tax_types', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + $table->decimal('percent', 5, 2)->nullable(); + $table->tinyInteger('compound_tax')->default(0); + $table->tinyInteger('collective_tax')->default(0); + $table->text('description')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->timestamps(); + $table->enum('type', ['GENERAL', 'MODULE'])->default('GENERAL'); + + $table->foreign('company_id')->references('id')->on('companies'); + }); + + // Every document type that can carry tax points here, which is why the + // table has nine foreign keys and eight nullable owners. + Schema::create('taxes', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('tax_type_id'); + $table->unsignedInteger('invoice_id')->nullable(); + $table->unsignedInteger('estimate_id')->nullable(); + $table->unsignedInteger('invoice_item_id')->nullable(); + $table->unsignedInteger('estimate_item_id')->nullable(); + $table->unsignedInteger('item_id')->nullable(); + $table->unsignedInteger('company_id')->nullable(); + $table->string('name'); + $table->bigInteger('amount'); + $table->decimal('percent', 5, 2)->nullable(); + $table->tinyInteger('compound_tax')->default(0); + $table->timestamps(); + $table->decimal('exchange_rate', 19, 6)->nullable(); + $table->unsignedBigInteger('base_amount')->nullable(); + $table->unsignedInteger('currency_id')->nullable(); + $table->unsignedBigInteger('recurring_invoice_id')->nullable(); + + $table->foreign('tax_type_id')->references('id')->on('tax_types'); + $table->foreign('invoice_id')->references('id')->on('invoices')->onDelete('cascade'); + $table->foreign('estimate_id')->references('id')->on('estimates')->onDelete('cascade'); + $table->foreign('invoice_item_id')->references('id')->on('invoice_items')->onDelete('cascade'); + $table->foreign('estimate_item_id')->references('id')->on('estimate_items')->onDelete('cascade'); + $table->foreign('item_id')->references('id')->on('items')->onDelete('cascade'); + $table->foreign('company_id')->references('id')->on('companies'); + $table->foreign('currency_id')->references('id')->on('currencies'); + $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); + }); + } + + /** + * Bouncer's four tables. + * + * The explicit index on each foreign-key column is deliberate: MySQL + * reuses it for the constraint instead of creating a second, differently + * named one, which is what the boundary schema records. + */ + private function createAuthorizationTables(): void + { + Schema::create('abilities', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('title')->nullable(); + $table->unsignedBigInteger('entity_id')->nullable(); + $table->string('entity_type')->nullable(); + $table->boolean('only_owned')->default(false); + $table->json('options')->nullable(); + $table->integer('scope')->nullable()->index(); + $table->timestamps(); + }); + + Schema::create('roles', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->string('name'); + $table->string('title')->nullable(); + $table->unsignedInteger('level')->nullable(); + $table->integer('scope')->nullable()->index(); + $table->timestamps(); + + $table->unique(['name', 'scope'], 'roles_name_unique'); + }); + + Schema::create('assigned_roles', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedBigInteger('role_id'); + $table->unsignedBigInteger('entity_id'); + $table->string('entity_type'); + $table->unsignedBigInteger('restricted_to_id')->nullable(); + $table->string('restricted_to_type')->nullable(); + $table->integer('scope')->nullable()->index(); + + $table->index(['entity_id', 'entity_type', 'scope'], 'assigned_roles_entity_index'); + $table->index('role_id', 'assigned_roles_role_id_index'); + $table->foreign('role_id')->references('id')->on('roles') + ->onUpdate('cascade')->onDelete('cascade'); + }); + + Schema::create('permissions', function (Blueprint $table) { + $table->bigIncrements('id'); + $table->unsignedBigInteger('ability_id'); + $table->unsignedBigInteger('entity_id')->nullable(); + $table->string('entity_type')->nullable(); + $table->boolean('forbidden')->default(false); + $table->integer('scope')->nullable()->index(); + + $table->index(['entity_id', 'entity_type', 'scope'], 'permissions_entity_index'); + $table->index('ability_id', 'permissions_ability_id_index'); + $table->foreign('ability_id')->references('id')->on('abilities') + ->onUpdate('cascade')->onDelete('cascade'); + }); + } + + /** + * Columns that MySQL keeps somewhere other than the end of the table. + * + * The replaced chain added these after the fact, and MySQL honours the + * `AFTER` clause while PostgreSQL and SQLite always append. Adding them in + * a second pass reproduces both layouts from one body of code: `after()` + * places them on MySQL and is ignored everywhere else, where they land in + * this method's own order. Column order carries no meaning to the + * application — it is reproduced so that a database built here and one + * upgraded from 2.x are physically the same. + */ + private function placeRelocatedColumns(): void + { + Schema::table('companies', function (Blueprint $table) { + $table->string('vat_id')->nullable()->after('unique_hash'); + $table->string('tax_id')->nullable()->after('vat_id'); + }); + + Schema::table('customers', function (Blueprint $table) { + $table->string('prefix')->nullable()->after('id'); + $table->string('tax_id')->nullable()->after('github_id'); + }); + + Schema::table('expenses', function (Blueprint $table) { + $table->string('expense_number')->nullable()->after('expense_date'); + $table->index('expense_number'); + }); + + Schema::table('personal_access_tokens', function (Blueprint $table) { + $table->dateTime('expires_at')->nullable()->after('last_used_at'); + }); + + Schema::table('tax_types', function (Blueprint $table) { + $table->enum('calculation_type', ['percentage', 'fixed'])->default('percentage')->after('name'); + $table->integer('fixed_amount')->nullable()->after('percent'); + }); + + Schema::table('taxes', function (Blueprint $table) { + $table->enum('calculation_type', ['percentage', 'fixed'])->default('percentage')->after('name'); + $table->integer('fixed_amount')->nullable()->after('percent'); + }); + + Schema::table('estimates', function (Blueprint $table) { + $table->unsignedMediumInteger('sequence_number')->nullable()->after('id'); + $table->unsignedMediumInteger('customer_sequence_number')->nullable()->after('sequence_number'); + $table->string('sales_tax_type')->nullable()->after('currency_id'); + $table->string('sales_tax_address_type')->nullable()->after('sales_tax_type'); + $table->boolean('tax_included')->default(false)->after('sales_tax_address_type'); + }); + + Schema::table('estimate_items', function (Blueprint $table) { + $table->string('unit_name')->nullable()->after('quantity'); + $table->decimal('exchange_rate', 19, 6)->nullable()->after('updated_at'); + $table->unsignedBigInteger('base_discount_val')->nullable()->after('exchange_rate'); + $table->unsignedBigInteger('base_price')->nullable()->after('base_discount_val'); + $table->unsignedBigInteger('base_tax')->nullable()->after('base_price'); + $table->unsignedBigInteger('base_total')->nullable()->after('base_tax'); + }); + + Schema::table('invoices', function (Blueprint $table) { + $table->unsignedMediumInteger('sequence_number')->nullable()->after('id'); + $table->unsignedMediumInteger('customer_sequence_number')->nullable()->after('sequence_number'); + $table->string('sales_tax_type')->nullable()->after('currency_id'); + $table->string('sales_tax_address_type')->nullable()->after('sales_tax_type'); + $table->boolean('overdue')->default(false)->after('sales_tax_address_type'); + $table->boolean('tax_included')->default(false)->after('overdue'); + }); + + Schema::table('invoice_items', function (Blueprint $table) { + $table->string('unit_name')->nullable()->after('quantity'); + $table->unsignedBigInteger('recurring_invoice_id')->nullable()->after('updated_at'); + $table->unsignedBigInteger('base_price')->nullable()->after('recurring_invoice_id'); + $table->decimal('exchange_rate', 19, 6)->nullable()->after('base_price'); + $table->bigInteger('base_discount_val')->nullable()->after('exchange_rate'); + $table->bigInteger('base_tax')->nullable()->after('base_discount_val'); + $table->bigInteger('base_total')->nullable()->after('base_tax'); + + $table->foreign('recurring_invoice_id')->references('id')->on('recurring_invoices'); + }); + + Schema::table('payments', function (Blueprint $table) { + $table->unsignedMediumInteger('sequence_number')->nullable()->after('id'); + $table->unsignedMediumInteger('customer_sequence_number')->nullable()->after('sequence_number'); + $table->unsignedBigInteger('transaction_id')->nullable()->after('currency_id'); + + $table->foreign('transaction_id')->references('id')->on('transactions'); + }); + } + + /** + * The two disks every installation starts with. + * + * Both are local, and both take their paths from the running application + * rather than from anything baked into this file, so an installation moved + * to a different directory still describes itself correctly. + */ + private function seedFileDisks(): void + { + $now = now(); + + DB::table('file_disks')->insert([ + [ + 'name' => 'public', + 'type' => 'SYSTEM', + 'driver' => 'local', + 'set_as_default' => false, + 'credentials' => json_encode([ + 'driver' => 'local', + 'root' => config('filesystems.disks.public.root'), + 'url' => config('app.url').'/storage', + 'visibility' => 'public', + ]), + 'created_at' => $now, + 'updated_at' => $now, + ], + [ + 'name' => 'local_private', + 'type' => 'SYSTEM', + 'driver' => 'local', + 'set_as_default' => true, + 'credentials' => json_encode([ + 'root' => config('filesystems.disks.local.root'), + 'driver' => 'local', + ]), + 'created_at' => $now, + 'updated_at' => $now, + ], + ]); + } + + /** + * Three currencies the replaced chain added after its first release. + * + * Each is written only when its code is absent, so a database that already + * knows one of them — because a seeder or a module got there first — keeps + * exactly one row for it. + */ + private function seedCurrencies(): void + { + $now = now(); + + $currencies = [ + [ + 'code' => 'DZD', + 'name' => 'Algerian Dinar', + 'symbol' => 'DA', + 'precision' => 2, + 'thousand_separator' => ',', + 'decimal_separator' => '.', + ], + [ + 'code' => 'PYG', + 'name' => 'Paraguayan Guaraní', + 'symbol' => '₲', + 'precision' => 0, + 'thousand_separator' => '.', + 'decimal_separator' => ',', + ], + [ + 'code' => 'QAR', + 'name' => 'Qatari Riyal', + 'symbol' => 'QR', + 'precision' => 2, + 'thousand_separator' => ',', + 'decimal_separator' => '.', + ], + ]; + + foreach ($currencies as $currency) { + $alreadyKnown = DB::table('currencies') + ->where('code', $currency['code']) + ->exists(); + + if ($alreadyKnown) { + continue; + } + + DB::table('currencies')->insert($currency + [ + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + } + + /** + * Stamp the version the replaced chain ended on. + * + * The v3-era migrations move it forward from here; what matters is that the + * row exists and reads as the boundary version, whether or not something + * wrote one first. + */ + private function seedVersion(): void + { + $now = now(); + + $existing = DB::table('settings')->where('option', 'version')->exists(); + + if ($existing) { + DB::table('settings') + ->where('option', 'version') + ->update([ + 'value' => self::BOUNDARY_VERSION, + 'updated_at' => $now, + ]); + + return; + } + + DB::table('settings')->insert([ + 'option' => 'version', + 'value' => self::BOUNDARY_VERSION, + 'created_at' => $now, + 'updated_at' => $now, + ]); + } + + /** + * Declare one of the SQLite-only legacy columns, if this is SQLite. + * + * Called from inside the table's own closure so the column lands in the + * position the historical chain left it in. + */ + private function addRetainedColumn(Blueprint $table, string $forTable): void + { + if (! $this->retainsLegacyColumns) { + return; + } + + $column = self::SQLITE_RETAINED_COLUMNS[$forTable]; + $references = $column === 'company_id' ? 'companies' : 'users'; + + $table->unsignedInteger($column)->nullable(); + $table->foreign($column)->references('id')->on($references)->onDelete('cascade'); + } +}; diff --git a/tests/Feature/Setup/BaseSchemaConsolidationTest.php b/tests/Feature/Setup/BaseSchemaConsolidationTest.php new file mode 100644 index 00000000..8baaf5bc --- /dev/null +++ b/tests/Feature/Setup/BaseSchemaConsolidationTest.php @@ -0,0 +1,503 @@ + 'squash', + '--path' => consolidationPath(), + '--force' => true, + ]); +} + +/** + * Give the throwaway database a migration repository holding these names. + */ +function recordHistory(array $names): void +{ + Artisan::call('migrate:install', ['--database' => 'squash']); + + DB::connection('squash')->table('migrations')->insert( + array_map(fn (string $name): array => ['migration' => $name, 'batch' => 1], $names) + ); +} + +/** + * The names a real 2.4.x database carries beyond the replaced set. + * + * Module migrations share the repository table, and databases from the 2.4.x + * line recorded one migration this codebase has never shipped. Both must be + * ignored by the decision. + */ +function foreignHistory(): array +{ + return [ + '2022_06_01_120000_create_payments_module_tables', + '2026_04_07_000001_increase_tax_percent_precision_to_three_decimals', + ]; +} + +/** + * Stand in for "this database already has a schema". + */ +function giveDatabaseASentinel(): void +{ + Schema::connection('squash')->create('companies', function (Blueprint $table) { + $table->increments('id'); + $table->string('name'); + }); +} + +/** + * Everything about the database that an ABORT must leave untouched. + */ +function databaseFootprint(): array +{ + $builder = Schema::connection('squash'); + $footprint = []; + + foreach ($builder->getTables() as $table) { + $footprint[$table['name']] = [ + 'columns' => array_column($builder->getColumns($table['name']), 'name'), + 'rows' => DB::connection('squash')->table($table['name'])->get()->toArray(), + ]; + } + + ksort($footprint); + + return $footprint; +} + +/** + * The four introspection calls the boundary fixture was captured with. + */ +function introspectConsolidatedSchema(): array +{ + $builder = Schema::connection('squash'); + $tables = []; + + foreach ($builder->getTables() as $table) { + $name = $table['name']; + + if ($name === 'migrations') { + continue; + } + + $tables[$name] = [ + 'columns' => array_values($builder->getColumns($name)), + 'indexes' => array_values($builder->getIndexes($name)), + 'foreign_keys' => array_values($builder->getForeignKeys($name)), + ]; + } + + return normaliseIntrospection($tables); +} + +/** + * Put an introspection result in a comparable shape. + * + * Column order is compared as it stands — it is part of what the consolidation + * reproduces. Index and foreign-key order is not: the drivers report them in + * whatever order they please, so both are sorted. The schema each foreign key + * names is dropped, because it is the database's own name and the fixture was + * captured from a database with a different one. + */ +function normaliseIntrospection(array $tables): array +{ + $normalised = []; + + foreach ($tables as $name => $table) { + $indexes = array_values($table['indexes']); + usort($indexes, fn (array $a, array $b): int => [$a['name'], implode(',', $a['columns'])] + <=> [$b['name'], implode(',', $b['columns'])]); + + $foreignKeys = array_map(function (array $key): array { + unset($key['foreign_schema']); + + return $key; + }, array_values($table['foreign_keys'])); + usort($foreignKeys, fn (array $a, array $b): int => [implode(',', $a['columns']), $a['foreign_table']] + <=> [implode(',', $b['columns']), $b['foreign_table']]); + + $normalised[$name] = [ + 'columns' => array_values($table['columns']), + 'indexes' => $indexes, + 'foreign_keys' => $foreignKeys, + ]; + } + + ksort($normalised); + + return $normalised; +} + +/** + * The captured end state of the replaced chain on SQLite. + */ +function boundaryFixture(): array +{ + $fixture = json_decode(file_get_contents(__DIR__.'/fixtures/boundary.sqlite.json'), true); + + return normaliseIntrospection($fixture['tables']); +} + +beforeEach(function () { + $this->squashDatabase = tempnam(sys_get_temp_dir(), 'squash-').'.sqlite'; + touch($this->squashDatabase); + + config(['database.connections.squash' => [ + 'driver' => 'sqlite', + 'database' => $this->squashDatabase, + 'prefix' => '', + 'foreign_key_constraints' => true, + ]]); + + DB::purge('squash'); +}); + +afterEach(function () { + DB::purge('squash'); + @unlink($this->squashDatabase); +}); + +// -- BUILD ------------------------------------------------------------------ + +it('builds the boundary schema on an empty database', function () { + runConsolidation(); + + $built = introspectConsolidatedSchema(); + $expected = boundaryFixture(); + + expect(array_keys($built))->toBe(array_keys($expected)) + ->and(count($built))->toBe(43) + ->and(array_sum(array_map(fn (array $t): int => count($t['columns']), $built)))->toBe(524); + + foreach ($expected as $name => $table) { + expect($built[$name]['columns'])->toEqual($table['columns']); + expect($built[$name]['indexes'])->toEqual($table['indexes']); + expect($built[$name]['foreign_keys'])->toEqual($table['foreign_keys']); + } +}); + +it('seeds the two system file disks', function () { + runConsolidation(); + + $disks = DB::connection('squash')->table('file_disks')->orderBy('id')->get(); + + expect($disks)->toHaveCount(2) + ->and($disks[0]->name)->toBe('public') + ->and($disks[0]->type)->toBe('SYSTEM') + ->and($disks[0]->driver)->toBe('local') + ->and((bool) $disks[0]->set_as_default)->toBeFalse() + ->and($disks[1]->name)->toBe('local_private') + ->and($disks[1]->type)->toBe('SYSTEM') + ->and($disks[1]->driver)->toBe('local') + ->and((bool) $disks[1]->set_as_default)->toBeTrue(); + + // Credentials are a single JSON encoding, and describe this installation + // rather than the one the release was built on. + expect(json_decode($disks[0]->credentials, true))->toBe([ + 'driver' => 'local', + 'root' => config('filesystems.disks.public.root'), + 'url' => config('app.url').'/storage', + 'visibility' => 'public', + ]); + + expect(json_decode($disks[1]->credentials, true))->toBe([ + 'root' => config('filesystems.disks.local.root'), + 'driver' => 'local', + ]); +}); + +it('seeds the three late currencies', function () { + runConsolidation(); + + $currencies = DB::connection('squash')->table('currencies')->orderBy('code')->get() + ->keyBy('code'); + + expect($currencies->keys()->all())->toBe(['DZD', 'PYG', 'QAR']) + ->and($currencies['DZD']->name)->toBe('Algerian Dinar') + ->and($currencies['DZD']->symbol)->toBe('DA') + ->and($currencies['DZD']->precision)->toBe(2) + ->and($currencies['DZD']->thousand_separator)->toBe(',') + ->and($currencies['DZD']->decimal_separator)->toBe('.') + ->and($currencies['PYG']->name)->toBe('Paraguayan Guaraní') + ->and($currencies['PYG']->symbol)->toBe('₲') + ->and($currencies['PYG']->precision)->toBe(0) + ->and($currencies['PYG']->thousand_separator)->toBe('.') + ->and($currencies['PYG']->decimal_separator)->toBe(',') + ->and($currencies['QAR']->name)->toBe('Qatari Riyal') + ->and($currencies['QAR']->symbol)->toBe('QR') + ->and($currencies['QAR']->precision)->toBe(2); +}); + +it('stamps the boundary version', function () { + runConsolidation(); + + expect(DB::connection('squash')->table('settings')->where('option', 'version')->value('value')) + ->toBe('1.3.0'); +}); + +/** + * The collision the seed step exists to survive: something else got a currency + * into the table first. The row is planted the moment the currencies table + * appears, so the seed step meets it exactly as it would in the wild. + */ +it('inserts only the currencies that are missing at seed time', function () { + DB::connection('squash')->listen(function ($query) { + static $planted = false; + + if ($planted || preg_match('/^create table "currencies"/i', $query->sql) !== 1) { + return; + } + + $planted = true; + + DB::connection('squash')->table('currencies')->insert([ + 'name' => 'Guarani From Somewhere Else', + 'code' => 'PYG', + 'symbol' => 'G', + 'precision' => 0, + 'thousand_separator' => '.', + 'decimal_separator' => ',', + ]); + }); + + runConsolidation(); + + $currencies = DB::connection('squash')->table('currencies')->orderBy('code')->get(); + + expect($currencies)->toHaveCount(3) + ->and($currencies->pluck('code')->all())->toBe(['DZD', 'PYG', 'QAR']) + ->and($currencies->firstWhere('code', 'PYG')->name)->toBe('Guarani From Somewhere Else'); +}); + +// -- SKIP ------------------------------------------------------------------- + +it('leaves a fully migrated 2.4.x database alone', function () { + recordHistory([...SchemaConsolidationGuard::REPLACED_MIGRATIONS, ...foreignHistory()]); + giveDatabaseASentinel(); + DB::connection('squash')->table('companies')->insert(['name' => 'Acme']); + + $before = databaseFootprint(); + + runConsolidation(); + + $after = databaseFootprint(); + + // The only change is the consolidation's own row in the repository. + expect(array_keys($after))->toBe(array_keys($before)) + ->and($after['companies'])->toEqual($before['companies']) + ->and(count($after['migrations']['rows']))->toBe(count($before['migrations']['rows']) + 1) + ->and(DB::connection('squash')->table('migrations') + ->where('migration', SchemaConsolidationGuard::CONSOLIDATION_MIGRATION)->exists())->toBeTrue(); + + // No schema was built: the sentinel is still the stub, and none of the + // boundary tables appeared beside it. + expect(Schema::connection('squash')->hasTable('invoices'))->toBeFalse() + ->and($after['companies']['columns'])->toBe(['id', 'name']); +}); + +it('ignores recorded names that are not part of the replaced set', function () { + recordHistory(foreignHistory()); + + // Nothing from the replaced set is present, and there is no schema, so this + // is still a fresh database however many other names it carries. + expect(SchemaConsolidationGuard::inspect('squash')->mode())->toBe(SchemaConsolidationGuard::BUILD); + + runConsolidation(); + + expect(Schema::connection('squash')->hasTable('invoices'))->toBeTrue(); +}); + +// -- ABORT: floor ----------------------------------------------------------- + +it('refuses a database that stopped one migration short of the 2.x boundary', function () { + $history = SchemaConsolidationGuard::REPLACED_MIGRATIONS; + array_pop($history); + + recordHistory([...$history, ...foreignHistory()]); + giveDatabaseASentinel(); + + $before = databaseFootprint(); + + expect(fn () => runConsolidation())->toThrow( + RuntimeException::class, + 'Upgrading to this version requires the database to be fully migrated on the 2.x line first. ' + .'This database has run 149 of the 150 historical migrations this version consolidates. ' + .'Install the latest InvoiceShelf 2.x release, run its migrations to completion, then upgrade ' + .'to this version again. No changes have been made.' + ); + + expect(databaseFootprint())->toEqual($before); +}); + +it('refuses a database that only ever ran the first migration', function () { + recordHistory([SchemaConsolidationGuard::REPLACED_MIGRATIONS[0]]); + + $before = databaseFootprint(); + + expect(fn () => runConsolidation())->toThrow( + RuntimeException::class, + 'This database has run 1 of the 150 historical migrations this version consolidates.' + ); + + expect(databaseFootprint())->toEqual($before); +}); + +/** + * One replaced name genuinely contains a space. Matching is byte-exact, so a + * database recording the tidied-up spelling has not run that migration. + */ +it('matches replaced names byte for byte', function () { + $spaced = '2018_11_02_133825_create_ expense_categories_table'; + + expect(SchemaConsolidationGuard::REPLACED_MIGRATIONS)->toContain($spaced); + + $history = array_map( + fn (string $name): string => $name === $spaced ? str_replace(' ', '', $name) : $name, + SchemaConsolidationGuard::REPLACED_MIGRATIONS + ); + + recordHistory($history); + giveDatabaseASentinel(); + + // 149 of 150 matched: the de-spaced name counts for nothing. + $verdict = SchemaConsolidationGuard::inspect('squash'); + + expect($verdict->mode())->toBe(SchemaConsolidationGuard::ABORT_FLOOR) + ->and($verdict->recordedCount())->toBe(149); +}); + +// -- ABORT: inconsistent ---------------------------------------------------- + +it('refuses a schema that arrived without its history', function () { + recordHistory([]); + giveDatabaseASentinel(); + + $before = databaseFootprint(); + + expect(fn () => runConsolidation())->toThrow( + RuntimeException::class, + 'The database schema and the migration history do not match (the recorded history and the ' + .'`companies` table disagree). This usually means a partial restore. Restore a consistent ' + .'backup — schema and migration history together — and run the upgrade again. No changes ' + .'have been made.' + ); + + expect(databaseFootprint())->toEqual($before); +}); + +it('refuses a history that arrived without its schema', function () { + recordHistory([...SchemaConsolidationGuard::REPLACED_MIGRATIONS, ...foreignHistory()]); + + $before = databaseFootprint(); + + expect(fn () => runConsolidation())->toThrow( + RuntimeException::class, + 'The database schema and the migration history do not match' + ); + + expect(databaseFootprint())->toEqual($before); +}); + +// -- Re-running and rolling back -------------------------------------------- + +it('does nothing when migrate runs a second time', function () { + runConsolidation(); + + $before = databaseFootprint(); + + runConsolidation(); + + expect(databaseFootprint())->toEqual($before); +}); + +it('refuses to be rolled back', function () { + runConsolidation(); + + $before = databaseFootprint(); + + expect(fn () => Artisan::call('migrate:rollback', [ + '--database' => 'squash', + '--path' => consolidationPath(), + '--force' => true, + ]))->toThrow(RuntimeException::class, 'irreversible'); + + expect(databaseFootprint())->toEqual($before); +}); + +// -- The decision table, read directly -------------------------------------- + +it('decides every row of the decision table from reads alone', function ( + bool $withHistory, + bool $partial, + bool $withSentinel, + string $expected, +) { + $history = []; + + if ($withHistory) { + $history = SchemaConsolidationGuard::REPLACED_MIGRATIONS; + + if ($partial) { + array_pop($history); + } + } + + recordHistory($history); + + if ($withSentinel) { + giveDatabaseASentinel(); + } + + expect(SchemaConsolidationGuard::inspect('squash')->mode())->toBe($expected); +})->with([ + 'empty history, no schema' => [false, false, false, SchemaConsolidationGuard::BUILD], + 'empty history, schema present' => [false, false, true, SchemaConsolidationGuard::ABORT_INCONSISTENT], + 'partial history, schema present' => [true, true, true, SchemaConsolidationGuard::ABORT_FLOOR], + 'partial history, no schema' => [true, true, false, SchemaConsolidationGuard::ABORT_FLOOR], + 'complete history, schema present' => [true, false, true, SchemaConsolidationGuard::SKIP], + 'complete history, no schema' => [true, false, false, SchemaConsolidationGuard::ABORT_INCONSISTENT], +]); + +it('embeds the replaced set exactly once, at full length', function () { + $replaced = SchemaConsolidationGuard::REPLACED_MIGRATIONS; + + expect($replaced)->toHaveCount(150) + ->and(array_unique($replaced))->toHaveCount(150) + ->and($replaced[0])->toBe('2014_10_11_071840_create_companies_table') + ->and(end($replaced))->toBe('2025_09_02_add_expense_number_to_expenses_table'); + + // None of them is still on disk: this migration is what replaced them. + foreach ($replaced as $name) { + expect(file_exists(base_path('database/migrations/'.$name.'.php')))->toBeFalse(); + } + + expect(glob(base_path('database/migrations/*.php'))) + ->each(fn ($file) => expect(basename($file->value))->toStartWith('2026_')); +}); diff --git a/tests/Feature/Setup/SchemaConsolidationPreflightTest.php b/tests/Feature/Setup/SchemaConsolidationPreflightTest.php new file mode 100644 index 00000000..4cdf8af5 --- /dev/null +++ b/tests/Feature/Setup/SchemaConsolidationPreflightTest.php @@ -0,0 +1,101 @@ +where('migration', SchemaConsolidationGuard::CONSOLIDATION_MIGRATION) + ->delete(); + + DB::table('migrations')->insert(array_map( + fn (string $name): array => ['migration' => $name, 'batch' => 1], + array_slice(SchemaConsolidationGuard::REPLACED_MIGRATIONS, 0, $ran) + )); +} + +it('has nothing to say about a database that already ran the consolidation', function () { + // The suite's own database: built by the consolidation, so it is recorded. + expect(DB::table('migrations') + ->where('migration', SchemaConsolidationGuard::CONSOLIDATION_MIGRATION)->exists())->toBeTrue() + ->and(SchemaConsolidationGuard::preflight())->toBeNull(); +}); + +/** + * Without the "already ran" check, this is the case that would break every + * future upgrade: such a database has none of the replaced names recorded but + * does have a schema, which the decision table reads as an inconsistency. + */ +it('does not mistake a consolidated database for an inconsistent one', function () { + expect(SchemaConsolidationGuard::inspect()->mode()) + ->toBe(SchemaConsolidationGuard::ABORT_INCONSISTENT) + ->and(SchemaConsolidationGuard::preflight())->toBeNull(); +}); + +it('reports the floor verdict for a half-migrated database', function () { + fabricateUnfinishedHistory(); + + $verdict = SchemaConsolidationGuard::preflight(); + + expect($verdict)->not->toBeNull() + ->and($verdict->isAbort())->toBeTrue() + ->and($verdict->mode())->toBe(SchemaConsolidationGuard::ABORT_FLOOR) + ->and($verdict->recordedCount())->toBe(149) + ->and($verdict->message())->toContain('has run 149 of the 150 historical migrations'); +}); + +it('stops the update pipeline before any migration runs', function () { + fabricateUnfinishedHistory(1); + + expect(fn () => Updater::migrateUpdate())->toThrow( + RuntimeException::class, + 'Upgrading to this version requires the database to be fully migrated on the 2.x line first. ' + .'This database has run 1 of the 150 historical migrations this version consolidates. ' + .'Install the latest InvoiceShelf 2.x release, run its migrations to completion, then upgrade ' + .'to this version again. No changes have been made.' + ); +}); + +it('lets the update pipeline through when the consolidation has no objection', function () { + expect(Updater::migrateUpdate())->toBeTrue(); +}); + +it('answers the migrate endpoint with the refusal instead of a crash', function () { + Artisan::call('db:seed', ['--class' => 'DatabaseSeeder', '--force' => true]); + Artisan::call('db:seed', ['--class' => 'DemoSeeder', '--force' => true]); + + $user = User::where('role', 'super admin')->first(); + $this->withHeaders(['company' => $user->companies()->first()->id]); + Sanctum::actingAs($user, ['*']); + + fabricateUnfinishedHistory(); + + postJson('/api/v1/update/migrate') + ->assertStatus(500) + ->assertJson([ + 'success' => false, + 'error' => 'Upgrading to this version requires the database to be fully migrated on the 2.x line first. ' + .'This database has run 149 of the 150 historical migrations this version consolidates. ' + .'Install the latest InvoiceShelf 2.x release, run its migrations to completion, then upgrade ' + .'to this version again. No changes have been made.', + ]); +}); diff --git a/tests/Feature/Setup/fixtures/boundary.sqlite.json b/tests/Feature/Setup/fixtures/boundary.sqlite.json new file mode 100644 index 00000000..01c4a865 --- /dev/null +++ b/tests/Feature/Setup/fixtures/boundary.sqlite.json @@ -0,0 +1,7682 @@ +{ + "driver": "sqlite", + "tables": { + "abilities": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "title", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "only_owned", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "options", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "scope", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "abilities_scope_index", + "columns": [ + "scope" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "addresses": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "address_street_1", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "address_street_2", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "city", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "state", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "country_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "zip", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "phone", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "fax", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "customer_id" + ], + "foreign_schema": "main", + "foreign_table": "customers", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "country_id" + ], + "foreign_schema": "main", + "foreign_table": "countries", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "assigned_roles": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "role_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "restricted_to_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "restricted_to_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "scope", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "assigned_roles_entity_index", + "columns": [ + "entity_id", + "entity_type", + "scope" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "assigned_roles_role_id_index", + "columns": [ + "role_id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "assigned_roles_scope_index", + "columns": [ + "scope" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "role_id" + ], + "foreign_schema": "main", + "foreign_table": "roles", + "foreign_columns": [ + "id" + ], + "on_update": "cascade", + "on_delete": "cascade" + } + ] + }, + "cache": { + "columns": [ + { + "name": "key", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "value", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expiration", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "sqlite_autoindex_cache_1", + "columns": [ + "key" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "cache_locks": { + "columns": [ + { + "name": "key", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "owner", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expiration", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "sqlite_autoindex_cache_locks_1", + "columns": [ + "key" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "companies": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "logo", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unique_hash", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "slug", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "owner_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "vat_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "owner_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "company_settings": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "option", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "value", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "countries": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "code", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "phonecode", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "countries_id_index", + "columns": [ + "id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "currencies": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "code", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "symbol", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "precision", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "thousand_separator", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "decimal_separator", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "swap_currency_symbol", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "custom_field_values": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "custom_field_valuable_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "custom_field_valuable_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "boolean_answer", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "date_answer", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "time_answer", + "type_name": "time", + "type": "time", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "string_answer", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "number_answer", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "date_time_answer", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "custom_field_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "custom_field_id" + ], + "foreign_schema": "main", + "foreign_table": "custom_fields", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "custom_fields": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "slug", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "label", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "model_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "placeholder", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "options", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "boolean_answer", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "date_answer", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "time_answer", + "type_name": "time", + "type": "time", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "string_answer", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "number_answer", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "date_time_answer", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "is_required", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "order", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": "'1'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "customers": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "email", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "phone", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "password", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "remember_token", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "facebook_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "google_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "github_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "contact_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "website", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "enable_portal", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "prefix", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "email_logs": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "from", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "to", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "subject", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "body", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "mailable_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "mailable_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "token", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "email_logs_token_unique", + "columns": [ + "token" + ], + "type": null, + "unique": true, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "estimate_items": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "description", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "quantity", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "price", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "item_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "estimate_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unit_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_price", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "item_id" + ], + "foreign_schema": "main", + "foreign_table": "items", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "estimate_id" + ], + "foreign_schema": "main", + "foreign_table": "estimates", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "estimates": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "estimate_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expiry_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "estimate_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "status", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "reference_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sub_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unique_hash", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "template_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_sub_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_address_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_included", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "customer_id" + ], + "foreign_schema": "main", + "foreign_table": "customers", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "exchange_rate_logs": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "base_currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "exchange_rate_providers": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "driver", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "key", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currencies", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "driver_config", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "active", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'1'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "expense_categories": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "description", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "expenses": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "expense_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "attachment_receipt", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expense_category_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "payment_method_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expense_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "expenses_expense_number_index", + "columns": [ + "expense_number" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "payment_method_id" + ], + "foreign_schema": "main", + "foreign_table": "payment_methods", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "expense_category_id" + ], + "foreign_schema": "main", + "foreign_table": "expense_categories", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "file_disks": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'REMOTE'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "driver", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "set_as_default", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "credentials", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "invoice_items": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "description", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "price", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "quantity", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "item_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unit_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "recurring_invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_price", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "recurring_invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "recurring_invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "item_id" + ], + "foreign_schema": "main", + "foreign_table": "items", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "invoices": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "invoice_date", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "due_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "reference_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "status", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "paid_status", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sub_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "due_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sent", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "viewed", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unique_hash", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "template_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "recurring_invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_sub_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_due_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_address_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "overdue", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_included", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "recurring_invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "recurring_invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "customer_id" + ], + "foreign_schema": "main", + "foreign_table": "customers", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "items": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "description", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "price", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unit_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_per_item", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "unit_id" + ], + "foreign_schema": "main", + "foreign_table": "units", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "jobs": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "queue", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "payload", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "attempts", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "reserved_at", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "available_at", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "jobs_queue_index", + "columns": [ + "queue" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "media": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "model_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "model_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "collection_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "file_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "mime_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "disk", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "size", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "manipulations", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "custom_properties", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "responsive_images", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "order_column", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "uuid", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "conversions_disk", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "generated_conversions", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "media_model_type_model_id_index", + "columns": [ + "model_type", + "model_id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "modules": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "version", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "installed", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "enabled", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "notes": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "is_default", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "notifications": { + "columns": [ + { + "name": "id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notifiable_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notifiable_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "data", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "read_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "notifications_notifiable_type_notifiable_id_index", + "columns": [ + "notifiable_type", + "notifiable_id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "sqlite_autoindex_notifications_1", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "password_reset_tokens": { + "columns": [ + { + "name": "email", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "token", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "password_resets_email_index", + "columns": [ + "email" + ], + "type": null, + "unique": false, + "primary": false + } + ], + "foreign_keys": [] + }, + "payment_methods": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "driver", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'GENERAL'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "settings", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "active", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "use_test_env", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "payments": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "payment_number", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "payment_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unique_hash", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "payment_method_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_sequence_number", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "transaction_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "transaction_id" + ], + "foreign_schema": "main", + "foreign_table": "transactions", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "customer_id" + ], + "foreign_schema": "main", + "foreign_table": "customers", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "payment_method_id" + ], + "foreign_schema": "main", + "foreign_table": "payment_methods", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "permissions": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "ability_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "entity_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "forbidden", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "scope", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "permissions_ability_id_index", + "columns": [ + "ability_id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "permissions_entity_index", + "columns": [ + "entity_id", + "entity_type", + "scope" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "permissions_scope_index", + "columns": [ + "scope" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "ability_id" + ], + "foreign_schema": "main", + "foreign_table": "abilities", + "foreign_columns": [ + "id" + ], + "on_update": "cascade", + "on_delete": "cascade" + } + ] + }, + "personal_access_tokens": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "tokenable_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tokenable_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "token", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "abilities", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "last_used_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "expires_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "personal_access_tokens_token_unique", + "columns": [ + "token" + ], + "type": null, + "unique": true, + "primary": false + }, + { + "name": "personal_access_tokens_tokenable_type_tokenable_id_index", + "columns": [ + "tokenable_type", + "tokenable_id" + ], + "type": null, + "unique": false, + "primary": false + }, + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "recurring_invoices": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "starts_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "send_automatically", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "customer_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "status", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'ACTIVE'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "next_invoice_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "frequency", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "limit_by", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'NONE'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "limit_count", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "limit_date", + "type_name": "date", + "type": "date", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_per_item", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "notes", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "discount_val", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sub_total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "total", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "template_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "due_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "sales_tax_address_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "tax_included", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "customer_id" + ], + "foreign_schema": "main", + "foreign_table": "customers", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "roles": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "title", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "level", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "scope", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + }, + { + "name": "roles_name_unique", + "columns": [ + "name", + "scope" + ], + "type": null, + "unique": true, + "primary": false + }, + { + "name": "roles_scope_index", + "columns": [ + "scope" + ], + "type": null, + "unique": false, + "primary": false + } + ], + "foreign_keys": [] + }, + "settings": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "option", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "value", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [] + }, + "tax_types": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "percent", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "compound_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "collective_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "description", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'GENERAL'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "calculation_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'percentage'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "fixed_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "taxes": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "tax_type_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "estimate_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_item_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "estimate_item_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "item_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "percent", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "compound_tax", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": "'0'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "exchange_rate", + "type_name": "numeric", + "type": "numeric", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "base_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "recurring_invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "calculation_type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'percentage'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "fixed_amount", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "recurring_invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "recurring_invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "item_id" + ], + "foreign_schema": "main", + "foreign_table": "items", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "estimate_item_id" + ], + "foreign_schema": "main", + "foreign_table": "estimate_items", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "invoice_item_id" + ], + "foreign_schema": "main", + "foreign_table": "invoice_items", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "estimate_id" + ], + "foreign_schema": "main", + "foreign_table": "estimates", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "tax_type_id" + ], + "foreign_schema": "main", + "foreign_table": "tax_types", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "transactions": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "transaction_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "unique_hash", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "type", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "status", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "transaction_date", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "invoice_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "invoice_id" + ], + "foreign_schema": "main", + "foreign_table": "invoices", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "no action" + } + ] + }, + "units": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "user_company": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "user_settings": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "key", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "value", + "type_name": "text", + "type": "text", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "user_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "user_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + }, + "users": { + "columns": [ + { + "name": "id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": true, + "comment": null, + "generation": null + }, + { + "name": "name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "email", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "phone", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "password", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "role", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": false, + "default": "'user'", + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "remember_token", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "facebook_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "google_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "github_id", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "contact_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_name", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "website", + "type_name": "varchar", + "type": "varchar", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "enable_portal", + "type_name": "tinyint", + "type": "tinyint(1)", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "currency_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "company_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "created_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "updated_at", + "type_name": "datetime", + "type": "datetime", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + }, + { + "name": "creator_id", + "type_name": "integer", + "type": "integer", + "collation": null, + "nullable": true, + "default": null, + "auto_increment": false, + "comment": null, + "generation": null + } + ], + "indexes": [ + { + "name": "primary", + "columns": [ + "id" + ], + "type": null, + "unique": true, + "primary": true + }, + { + "name": "users_email_unique", + "columns": [ + "email" + ], + "type": null, + "unique": true, + "primary": false + } + ], + "foreign_keys": [ + { + "name": null, + "columns": [ + "creator_id" + ], + "foreign_schema": "main", + "foreign_table": "users", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "currency_id" + ], + "foreign_schema": "main", + "foreign_table": "currencies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + }, + { + "name": null, + "columns": [ + "company_id" + ], + "foreign_schema": "main", + "foreign_table": "companies", + "foreign_columns": [ + "id" + ], + "on_update": "no action", + "on_delete": "cascade" + } + ] + } + } +}