from editing import *
p='app/Actions/Fortify/ResetUserPassword.php';s=read(p);a=s.index('        $user->forceFill([');b=s.index('\n    }',a)
s=s[:a]+'''        \\Illuminate\\Support\\Facades\\DB::transaction(function () use ($user, $input): void {
            $locked = User::whereKey($user->id)->lockForUpdate()->firstOrFail();
            $locked->forceFill(['password' => $input['password']])->save();
            if ($locked->invitation_pending) {
                $locked->forceFill(['invitation_pending' => false, 'email_verified_at' => now()])->save();
                if ($locked->registration_consents['publish_catalogue'] ?? false) {
                    $locked->entreprises()->update(['is_published' => true]);
                }
            }
            $user->setRawAttributes($locked->getAttributes(), true);
        });'''+s[b:];write(p,s)
p='resources/js/pages/portal/QuoteRequests/Form.vue';replace(p," || new Date(form.response_due_at).getTime() <= Date.now()",'')
p='app/Http/Controllers/Portal/ReceivedQuoteRequestController.php';replace(p,"                $recipient = QuoteRequestRecipient::whereKey($recipient->id)->lockForUpdate()->firstOrFail();", "                \\App\\Models\\QuoteRequest::whereKey($recipient->quote_request_id)->lockForUpdate()->firstOrFail();\n                $recipient = QuoteRequestRecipient::whereKey($recipient->id)->lockForUpdate()->firstOrFail();")
write('tests/Feature/AuditWorkflowCorrectionsTest.php', '''<?php
namespace Tests\\Feature;
use App\\Models\\{User, Role, Entreprise, QuoteRequest, ContentPage, Mrc};
use App\\Notifications\\AccountActivationNotification;
use Illuminate\\Foundation\\Testing\\RefreshDatabase;
use Illuminate\\Http\\UploadedFile;
use Illuminate\\Support\\Facades\\{Notification, Storage, URL};
use Tests\\TestCase;
class AuditWorkflowCorrectionsTest extends TestCase
{
    use RefreshDatabase;
    private function account(string $role): User { $user = User::factory()->create(); $user->roles()->attach(Role::firstOrCreate(['slug'=>$role], ['name'=>$role])); return $user; }
    public function test_draft_resumes_and_sends_once_in_toronto_time(): void
    {
        Notification::fake();
        $this->travelTo(\\Carbon\\Carbon::parse('2026-10-20 12:00:00', 'UTC'));
        $buyer = $this->account('acheteur'); $supplier = $this->account('entreprise');
        $company = Entreprise::create(['nom'=>'Fournisseur', 'user_id'=>$supplier->id]);
        $this->actingAs($buyer)->post('/portail/demandes', ['titre'=>'Brouillon partiel', 'action'=>'draft', 'recipient_ids'=>[]])->assertSessionHasNoErrors();
        $quote = QuoteRequest::sole(); $this->assertNull($quote->response_due_at);
        $this->get('/portail/demandes/'.$quote->id.'/modifier')->assertOk();
        $this->put('/portail/demandes/'.$quote->id, ['titre'=>'Besoin final', 'description'=>'Produits locaux', 'response_due_at'=>'2026-10-30T16:00', 'action'=>'send', 'recipient_ids'=>[$company->id]])->assertSessionHasNoErrors();
        $this->assertSame('2026-10-30 20:00:00', $quote->fresh()->response_due_at->format('Y-m-d H:i:s'));
        $this->assertSame('sent', $quote->fresh()->status); $this->assertSame(1, $quote->recipients()->count());
        $this->put('/portail/demandes/'.$quote->id, ['titre'=>'Double envoi', 'description'=>'Produits locaux', 'response_due_at'=>'2026-10-30T16:00', 'action'=>'send', 'recipient_ids'=>[$company->id]])->assertSessionHasErrors('titre');
        $this->assertSame(1, $quote->recipients()->count());
        $other = $this->account('acheteur'); $this->actingAs($other)->get('/portail/demandes/'.$quote->id)->assertForbidden();
    }
    public function test_response_history_attachments_close_and_reopen_preserve_offers(): void
    {
        Notification::fake(); Storage::fake('local');
        $buyer=$this->account('acheteur'); $supplier=$this->account('entreprise');
        $company=Entreprise::create(['nom'=>'Fournisseur', 'user_id'=>$supplier->id]);
        $quote=QuoteRequest::create(['requester_id'=>$buyer->id, 'titre'=>'Demande', 'description'=>'Besoin', 'status'=>'sent', 'response_due_at'=>now()->addWeek(), 'sent_at'=>now()]);
        $recipient=$quote->recipients()->create(['entreprise_id'=>$company->id,'entreprise_nom'=>$company->nom,'email'=>$supplier->email,'status'=>'pending','sent_at'=>now()]);
        $this->actingAs($supplier)->post('/portail/demandes-recues/'.$recipient->id.'/repondre', ['amount'=>'125.75','message'=>'Première offre','attachment'=>UploadedFile::fake()->create('offre.pdf',1,'application/pdf')])->assertSessionHasNoErrors();
        $response=$recipient->response()->sole(); $originalPath=$response->attachment_path;
        $this->post('/portail/demandes-recues/'.$recipient->id.'/repondre', ['amount'=>'130.25','message'=>'Nouvelle offre'])->assertSessionHasNoErrors();
        $this->assertSame('125.75',$response->versions()->sole()->amount); $this->assertSame($originalPath,$response->fresh()->attachment_path);
        $this->actingAs($buyer)->get('/portail/reponses/'.$response->id.'/piece-jointe')->assertOk();
        $version=$response->versions()->sole(); $this->get('/portail/reponses/'.$response->id.'/versions/'.$version->id.'/piece-jointe')->assertOk();
        $this->post('/portail/demandes/'.$quote->id.'/fermer')->assertRedirect();
        $this->actingAs($supplier)->post('/portail/demandes-recues/'.$recipient->id.'/repondre',['amount'=>'1.00','message'=>'Trop tard'])->assertSessionHasErrors('message');
        $this->assertSame('130.25',$response->fresh()->amount); $this->assertSame(1,$response->versions()->count());
        $this->actingAs($buyer)->post('/portail/demandes/'.$quote->id.'/rouvrir',['response_due_at'=>now()->addWeeks(2)->toIso8601String()])->assertSessionHasNoErrors();
        $this->assertSame('sent',$quote->fresh()->status); $this->assertSame('130.25',$response->fresh()->amount);
        $outsider=$this->account('entreprise'); $this->actingAs($outsider)->get('/portail/reponses/'.$response->id.'/versions/'.$version->id.'/piece-jointe')->assertForbidden();
    }
    public function test_invitation_password_submission_activates_and_publishes_once(): void
    {
        Notification::fake();
        $user=User::factory()->unverified()->create(['invitation_pending'=>true,'registration_consents'=>['publish_catalogue'=>true]]);
        $company=Entreprise::create(['nom'=>'Encore privée','user_id'=>$user->id,'is_published'=>false]);
        $url=URL::temporarySignedRoute('activation.verify',now()->addDay(),['user'=>$user->id,'hash'=>sha1($user->email)]);
        $this->get('/repertoire/'.$company->id)->assertNotFound();
        $activation=$this->get($url)->assertRedirect(); $this->assertNull($user->fresh()->email_verified_at);
        parse_str(parse_url($activation->headers->get('Location'),PHP_URL_QUERY),$query);
        $token=basename(parse_url($activation->headers->get('Location'),PHP_URL_PATH));
        $this->post('/reset-password',['email'=>$user->email,'token'=>$token,'password'=>'NewPassword2026!Local','password_confirmation'=>'NewPassword2026!Local'])->assertSessionHasNoErrors();
        $this->assertTrue($user->fresh()->hasVerifiedEmail()); $this->assertFalse($user->fresh()->invitation_pending); $this->assertTrue($company->fresh()->is_published);
        $this->get('/repertoire/'.$company->id)->assertOk(); Notification::assertNothingSent();
        $this->get($url)->assertRedirect(route('login'));
    }
    public function test_import_requires_preview_and_invalid_last_row_writes_nothing(): void
    {
        $admin=$this->account('admin'); $this->actingAs($admin);
        $file=UploadedFile::fake()->createWithContent('mrc.csv',"nom\\nPremière\\nDeuxième;Invalide\\n");
        $this->post('/admin/tools/import',['resource'=>'mrcs','file'=>$file])->assertSessionHasErrors('file'); $this->assertDatabaseMissing('mrcs',['nom'=>'Première']);
        $file=UploadedFile::fake()->createWithContent('mrc.csv',"nom\\nPremière\\nDeuxième\\n");
        $this->post('/admin/tools/import',['resource'=>'mrcs','file'=>$file])->assertSessionHasNoErrors(); $this->assertDatabaseCount('mrcs',0);
        $preview=session('raca_import_preview');
        $this->post('/admin/tools/import',['confirm_import'=>true,'token'=>$preview['token']])->assertSessionHasNoErrors(); $this->assertDatabaseCount('mrcs',2);
        $this->post('/admin/tools/import',['confirm_import'=>true,'token'=>$preview['token']])->assertSessionHasErrors('file'); $this->assertDatabaseCount('mrcs',2);
    }
    public function test_content_preview_history_restore_and_structure_validation(): void
    {
        $admin=$this->account('admin'); $this->actingAs($admin);
        $this->put('/admin/content/home',['content'=>['hero_title'=>['invalid']]])->assertSessionHasErrors('content.hero_title');
        $this->put('/admin/content/home',['content'=>['hero_title'=>'Première version']])->assertSessionHasNoErrors();
        $this->get('/admin/content/home/apercu')->assertOk()->assertSee('Première version')->assertHeader('X-Robots-Tag','noindex, nofollow');
        $this->get('/')->assertDontSee('Première version');
        $this->post('/admin/content/home/publish')->assertRedirect(); $page=ContentPage::sole(); $first=$page->versions()->sole();
        $this->put('/admin/content/home',['content'=>['hero_title'=>'Deuxième version']])->assertSessionHasNoErrors(); $this->post('/admin/content/home/publish')->assertRedirect();
        $this->assertSame(2,$page->versions()->count());
        $this->post('/admin/content/home/versions/'.$first->id.'/restaurer')->assertRedirect();
        $this->assertSame('Première version',$page->fresh()->draft_content['hero_title']); $this->get('/')->assertSee('Deuxième version');
        $this->actingAs($this->account('acheteur'))->get('/admin/content/home/apercu')->assertForbidden();
    }
    public function test_delegated_menu_and_primary_role_follow_server_permissions(): void
    {
        $user=$this->account('delegue'); $user->roles()->first()->permissions()->create(['permission_slug'=>'manageUsers']);
        $this->actingAs($user)->get('/admin/users')->assertInertia(fn(\\Inertia\\Testing\\AssertableInertia $p)=>$p->where('auth.user.capabilities.users',true)->where('auth.user.capabilities.entreprises',false));
        $this->actingAs($this->account('acheteur'))->get('/portail/demandes')->assertInertia(fn(\\Inertia\\Testing\\AssertableInertia $p)=>$p->where('auth.user.capabilities.dashboard',false)->where('auth.user.capabilities.requests',true));
        $user->roles()->attach(Role::firstOrCreate(['slug'=>'admin'],['name'=>'Administrateur'])); $this->assertSame('admin',$user->roleSlug());
        $this->actingAs($user)->get('/dashboard')->assertRedirect(route('admin.dashboard'));
    }
    public function test_inertia_logout_requests_a_full_page_navigation(): void
    {
        $this->actingAs($this->account('acheteur'))->withHeader('X-Inertia','true')->post('/logout')->assertStatus(409)->assertHeader('X-Inertia-Location',route('home'));
        $this->assertGuest();
    }
}''')
