id(); $table->foreignId('tenant_id')->constrained(); $table->foreignId('goods_receipt_id')->constrained(); $table->foreignId('uploaded_by')->constrained('users'); $table->string('document_type', 30); $table->string('original_name', 180); $table->string('path'); $table->string('mime_type', 100); $table->unsignedInteger('size'); $table->string('sha256', 64); $table->timestamp('created_at'); $table->unique(['tenant_id', 'goods_receipt_id', 'document_type', 'sha256'], 'receipt_document_content_unique'); }); if (DB::getDriverName() === 'pgsql') { DB::unprepared("CREATE FUNCTION receipt_documents_immutable() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN RAISE EXCEPTION 'Receipt documents are append only'; END; $$; CREATE TRIGGER receipt_documents_immutable BEFORE UPDATE OR DELETE ON receipt_documents FOR EACH ROW EXECUTE FUNCTION receipt_documents_immutable();"); } elseif (DB::getDriverName() === 'sqlite') { foreach (['UPDATE', 'DELETE'] as $event) { DB::unprepared('CREATE TRIGGER receipt_documents_'.strtolower($event)." BEFORE $event ON receipt_documents BEGIN SELECT RAISE(ABORT, 'Receipt documents are append only'); END"); } } } public function down(): void { Schema::dropIfExists('receipt_documents'); if (DB::getDriverName() === 'pgsql') { DB::unprepared('DROP FUNCTION IF EXISTS receipt_documents_immutable()'); } } };