feat: import work hours from SaaS and exchange signed time sheet
This commit is contained in:
1 parent
7cf39850e6
commit
1a7c272414
73 files changed
+10488
-72
No files matched your search
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Service\Pdf;
|
||||
|
||||
use App\Entity\Teamer;
|
||||
use App\Entity\TeamerWorkMonth;
|
||||
use App\Model\WorkMonthDays;
|
||||
use App\Service\Pdf\WorkMonthRenderer;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Twig\Environment;
|
||||
|
||||
/**
|
||||
* Renders a real document rather than mocking Twig: the point of the test is that the
|
||||
* template, the duration filter and Dompdf agree with each other.
|
||||
*/
|
||||
class WorkMonthRendererTest extends WebTestCase
|
||||
{
|
||||
public function testItRendersAPdf(): void
|
||||
{
|
||||
$pdf = $this->render($this->createWorkMonth());
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $pdf);
|
||||
$this->assertGreaterThan(2000, strlen($pdf), 'A month of rows should not fit in a stub.');
|
||||
}
|
||||
|
||||
/**
|
||||
* A month whose days the SaaS never reported still has to produce a full sheet, so the
|
||||
* teamer can write on the empty lines.
|
||||
*/
|
||||
public function testItPlacesTheLogoInTheTopRightCorner(): void
|
||||
{
|
||||
$pdf = $this->render($this->createWorkMonth());
|
||||
|
||||
// A4 is 595.28 x 841.89pt and the page margins are 15mm at the side, 18mm at the top,
|
||||
// so the content corner sits at 552.76 / 790.87. Asserting the placement rather than
|
||||
// the markup is the only way to know the logo actually landed there.
|
||||
$this->assertSame(1, preg_match(
|
||||
'#([\d.]+) 0 0 ([\d.]+) ([\d.]+) ([\d.]+) cm\s*/\w+ Do#',
|
||||
$this->contentStreams($pdf),
|
||||
$placement
|
||||
), 'The logo should be drawn exactly once.');
|
||||
|
||||
[, $width, $height, $x, $y] = array_map('floatval', $placement);
|
||||
|
||||
$this->assertEqualsWithDelta(552.76, $x + $width, 1.0, 'Flush with the right margin.');
|
||||
$this->assertEqualsWithDelta(790.87, $y + $height, 1.0, 'Flush with the top margin.');
|
||||
$this->assertEqualsWithDelta(230 / 70, $width / $height, 0.05, 'The logo must not be stretched.');
|
||||
}
|
||||
|
||||
/**
|
||||
* A working-time record is worth printing without its letterhead.
|
||||
*/
|
||||
public function testItRendersWithoutALogo(): void
|
||||
{
|
||||
$renderer = new WorkMonthRenderer(
|
||||
static::getContainer()->get(Environment::class),
|
||||
['project_dir' => sys_get_temp_dir()],
|
||||
);
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $renderer->render($this->createWorkMonth()));
|
||||
}
|
||||
|
||||
private function contentStreams(string $pdf): string
|
||||
{
|
||||
preg_match_all('#stream\r?\n(.*?)endstream#s', $pdf, $matches);
|
||||
|
||||
$decoded = '';
|
||||
|
||||
foreach ($matches[1] as $stream) {
|
||||
$inflated = @gzuncompress(ltrim($stream, "\r\n"));
|
||||
|
||||
if (false !== $inflated) {
|
||||
$decoded .= $inflated;
|
||||
}
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
public function testItRendersAMonthWithoutAnyBookings(): void
|
||||
{
|
||||
$workMonth = (new TeamerWorkMonth())
|
||||
->setTeamer($this->createTeamer())
|
||||
->setYear(2026)
|
||||
->setMonth(2)
|
||||
->setDays(WorkMonthDays::fromArray([]))
|
||||
;
|
||||
|
||||
$this->assertStringStartsWith('%PDF', $this->render($workMonth));
|
||||
}
|
||||
|
||||
public function testTheFilenameCarriesTheTeamerAndTheMonth(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
'Arbeitszeitnachweis_Test_2026-03.pdf',
|
||||
$this->createRenderer()->filename($this->createWorkMonth()),
|
||||
);
|
||||
}
|
||||
|
||||
public function testTheFilenameSurvivesAnAwkwardName(): void
|
||||
{
|
||||
$renderer = $this->createRenderer();
|
||||
$workMonth = $this->createWorkMonth();
|
||||
$workMonth->getTeamer()->setLastName('Müller-Lüdenscheidt');
|
||||
|
||||
$this->assertSame('Arbeitszeitnachweis_M_ller_L_denscheidt_2026-03.pdf', $renderer->filename($workMonth));
|
||||
}
|
||||
|
||||
private function render(TeamerWorkMonth $workMonth): string
|
||||
{
|
||||
return $this->createRenderer()->render($workMonth);
|
||||
}
|
||||
|
||||
private function createRenderer(): WorkMonthRenderer
|
||||
{
|
||||
$projectDir = static::getContainer()->getParameter('kernel.project_dir');
|
||||
|
||||
/** @var Environment $twig */
|
||||
$twig = static::getContainer()->get(Environment::class);
|
||||
|
||||
return new WorkMonthRenderer($twig, ['project_dir' => $projectDir]);
|
||||
}
|
||||
|
||||
private function createWorkMonth(): TeamerWorkMonth
|
||||
{
|
||||
return (new TeamerWorkMonth())
|
||||
->setTeamer($this->createTeamer())
|
||||
->setYear(2026)
|
||||
->setMonth(3)
|
||||
->setDays(WorkMonthDays::fromArray([
|
||||
'2026-03-01' => ['minutes' => 285],
|
||||
'2026-03-05' => ['minutes' => 0, 'absence' => 'Ruhetag 24/25'],
|
||||
'2026-03-12' => ['minutes' => 510],
|
||||
]))
|
||||
->setSourceIstMinutes(795)
|
||||
;
|
||||
}
|
||||
|
||||
private function createTeamer(): Teamer
|
||||
{
|
||||
return (new Teamer())->setFirstName('Theo')->setLastName('Test');
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user