feat(policy): rule builder and mandatory dry-run sandbox
Task 09 shipped a powerful API that a non-technical clinic owner could not safely use. This closes that gap: activation now requires having seen what the rule actually does. - PolicySimulator runs a policy against real past appointments and writes nothing: evaluation works on facts (never entities), the whole run sits in a transaction rolled back and cleared in `finally`, and a test counts rows in five sensitive tables before and after - activate() now demands a simulation of the *same version* — a report for version 1 does not unlock version 2 - PolicyTemplateRegistry: six ready-made rules, so the common case never touches a raw condition - Severity from the affected ratio; 0% is a warning too, since a rule that changes nothing usually has a condition that never matches - An empty clinic still succeeds with a warning, otherwise a new clinic could never activate anything Admin: PoliciesPage, PolicyFormPage, PolicySimulationPage, and a PolicyConditionBuilder built entirely from GET /policy-schema — a test proves a field that exists only in the schema shows up with no frontend change, and that operators are filtered per field type. The schema response now carries per-field metadata (label, type, meaningful operators) so the form has one source of truth instead of two. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policy\Repository;
|
||||
|
||||
use App\Policy\Entity\Policy;
|
||||
use App\Policy\Entity\PolicySimulationRun;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<PolicySimulationRun>
|
||||
*/
|
||||
class PolicySimulationRunRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, PolicySimulationRun::class);
|
||||
}
|
||||
|
||||
/** آخرین اجرای آزمایشی این قانون، از هر نسخهای. */
|
||||
public function latestFor(Policy $policy): ?PolicySimulationRun
|
||||
{
|
||||
return $this->createQueryBuilder('r')
|
||||
->where('r.policy = :policy')
|
||||
->setParameter('policy', $policy)
|
||||
->orderBy('r.createdAt', 'DESC')
|
||||
->addOrderBy('r.id', 'DESC')
|
||||
->setMaxResults(1)
|
||||
->getQuery()
|
||||
->getOneOrNullResult();
|
||||
}
|
||||
|
||||
/** @return PolicySimulationRun[] */
|
||||
public function historyFor(Policy $policy, int $limit = 10): array
|
||||
{
|
||||
return $this->createQueryBuilder('r')
|
||||
->where('r.policy = :policy')
|
||||
->setParameter('policy', $policy)
|
||||
->orderBy('r.createdAt', 'DESC')
|
||||
->addOrderBy('r.id', 'DESC')
|
||||
->setMaxResults($limit)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
}
|
||||
|
||||
public function save(PolicySimulationRun $run): void
|
||||
{
|
||||
$em = $this->getEntityManager();
|
||||
$em->persist($run);
|
||||
$em->flush();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user