Close isolated direct cost trial by delegated technical acceptance
Preserve verified 275 MZN and incomplete 175 MZN demonstrations. Keep 090000 and four real cost grants unapplied; real costing and fiscal blocks remain intact.
This commit is contained in:
@@ -15,8 +15,8 @@ final class DirectCostsTest extends TestCase {
|
||||
protected function setUp(): void {parent::setUp();$this->f=DirectCostFixture::create();$this->login();}
|
||||
private function login(string $who='manager'): void {$this->postJson('/api/login',['email'=>$this->f[$who]->email,'password'=>$this->f['password']])->assertOk();$this->token=$this->postJson('/api/context',['tenant_id'=>$this->f['tenant'],'company_id'=>$this->f['company'],'unit_id'=>$this->f['unit']])->assertOk()->json('context.token');}
|
||||
private function send(string $action,array $d,?string $key=null,?int $order=null){return $this->withHeaders(['X-SGF-Context'=>$this->token,'Idempotency-Key'=>$key??(string)Str::uuid()])->postJson('/api/direct-costs/orders/'.($order??$this->f['order']).'/'.$action,$d);}
|
||||
private function rate(string $kind,string $amount='50',array $extra=[]){return $this->send('rates',array_replace(['entry_id'=>$this->f[$kind],'entry_version'=>1,'identifier'=>'TEST-'.$kind,'version'=>1,'previous_id'=>null,'currency'=>'MZN','unit'=>$kind==='time'?'H':'UN','amount'=>$amount,'evidence'=>'Taxa sintética, não custo real.'],$extra));}
|
||||
private function decide(string $kind,int $id,array $extra=[]){$r=DB::table($kind==='rate'?'dc_rates':'dc_calculations')->find($id);return $this->send('decisions',array_replace(['kind'=>$kind,'id'=>$id,'version'=>$r->version,'decision'=>'approved','reason'=>'Revisão sintética de custos e taxas.'],$extra));}
|
||||
private function rate(string $kind,string $amount='50',array $extra=[]){return $this->send('rates',array_replace(['entry_id'=>$this->f[$kind],'entry_version'=>1,'identifier'=>'TEST-'.$kind,'version'=>1,'previous_id'=>null,'currency'=>'MZN','unit'=>$kind==='time'?'H':'UN','amount'=>$amount,'evidence'=>'Taxa sintética, não custo real.'],$extra));}
|
||||
private function decide(string $kind,int $id,array $extra=[]){$r=DB::table($kind==='rate'?'dc_rates':'dc_calculations')->find($id);return $this->send('decisions',array_replace(['kind'=>$kind,'id'=>$id,'version'=>$r->version,'decision'=>'approved','reason'=>'Revisão sintética de custos e taxas.'],$extra));}
|
||||
private function rates(): array {$m=$this->rate('material')->assertOk()->json('id');$t=$this->rate('time','100')->assertOk()->json('id');$this->login('reader');$this->decide('rate',$m)->assertOk();$this->decide('rate',$t)->assertOk();$this->login();return [$m,$t];}
|
||||
private function input(?int $m,?int $t,array $extra=[]): array {return array_replace(['currency'=>'MZN','version'=>1,'previous_id'=>null,'lines'=>[['entry_id'=>$this->f['material'],'entry_version'=>1,'rate_id'=>$m,'rate_version'=>$m?1:null],['entry_id'=>$this->f['time'],'entry_version'=>1,'rate_id'=>$t,'rate_version'=>$t?1:null]]],$extra);}
|
||||
private function listing(){return $this->getJson('/api/direct-costs')->assertOk();}
|
||||
@@ -24,6 +24,7 @@ final class DirectCostsTest extends TestCase {
|
||||
public function test_exact_known_cost_and_distinct_validation_without_new_consumption(): void {
|
||||
$before=DB::table('wo_entries')->get()->toJson();[$m,$t]=$this->rates();$id=$this->send('calculations',$this->input($m,$t))->assertOk()->json('id');$s=$this->snapshot($id);
|
||||
$this->assertSame('275',$s['known_subtotal_exact']);$this->assertSame('275',$s['selected_direct_total_exact']);$this->assertFalse($s['incomplete']);$this->assertSame(['175','100'],array_column($s['lines'],'cost_exact'));$this->assertSame('1.75',$s['lines'][0]['cost_quantity_exact']);
|
||||
IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'direct_costs.validate',$this->f['unit']);
|
||||
$this->decide('calculation',$id)->assertForbidden();$this->login('reader');$this->decide('calculation',$id)->assertOk();$this->assertSame($before,DB::table('wo_entries')->get()->toJson());
|
||||
$this->assertSame(DB::table('dc_commands')->count(),DB::table('dc_events')->count());$this->assertSame(DB::table('dc_events')->count(),DB::table('dc_outbox')->count());
|
||||
}
|
||||
@@ -44,7 +45,7 @@ final class DirectCostsTest extends TestCase {
|
||||
foreach(['','1.000000001','-1','1e2','1,00'] as $value)$this->rate('material',$value)->assertStatus(422);
|
||||
$this->rate('material','50',['amount'=>50])->assertStatus(422);
|
||||
$e=DB::table('wo_entries')->find($this->f['material']);$d=json_decode($e->data,true);unset($d['policy']);$rev=(int)DB::selectOne('SELECT sgf_wo_revision(?) AS v',[$e->order_id])->v;
|
||||
app(CompanyContext::class)->selectFor($this->f['technician']->id,$this->f['tenant'],$this->f['company'],$this->f['unit']);app(WorkshopWorkflow::class)->record($e->order_id,$this->f['technician']->id,array_replace($d,['version'=>2,'previous_id'=>$e->id,'revision'=>$rev,'quantity'=>'2.1','correction_reason'=>'Correcção sintética de quantidade.']),(string)Str::uuid());$this->login();$this->rate('material')->assertStatus(409);$latest=DB::table('wo_entries')->where('previous_id',$e->id)->first();$this->rate('material','50',['entry_id'=>$latest->id,'entry_version'=>2])->assertStatus(409);
|
||||
app(CompanyContext::class)->selectFor($this->f['technician']->id,$this->f['tenant'],$this->f['company'],$this->f['unit']);app(WorkshopWorkflow::class)->record($e->order_id,$this->f['technician']->id,array_replace($d,['version'=>2,'previous_id'=>$e->id,'revision'=>$rev,'quantity'=>'2.1','correction_reason'=>'Correcção sintética de quantidade.']),(string)Str::uuid());$this->login();$this->rate('material')->assertStatus(409);$latest=DB::table('wo_entries')->where('previous_id',$e->id)->first();$this->rate('material','50',['entry_id'=>$latest->id,'entry_version'=>2])->assertStatus(409);
|
||||
}
|
||||
public function test_roles_scope_revocation_and_idempotent_response_recovery(): void {
|
||||
$key=(string)Str::uuid();$d=['entry_id'=>$this->f['material'],'entry_version'=>1,'identifier'=>'TEST-REPLAY','version'=>1,'previous_id'=>null,'currency'=>'MZN','unit'=>'UN','amount'=>'50','evidence'=>'TEST-REPLAY'];$id=$this->send('rates',$d,$key)->assertOk()->json('id');$this->send('rates',$d,$key)->assertOk()->assertJsonPath('replayed',true)->assertJsonPath('id',$id);$this->send('rates',array_replace($d,['amount'=>'51']),$key)->assertStatus(409);
|
||||
@@ -64,7 +65,7 @@ final class DirectCostsTest extends TestCase {
|
||||
$e=DB::table('wo_entries')->find($this->f['time']);$d=json_decode($e->data,true);unset($d['policy']);$d=array_replace($d,['version'=>2,'previous_id'=>$e->id,'revision'=>(int)DB::selectOne('SELECT sgf_wo_revision(?) AS v',[$e->order_id])->v,'ends_at'=>$this->f['day'].'T09:01:00+02:00','pauses'=>[],'correction_reason'=>'Um minuto no novo ensaio exacto.']);
|
||||
app(CompanyContext::class)->selectFor($this->f['technician']->id,$this->f['tenant'],$this->f['company'],$this->f['unit']);$w=app(WorkshopWorkflow::class);$next=$w->record($e->order_id,$this->f['technician']->id,$d,(string)Str::uuid())['id'];
|
||||
$this->login('reader');$this->decide('calculation',$id)->assertStatus(409);
|
||||
app(CompanyContext::class)->selectFor($this->f['reviewer']->id,$this->f['tenant'],$this->f['company'],$this->f['unit']);$w->verify($next,$this->f['reviewer']->id,['version'=>2,'revision'=>(int)DB::selectOne('SELECT sgf_wo_revision(?) AS v',[$e->order_id])->v,'decision'=>'verified','reason'=>'Verificação técnica da correcção.'],(string)Str::uuid());$this->login();
|
||||
app(CompanyContext::class)->selectFor($this->f['reviewer']->id,$this->f['tenant'],$this->f['company'],$this->f['unit']);$w->verify($next,$this->f['reviewer']->id,['version'=>2,'revision'=>(int)DB::selectOne('SELECT sgf_wo_revision(?) AS v',[$e->order_id])->v,'decision'=>'verified','reason'=>'Verificação técnica da correcção.'],(string)Str::uuid());$this->login();
|
||||
$newRate=$this->rate('time','1',['entry_id'=>$next,'entry_version'=>2,'version'=>2,'previous_id'=>$t])->assertOk()->json('id');$this->login('reader');$this->decide('rate',$newRate)->assertOk();$this->login();$in=$this->input(null,$newRate,['version'=>2,'previous_id'=>$id]);$in['lines'][1]=['entry_id'=>$next,'entry_version'=>2,'rate_id'=>$newRate,'rate_version'=>2];$result=$this->send('calculations',$in)->assertOk()->json('id');$this->assertSame('1/60',$this->snapshot($result)['known_subtotal_exact']);$this->assertSame($original,DB::table('dc_calculations')->find($id)->snapshot);
|
||||
// Own tenant does not imply sibling company or hidden unit access.
|
||||
IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'unit.view',$this->f['hidden']);IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'direct_costs.view',$this->f['hidden']);IdentityFixture::grant($this->f['manager']->id,$this->f['tenant'],$this->f['company'],'direct_costs.prepare',$this->f['hidden']);
|
||||
|
||||
Reference in New Issue
Block a user