diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php index e170a362a..10d02749a 100644 --- a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepository.php @@ -14,9 +14,8 @@ namespace Chill\BudgetBundle\Repository; use Chill\BudgetBundle\Entity\ChargeKind; use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityRepository; -use Doctrine\Persistence\ObjectRepository; -class ChargeKindRepository implements ObjectRepository +final class ChargeKindRepository implements ChargeKindRepositoryInterface { private EntityRepository $repository; @@ -50,7 +49,8 @@ class ChargeKindRepository implements ObjectRepository ->where($qb->expr()->eq('c.isActive', 'true')) ->orderBy('c.ordering', 'ASC') ->getQuery() - ->getResult(); + ->getResult() + ; } /** @@ -77,6 +77,11 @@ class ChargeKindRepository implements ObjectRepository return $this->repository->findOneBy($criteria); } + public function findOneByKind(string $kind): ?ChargeKind + { + return $this->repository->findOneBy(['kind' => $kind]); + } + public function getClassName(): string { return ChargeKind::class; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php new file mode 100644 index 000000000..5099a5674 --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ChargeKindRepositoryInterface.php @@ -0,0 +1,49 @@ +where($qb->expr()->eq('r.isActive', 'true')) ->orderBy('r.ordering', 'ASC') ->getQuery() - ->getResult(); + ->getResult() + ; } public function findOneByKind(string $kind): ?ResourceKind @@ -82,6 +82,11 @@ class ResourceKindRepository implements ObjectRepository return $this->repository->findOneBy($criteria); } + public function findOneByKind(string $kind): ?ResourceKind + { + return $this->repository->findOneBy(['kind' => $kind]); + } + public function getClassName(): string { return ResourceKind::class; diff --git a/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php new file mode 100644 index 000000000..658a87a3d --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Repository/ResourceKindRepositoryInterface.php @@ -0,0 +1,49 @@ +andWhere('c.startDate < :date') // TODO: there is a misconception here, the end date must be lower or null. startDate are never null //->andWhere('c.startDate < :date OR c.startDate IS NULL'); -; + ; if (null !== $sort) { $qb->orderBy($sort); diff --git a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php index 096531c52..2f401f1ec 100644 --- a/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php +++ b/src/Bundle/ChillBudgetBundle/Service/Summary/SummaryBudget.php @@ -14,7 +14,9 @@ namespace Chill\BudgetBundle\Service\Summary; use Chill\BudgetBundle\Entity\ChargeKind; use Chill\BudgetBundle\Entity\ResourceKind; use Chill\BudgetBundle\Repository\ChargeKindRepository; +use Chill\BudgetBundle\Repository\ChargeKindRepositoryInterface; use Chill\BudgetBundle\Repository\ResourceKindRepository; +use Chill\BudgetBundle\Repository\ResourceKindRepositoryInterface; use Chill\MainBundle\Templating\TranslatableStringHelperInterface; use Chill\PersonBundle\Entity\Household\Household; use Chill\PersonBundle\Entity\Person; @@ -27,7 +29,7 @@ use function count; /** * Helps to find a summary of the budget: the sum of resources and charges. */ -class SummaryBudget implements SummaryBudgetInterface +final class SummaryBudget implements SummaryBudgetInterface { private const QUERY_CHARGE_BY_HOUSEHOLD = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, charge_id AS kind_id FROM chill_budget.charge WHERE (person_id IN (_ids_) OR household_id = ?) AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY charge_id'; @@ -37,23 +39,19 @@ class SummaryBudget implements SummaryBudgetInterface private const QUERY_RESOURCE_BY_PERSON = 'select SUM(amount) AS sum, string_agg(comment, \'|\') AS comment, resource_id AS kind_id FROM chill_budget.resource WHERE person_id = ? AND NOW() BETWEEN startdate AND COALESCE(enddate, \'infinity\'::timestamp) GROUP BY resource_id'; - private ChargeKindRepository $chargeKindRepository; - - private array $chargeLabels; + private ChargeKindRepositoryInterface $chargeKindRepository; private EntityManagerInterface $em; - private ResourceKindRepository $resourceKindRepository; - - private array $resourcesLabels; + private ResourceKindRepositoryInterface $resourceKindRepository; private TranslatableStringHelperInterface $translatableStringHelper; public function __construct( EntityManagerInterface $em, TranslatableStringHelperInterface $translatableStringHelper, - ResourceKindRepository $resourceKindRepository, - ChargeKindRepository $chargeKindRepository + ResourceKindRepositoryInterface $resourceKindRepository, + ChargeKindRepositoryInterface $chargeKindRepository ) { $this->em = $em; $this->translatableStringHelper = $translatableStringHelper; @@ -129,19 +127,19 @@ class SummaryBudget implements SummaryBudgetInterface private function getEmptyChargeArray(): array { - $keys = array_map(static fn (ChargeKind $kind) => $kind->getId(), $this->chargeKindRepository->findAll()); + $keys = array_map(static fn (ChargeKind $kind) => $kind->getKind(), $this->chargeKindRepository->findAll()); - return array_combine($keys, array_map(function ($id) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->find($id)->getName()), 'comment' => '']; + return array_combine($keys, array_map(function ($kind) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->chargeKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; }, $keys)); } private function getEmptyResourceArray(): array { - $keys = array_map(static fn (ResourceKind $kind) => $kind->getId(), $this->resourceKindRepository->findAll()); + $keys = array_map(static fn (ResourceKind $kind) => $kind->getKind(), $this->resourceKindRepository->findAll()); - return array_combine($keys, array_map(function ($id) { - return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->find($id)->getName()), 'comment' => '']; + return array_combine($keys, array_map(function ($kind) { + return ['sum' => 0.0, 'label' => $this->translatableStringHelper->localize($this->resourceKindRepository->findOneByKind($kind)->getName()), 'comment' => '']; }, $keys)); } @@ -155,7 +153,7 @@ class SummaryBudget implements SummaryBudgetInterface $chargeKind = $this->chargeKindRepository->find($row['kind_id']); if (null === $chargeKind) { - throw new RuntimeException('charge kind not found'); + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); } $result[$chargeKind->getKind()] = [ 'sum' => (float) $row['sum'], @@ -171,7 +169,7 @@ class SummaryBudget implements SummaryBudgetInterface $resourceKind = $this->resourceKindRepository->find($row['kind_id']); if (null === $resourceKind) { - throw new RuntimeException('charge kind not found'); + throw new RuntimeException('charge kind not found: ' . $row['kind_id']); } $result[$resourceKind->getKind()] = [ diff --git a/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php new file mode 100644 index 000000000..cf8c00efe --- /dev/null +++ b/src/Bundle/ChillBudgetBundle/Tests/Service/Summary/SummaryBudgetTest.php @@ -0,0 +1,158 @@ +prophesize(AbstractQuery::class); + $queryCharges->getResult()->willReturn([ + [ + 'sum' => 250.0, + 'comment' => '', + 'kind_id' => 1, // kind: rental + ], + ]); + $queryCharges->setParameters(Argument::type('array')) + ->will(function ($args, $query) { + return $query; + }) + ; + + $queryResources = $this->prophesize(AbstractQuery::class); + $queryResources->getResult()->willReturn([ + [ + 'sum' => 1500.0, + 'comment' => '', + 'kind_id' => 2, // kind: 'salary', + ], + ]); + $queryResources->setParameters(Argument::type('array')) + ->will(function ($args, $query) { + return $query; + }) + ; + + $em = $this->prophesize(EntityManagerInterface::class); + $em->createNativeQuery(Argument::type('string'), Argument::type(Query\ResultSetMapping::class)) + ->will(function ($args) use ($queryResources, $queryCharges) { + if (false !== strpos($args[0], 'chill_budget.resource')) { + return $queryResources->reveal(); + } + if (false !== strpos($args[0], 'chill_budget.charge')) { + return $queryCharges->reveal(); + } + throw new \RuntimeException('this query does not have a stub counterpart: '.$args[0]); + }) + ; + + $chargeRepository = $this->prophesize(ChargeKindRepositoryInterface::class); + $chargeRepository->findAll()->willReturn([ + $rental = (new ChargeKind())->setKind('rental')->setName(['fr' => 'Rental']), + $other = (new ChargeKind())->setKind('other')->setName(['fr' => 'Other']), + ]); + $chargeRepository->find(1)->willReturn($rental); + $chargeRepository->findOneByKind('rental')->willReturn($rental); + $chargeRepository->findOneByKind('other')->willReturn($other); + + $resourceRepository = $this->prophesize(ResourceKindRepositoryInterface::class); + $resourceRepository->findAll()->willReturn([ + $salary = (new ResourceKind())->setKind('salary')->setName(['fr' => 'Salary']), + $misc = (new ResourceKind())->setKind('misc')->setName(['fr' => 'Misc']), + ]); + $resourceRepository->find(2)->willReturn($salary); + $resourceRepository->findOneByKind('salary')->willReturn($salary); + $resourceRepository->findOneByKind('misc')->willReturn($misc); + + $translatableStringHelper = $this->prophesize(TranslatableStringHelperInterface::class); + $translatableStringHelper->localize(Argument::type('array'))->will(function ($arg) { + return $arg[0]['fr']; + }); + + $person = new Person(); + $personReflection = new \ReflectionClass($person); + $personIdReflection = $personReflection->getProperty('id'); + $personIdReflection->setAccessible(true); + $personIdReflection->setValue($person, 1); + + $household = new Household(); + $householdReflection = new \ReflectionClass($household); + $householdId = $householdReflection->getProperty('id'); + $householdId->setAccessible(true); + $householdId->setValue($household, 1); + $householdMember = (new HouseholdMember())->setPerson($person) + ->setStartDate(new \DateTimeImmutable('1 month ago')) + ; + $household->addMember($householdMember); + + $summaryBudget = new SummaryBudget( + $em->reveal(), + $translatableStringHelper->reveal(), + $resourceRepository->reveal(), + $chargeRepository->reveal() + ); + + $summary = $summaryBudget->getSummaryForPerson($person); + $summaryForHousehold = $summaryBudget->getSummaryForHousehold($household); + + // we check the structure for the summary. The structure is the same for household + // and persons + + $expected = [ + 'charges' => [ + 'rental' => ['sum' => 250.0, 'comment' => '', 'label' => 'Rental'], + 'other' => ['sum' => 0.0, 'comment' => '', 'label' => 'Other'], + ], + 'resources' => [ + 'salary' => ['sum' => 1500.0, 'comment' => '', 'label' => 'Salary'], + 'misc' => ['sum' => 0.0, 'comment' => '', 'label' => 'Misc'], + ], + ]; + + foreach ([$summaryForHousehold, $summary] as $summary) { + $this->assertIsArray($summary); + $this->assertEqualsCanonicalizing(['charges', 'resources'], array_keys($summary)); + $this->assertEqualsCanonicalizing(['rental', 'other'], array_keys($summary['charges'])); + $this->assertEqualsCanonicalizing(['salary', 'misc'], array_keys($summary['resources'])); + + foreach ($expected as $resCha => $contains) { + foreach ($contains as $kind => $row) { + $this->assertEqualsCanonicalizing($row, $summary[$resCha][$kind]); + } + } + } + } +} diff --git a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php index e54c4ae34..d618f758a 100644 --- a/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php +++ b/src/Bundle/ChillDocGeneratorBundle/Controller/DocGeneratorTemplateController.php @@ -272,8 +272,9 @@ final class DocGeneratorTemplateController extends AbstractController } if ($isTest && isset($form) && $form['show_data']->getData()) { - // very ugly hack... - dd($context->getData($template, $entity, $contextGenerationData)); + return $this->render('@ChillDocGenerator/Generator/debug_value.html.twig', [ + 'datas' => json_encode($context->getData($template, $entity, $contextGenerationData), JSON_PRETTY_PRINT) + ]); } try { diff --git a/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig new file mode 100644 index 000000000..c08a05c96 --- /dev/null +++ b/src/Bundle/ChillDocGeneratorBundle/Resources/views/Generator/debug_value.html.twig @@ -0,0 +1,8 @@ + +
+{{ datas }}+ + diff --git a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue index cb72fa5cd..60b368cd3 100644 --- a/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue +++ b/src/Bundle/ChillDocStoreBundle/Resources/public/vuejs/DocumentActionButtonsGroup.vue @@ -7,7 +7,7 @@