Files
clinicpro/src/Policy/Repository/PolicySimulationRunRepository.php
T
hamedandClaude Opus 5 bcfa87bfad 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>
2026-07-31 10:44:28 +03:30

53 lines
1.5 KiB
PHP

<?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();
}
}