f=suggestionFixture(); $this->actingAs($this->f['issuer']); $this->input=['operation_key'=>(string)Str::uuid(),'version'=>0,'action'=>'save','reason'=>'Parametrização autorizada para o ensaio isolado.', 'data'=>['policy_key'=>$this->f['policy']->record_key,'requester_id'=>$this->f['issuer']->id,'cost_center_id'=>$this->f['r']->cost_center_id,'unit_price'=>null,'tax_rate'=>null,'needed_days'=>5,'valid_until'=>today()->addMonth()->toDateString()]]; }); function activateAutomaticRule($test) { $test->actingAs($test->f['issuer']); $rule=app(AutomaticPurchases::class)->save($test->f['issuer'],$test->input); $test->actingAs($test->f['receiver']); return app(AutomaticPurchases::class)->save($test->f['receiver'],['operation_key'=>(string)Str::uuid(),'record_key'=>$rule->record_key,'version'=>$rule->version,'action'=>'approve','reason'=>'Activação independente da parametrização verificada.','data'=>[]]); } function automaticIssue($test,string $quantity) { $test->actingAs($test->f['receiver']); return app(StockWorkflow::class)->record($test->f['receiver'],['kind'=>'issue','reference'=>'TEST-'.Str::uuid(),'idempotency_key'=>(string)Str::uuid(),'responsible_id'=>$test->f['issuer']->id,'warehouse_id'=>$test->f['warehouse']->id,'stock_item_id'=>$test->f['stock']->id,'quantity'=>$quantity,'destination'=>'Manutenção interna','reason'=>'Consumo físico confirmado no ensaio isolado.']); } test('automatic purchase reuses approved policy catalogue agreement and tax while preserving human submission and approvals',function(){ $rule=activateAutomaticRule($this); expect($rule->data['unit_price'])->toBe('10.00'); $count=Requisition::query()->count(); automaticIssue($this,'100.000'); app(AutomaticPurchases::class)->evaluate($this->f['receiver'],$this->f['stock']->id,$this->f['warehouse']->id); $need=app(SupplyPlanning::class)->latest('auto_need')->sole(); $r=Requisition::query()->findOrFail($need->data['purchases'][0]['id']); expect(Requisition::query()->count())->toBe($count+1)->and($r->status)->toBe('draft')->and($r->requester_id)->toBe($this->f['issuer']->id)->and($r->cost_center_id)->toBe($this->f['r']->cost_center_id) ->and($r->items()->sole()->quantity)->toBe('84.000')->and($r->steps()->count())->toBe(0)->and(DB::table('budget_entries')->count())->toBe(0) ->and(DB::table('audit_logs')->where('action','requisition.automatically_created')->value('user_id'))->toBe($this->f['receiver']->id); app(AutomaticPurchases::class)->evaluate($this->f['receiver'],$this->f['stock']->id,$this->f['warehouse']->id); expect(Requisition::query()->count())->toBe($count+1)->and(app(SupplyPlanning::class)->latest('auto_need')->sole()->state)->toBe('covered'); $this->actingAs($this->f['issuer']); app(RequisitionWorkflow::class)->submit($this->f['issuer'],$r); expect($r->fresh()->status)->toBe('pending')->and($r->steps()->first()->role)->toBe('department_head'); }); test('missing configuration records a pending need and never invents a requester or budget',function(){ $before=Requisition::query()->count(); automaticIssue($this,'120.000'); app(AutomaticPurchases::class)->evaluate($this->f['receiver'],$this->f['stock']->id,$this->f['warehouse']->id); $need=app(SupplyPlanning::class)->latest('auto_need')->sole(); expect($need->state)->toBe('pending')->and($need->data['purchases'])->toBe([])->and($need->data['message'])->toContain('Parametrize')->and(Requisition::query()->count())->toBe($before); }); test('a preparer cannot activate their own rule and stale or changed commands do not create duplicate rules',function(){ supplyGrant($this->f['issuer'],['supply_review']); $rule=app(AutomaticPurchases::class)->save($this->f['issuer'],$this->input); expect(app(AutomaticPurchases::class)->save($this->f['issuer'],$this->input)->id)->toBe($rule->id); $decision=['operation_key'=>(string)Str::uuid(),'record_key'=>$rule->record_key,'version'=>1,'action'=>'approve','reason'=>'Tentativa de activar a própria regra.','data'=>[]]; expect(fn()=>app(AutomaticPurchases::class)->save($this->f['issuer'],$decision))->toThrow(ValidationException::class); $this->input['data']['needed_days']=9; expect(fn()=>app(AutomaticPurchases::class)->save($this->f['issuer'],$this->input))->toThrow(ValidationException::class); $this->actingAs($this->f['receiver']); $decision['version']=0; expect(fn()=>app(AutomaticPurchases::class)->save($this->f['receiver'],$decision))->toThrow(ValidationException::class); }); test('expired or changed sources keep the need pending without preventing the physical operation',function(){ activateAutomaticRule($this); $before=Requisition::query()->count(); $this->travel(32)->days(); automaticIssue($this,'120.000'); app(AutomaticPurchases::class)->evaluate($this->f['receiver'],$this->f['stock']->id,$this->f['warehouse']->id); expect(app(SupplyPlanning::class)->latest('auto_need')->sole()->state)->toBe('pending')->and(Requisition::query()->count())->toBe($before) ->and(app(StockWorkflow::class)->balance($this->f['stock']->id,$this->f['warehouse']->id)['quantity'])->toBe(0); }); test('configuration UI separates preparation review and read and does not generate purchases on GET',function(){ $before=Requisition::query()->count(); $this->get('/stock/reposicao')->assertOk()->assertInertia(fn(Assert $p)=>$p->component('stock/replenishment')->where('canPrepare',true)->where('canReview',false)->has('requesters',1)); $this->actingAs($this->f['receiver'])->get('/stock/reposicao')->assertOk()->assertInertia(fn(Assert $p)=>$p->where('canPrepare',false)->where('canReview',true)->has('requesters',0)); expect(Requisition::query()->count())->toBe($before); });