Compare commits

...

9 Commits

Author SHA1 Message Date
gitea-actions
3994be6556 chore: bump version to v0.1.24
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m8s
2026-03-11 12:53:39 +00:00
f46eeaa893 fix : prise en compte des jours de congés sur l'année N-1 même si on a pas d'historique de contrat sur l'année N-1
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
2026-03-11 13:53:29 +01:00
gitea-actions
eb703272c7 chore: bump version to v0.1.23
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m11s
2026-03-11 10:41:38 +00:00
6629eb98cb fix : on ne prend plus en compte les jour de congé du samedi et dimanche pour les forfaits dans le calcule des RTT
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
2026-03-11 11:41:26 +01:00
gitea-actions
029bc03a5a chore: bump version to v0.1.22
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m12s
2026-03-11 09:05:51 +00:00
82e575fff0 fix : plus de date de fin obligatoire sur les contrats interim
All checks were successful
Auto Tag Develop / tag (push) Successful in 5s
2026-03-11 10:05:41 +01:00
gitea-actions
0213c0a97d chore: bump version to v0.1.21
All checks were successful
Auto Tag Develop / tag (push) Successful in 4s
Build Release Artefact / build (push) Successful in 1m11s
2026-03-11 07:50:38 +00:00
12def35dda Merge remote-tracking branch 'origin/develop' into develop
Some checks failed
Auto Tag Develop / tag (push) Has been cancelled
2026-03-11 08:50:23 +01:00
2d1c1e6e22 fix : api jour férié qui a changé d'url 2026-03-11 08:42:57 +01:00
11 changed files with 84 additions and 36 deletions

View File

@@ -6,7 +6,8 @@
"Bash(php:*)", "Bash(php:*)",
"Bash(docker compose:*)", "Bash(docker compose:*)",
"Bash(make test:*)", "Bash(make test:*)",
"Bash(grep:*)" "Bash(grep:*)",
"Bash(docker exec:*)"
] ]
} }
} }

View File

@@ -22,6 +22,10 @@ services:
App\: App\:
resource: '../src/' resource: '../src/'
App\Service\PublicHolidayService:
arguments:
$holidayUrl: '%env(HOLIDAY_URL)%'
App\Repository\Contract\AbsenceReadRepositoryInterface: '@App\Repository\AbsenceRepository' App\Repository\Contract\AbsenceReadRepositoryInterface: '@App\Repository\AbsenceRepository'
App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface: '@App\Repository\EmployeeContractPeriodRepository' App\Repository\Contract\EmployeeContractPeriodReadRepositoryInterface: '@App\Repository\EmployeeContractPeriodRepository'
App\Repository\Contract\EmployeeScopedRepositoryInterface: '@App\Repository\EmployeeRepository' App\Repository\Contract\EmployeeScopedRepositoryInterface: '@App\Repository\EmployeeRepository'

View File

@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.1.20' app.version: '0.1.24'

View File

@@ -162,9 +162,9 @@
<input id="create-contract-start-date" v-model="createContractForm.startDate" type="date" :class="createContractStartDateFieldClass" /> <input id="create-contract-start-date" v-model="createContractForm.startDate" type="date" :class="createContractStartDateFieldClass" />
</div> </div>
<div v-if="requiresCreateContractEndDate"> <div v-if="showsCreateContractEndDate">
<label class="text-md font-semibold text-neutral-700" for="create-contract-end-date"> <label class="text-md font-semibold text-neutral-700" for="create-contract-end-date">
Fin contrat <span class="text-red-600">*</span> Fin contrat <span v-if="requiresCreateContractEndDate" class="text-red-600">*</span>
</label> </label>
<input id="create-contract-end-date" v-model="createContractForm.endDate" type="date" :class="createContractEndDateFieldClass" /> <input id="create-contract-end-date" v-model="createContractForm.endDate" type="date" :class="createContractEndDateFieldClass" />
</div> </div>
@@ -235,6 +235,7 @@ defineProps<{
createContractNatureFieldClass: string createContractNatureFieldClass: string
createContractFieldClass: string createContractFieldClass: string
createContractStartDateFieldClass: string createContractStartDateFieldClass: string
showsCreateContractEndDate: boolean
requiresCreateContractEndDate: boolean requiresCreateContractEndDate: boolean
createContractEndDateFieldClass: string createContractEndDateFieldClass: string
isCreateContractFormValid: boolean isCreateContractFormValid: boolean

View File

@@ -11,7 +11,7 @@ import { getEmployeeRttSummary, createRttPayment } from '~/services/employee-rtt
import { getEmployee, updateEmployee } from '~/services/employees' import { getEmployee, updateEmployee } from '~/services/employees'
import { listPublicHolidays } from '~/services/public-holidays' import { listPublicHolidays } from '~/services/public-holidays'
import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date' import { formatNullableYmdToFr, getTodayYmd, shiftYmd } from '~/utils/date'
import { contractNatureLabel, isContractNature, requiresContractEndDate } from '~/utils/contract' import { contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate } from '~/utils/contract'
export const useEmployeeDetailPage = () => { export const useEmployeeDetailPage = () => {
const route = useRoute() const route = useRoute()
@@ -99,6 +99,7 @@ export const useEmployeeDetailPage = () => {
const isContractEndDateValid = computed(() => contractForm.endDate !== '') const isContractEndDateValid = computed(() => contractForm.endDate !== '')
const showContractEndDateError = computed(() => validationTouched.endDate && !isContractEndDateValid.value) const showContractEndDateError = computed(() => validationTouched.endDate && !isContractEndDateValid.value)
const showsCreateContractEndDate = computed(() => showsContractEndDate(createContractForm.contractNature))
const requiresCreateContractEndDate = computed(() => requiresContractEndDate(createContractForm.contractNature)) const requiresCreateContractEndDate = computed(() => requiresContractEndDate(createContractForm.contractNature))
const isCreateContractValid = computed(() => createContractForm.contractId !== '') const isCreateContractValid = computed(() => createContractForm.contractId !== '')
const isCreateContractNatureValid = computed(() => isContractNature(createContractForm.contractNature)) const isCreateContractNatureValid = computed(() => isContractNature(createContractForm.contractNature))
@@ -314,8 +315,8 @@ export const useEmployeeDetailPage = () => {
await loadEmployee() await loadEmployee()
} }
watch(requiresCreateContractEndDate, (required) => { watch(showsCreateContractEndDate, (shows) => {
if (!required) { if (!shows) {
createContractForm.endDate = '' createContractForm.endDate = ''
} }
}) })
@@ -353,6 +354,7 @@ export const useEmployeeDetailPage = () => {
createContractNatureFieldClass, createContractNatureFieldClass,
createContractFieldClass, createContractFieldClass,
createContractStartDateFieldClass, createContractStartDateFieldClass,
showsCreateContractEndDate,
requiresCreateContractEndDate, requiresCreateContractEndDate,
createContractEndDateFieldClass, createContractEndDateFieldClass,
isCreateContractFormValid, isCreateContractFormValid,

View File

@@ -78,6 +78,7 @@
:create-contract-nature-field-class="createContractNatureFieldClass" :create-contract-nature-field-class="createContractNatureFieldClass"
:create-contract-field-class="createContractFieldClass" :create-contract-field-class="createContractFieldClass"
:create-contract-start-date-field-class="createContractStartDateFieldClass" :create-contract-start-date-field-class="createContractStartDateFieldClass"
:shows-create-contract-end-date="showsCreateContractEndDate"
:requires-create-contract-end-date="requiresCreateContractEndDate" :requires-create-contract-end-date="requiresCreateContractEndDate"
:create-contract-end-date-field-class="createContractEndDateFieldClass" :create-contract-end-date-field-class="createContractEndDateFieldClass"
:is-create-contract-form-valid="isCreateContractFormValid" :is-create-contract-form-valid="isCreateContractFormValid"
@@ -131,6 +132,7 @@ const {
createContractNatureFieldClass, createContractNatureFieldClass,
createContractFieldClass, createContractFieldClass,
createContractStartDateFieldClass, createContractStartDateFieldClass,
showsCreateContractEndDate,
requiresCreateContractEndDate, requiresCreateContractEndDate,
createContractEndDateFieldClass, createContractEndDateFieldClass,
isCreateContractFormValid, isCreateContractFormValid,

View File

@@ -154,7 +154,7 @@
La date de début est obligatoire. La date de début est obligatoire.
</p> </p>
</div> </div>
<div v-if="requiresContractEndDateComputed"> <div v-if="showsContractEndDateComputed">
<label class="text-md font-semibold text-neutral-700" for="contract-end-date"> <label class="text-md font-semibold text-neutral-700" for="contract-end-date">
Fin contrat Fin contrat
<span v-if="requiresContractEndDateComputed" class="text-red-600">*</span> <span v-if="requiresContractEndDateComputed" class="text-red-600">*</span>
@@ -166,7 +166,7 @@
:class="contractEndDateFieldClass" :class="contractEndDateFieldClass"
/> />
<p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600"> <p v-if="showContractEndDateError" class="mt-1 text-sm text-red-600">
La date de fin est obligatoire pour un CDD ou un intérim. La date de fin est obligatoire pour un CDD.
</p> </p>
</div> </div>
</template> </template>
@@ -199,7 +199,7 @@ import {listContracts} from '~/services/contracts'
import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees' import {createEmployee, deleteEmployee, listEmployees, updateEmployee} from '~/services/employees'
import {listSites} from '~/services/sites' import {listSites} from '~/services/sites'
import SiteFilterSelector from '~/components/SiteFilterSelector.vue' import SiteFilterSelector from '~/components/SiteFilterSelector.vue'
import {contractNatureLabel, isContractNature, requiresContractEndDate} from '~/utils/contract' import {contractNatureLabel, isContractNature, requiresContractEndDate, showsContractEndDate} from '~/utils/contract'
useHead({ useHead({
title: 'Employés' title: 'Employés'
@@ -264,6 +264,7 @@ const isSiteValid = computed(() => form.siteId !== '')
const isContractValid = computed(() => form.contractId !== '') const isContractValid = computed(() => form.contractId !== '')
const isContractNatureValid = computed(() => isContractNature(form.contractNature)) const isContractNatureValid = computed(() => isContractNature(form.contractNature))
const isContractStartDateValid = computed(() => form.contractStartDate !== '') const isContractStartDateValid = computed(() => form.contractStartDate !== '')
const showsContractEndDateComputed = computed(() => showsContractEndDate(form.contractNature))
const requiresContractEndDateComputed = computed(() => requiresContractEndDate(form.contractNature)) const requiresContractEndDateComputed = computed(() => requiresContractEndDate(form.contractNature))
const isContractEndDateValid = computed(() => { const isContractEndDateValid = computed(() => {
if (!requiresContractEndDateComputed.value) return true if (!requiresContractEndDateComputed.value) return true
@@ -429,7 +430,7 @@ const handleSubmit = async () => {
contractId: Number(form.contractId), contractId: Number(form.contractId),
contractNature: form.contractNature, contractNature: form.contractNature,
contractStartDate: form.contractStartDate, contractStartDate: form.contractStartDate,
contractEndDate: requiresContractEndDateComputed.value ? form.contractEndDate : null contractEndDate: form.contractEndDate || null
}) })
} }
@@ -460,8 +461,8 @@ watch(isDrawerOpen, (isOpen) => {
} }
}) })
watch(requiresContractEndDateComputed, (required) => { watch(showsContractEndDateComputed, (shows) => {
if (!required) { if (!shows) {
form.contractEndDate = '' form.contractEndDate = ''
} }
}) })

View File

@@ -8,10 +8,14 @@ export const contractNatureLabel = (value?: ContractNature) => {
return 'CDI' return 'CDI'
} }
export const requiresContractEndDate = (nature: ContractNature) => { export const showsContractEndDate = (nature: ContractNature) => {
return nature === 'CDD' || nature === 'INTERIM' return nature === 'CDD' || nature === 'INTERIM'
} }
export const requiresContractEndDate = (nature: ContractNature) => {
return nature === 'CDD'
}
export const isContractNature = (value: string): value is ContractNature => { export const isContractNature = (value: string): value is ContractNature => {
return (CONTRACT_NATURES as readonly string[]).includes(value) return (CONTRACT_NATURES as readonly string[]).includes(value)
} }

View File

@@ -349,14 +349,25 @@ final readonly class LeaveBalanceComputationService
} }
for ($cursor = $rangeStart; $cursor <= $rangeEnd; $cursor = $cursor->modify('+1 day')) { for ($cursor = $rangeStart; $cursor <= $rangeEnd; $cursor = $cursor->modify('+1 day')) {
$dayOfWeek = (int) $cursor->format('N');
if ($splitSaturdays) {
if (7 === $dayOfWeek) {
continue;
}
} else {
if ($dayOfWeek >= 6) {
continue;
}
}
[$am, $pm] = $this->resolveSegmentsForDate($absence, $cursor->format('Y-m-d')); [$am, $pm] = $this->resolveSegmentsForDate($absence, $cursor->format('Y-m-d'));
$dayAmount = ($am ? 0.5 : 0.0) + ($pm ? 0.5 : 0.0); $dayAmount = ($am ? 0.5 : 0.0) + ($pm ? 0.5 : 0.0);
if ($dayAmount <= 0.0) { if ($dayAmount <= 0.0) {
continue; continue;
} }
$isSaturday = $splitSaturdays && '6' === $cursor->format('N'); if ($splitSaturdays && 6 === $dayOfWeek) {
if ($isSaturday) {
$takenSaturdays += $dayAmount; $takenSaturdays += $dayAmount;
} else { } else {
$takenDays += $dayAmount; $takenDays += $dayAmount;

View File

@@ -13,12 +13,11 @@ use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Throwable; use Throwable;
final class PublicHolidayService implements PublicHolidayServiceInterface final readonly class PublicHolidayService implements PublicHolidayServiceInterface
{ {
private const string BASE_URL = 'https://calendrier.api.gouv.fr/jours-feries/';
public function __construct( public function __construct(
private readonly HttpClientInterface $client, private HttpClientInterface $client,
private string $holidayUrl
) {} ) {}
/** /**
@@ -31,7 +30,7 @@ final class PublicHolidayService implements PublicHolidayServiceInterface
public function getHolidaysDay(string $zone): array public function getHolidaysDay(string $zone): array
{ {
$zone = strtolower(trim($zone)); $zone = strtolower(trim($zone));
$url = self::BASE_URL."{$zone}.json"; $url = $this->holidayUrl."{$zone}.json";
try { try {
$response = $this->client->request( $response = $this->client->request(
@@ -61,7 +60,7 @@ final class PublicHolidayService implements PublicHolidayServiceInterface
{ {
$zone = strtolower(trim($zone)); $zone = strtolower(trim($zone));
$years = trim($years); $years = trim($years);
$url = self::BASE_URL."{$zone}/{$years}.json"; $url = $this->holidayUrl."{$zone}/{$years}.json";
try { try {
$response = $this->client->request('GET', $url); $response = $this->client->request('GET', $url);

View File

@@ -163,12 +163,12 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
$effectiveFrom = $this->resolveEffectivePeriodStart($employee, $from, $to); $effectiveFrom = $this->resolveEffectivePeriodStart($employee, $from, $to);
$hasShiftedStart = $effectiveFrom > $from; $hasShiftedStart = $effectiveFrom > $from;
if ($hasShiftedStart) { if ($hasShiftedStart && null === $openingBalance) {
$carryDays = 0.0; $carryDays = 0.0;
$carrySaturdays = 0.0; $carrySaturdays = 0.0;
} }
$calculationEnd = $this->resolveCalculationEndDate($leavePolicy['ruleCode'], $year, $to); $calculationEnd = $this->resolveCalculationEndDate($leavePolicy['ruleCode'], $year, $to, $employee);
$generatedDays = $leavePolicy['accrualPerMonth'] > 0.0 $generatedDays = $leavePolicy['accrualPerMonth'] > 0.0
? $this->computeAccruedDaysFromStart( ? $this->computeAccruedDaysFromStart(
$leavePolicy['acquiredDays'], $leavePolicy['acquiredDays'],
@@ -356,7 +356,8 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
private function resolveCalculationEndDate( private function resolveCalculationEndDate(
string $ruleCode, string $ruleCode,
int $year, int $year,
DateTimeImmutable $periodEnd DateTimeImmutable $periodEnd,
Employee $employee
): ?DateTimeImmutable { ): ?DateTimeImmutable {
$today = new DateTimeImmutable('today'); $today = new DateTimeImmutable('today');
$currentYear = LeaveRuleCode::FORFAIT_218->value === $ruleCode $currentYear = LeaveRuleCode::FORFAIT_218->value === $ruleCode
@@ -364,18 +365,27 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
: $this->resolveCurrentLeaveYear($today); : $this->resolveCurrentLeaveYear($today);
if ($year < $currentYear) { if ($year < $currentYear) {
return $periodEnd; $end = $periodEnd;
} } elseif ($year > $currentYear) {
if ($year > $currentYear) { $end = null;
return null; } else {
$lastDayPreviousMonth = $today
->modify('first day of this month')
->modify('-1 day')
;
$end = $lastDayPreviousMonth < $periodEnd ? $lastDayPreviousMonth : $periodEnd;
} }
$lastDayPreviousMonth = $today // Cap at contract end date if the employee has left.
->modify('first day of this month') $contractEndRaw = $employee->getCurrentContractEndDate();
->modify('-1 day') if (null !== $end && null !== $contractEndRaw && '' !== trim($contractEndRaw)) {
; $contractEnd = DateTimeImmutable::createFromFormat('Y-m-d', $contractEndRaw);
if ($contractEnd instanceof DateTimeImmutable && $contractEnd < $end) {
$end = $contractEnd;
}
}
return $lastDayPreviousMonth < $periodEnd ? $lastDayPreviousMonth : $periodEnd; return $end;
} }
/** /**
@@ -622,14 +632,27 @@ final readonly class EmployeeLeaveSummaryProvider implements ProviderInterface
} }
for ($cursor = $rangeStart; $cursor <= $rangeEnd; $cursor = $cursor->modify('+1 day')) { for ($cursor = $rangeStart; $cursor <= $rangeEnd; $cursor = $cursor->modify('+1 day')) {
$dayOfWeek = (int) $cursor->format('N');
if ($splitSaturdays) {
// Mode CDI/CDD : dimanche ignoré, samedi compté séparément.
if (7 === $dayOfWeek) {
continue;
}
} else {
// Mode forfait : seuls les jours ouvrés (lun-ven) comptent.
if ($dayOfWeek >= 6) {
continue;
}
}
[$am, $pm] = $this->resolveSegmentsForDate($absence, $cursor->format('Y-m-d')); [$am, $pm] = $this->resolveSegmentsForDate($absence, $cursor->format('Y-m-d'));
$dayAmount = ($am ? 0.5 : 0.0) + ($pm ? 0.5 : 0.0); $dayAmount = ($am ? 0.5 : 0.0) + ($pm ? 0.5 : 0.0);
if ($dayAmount <= 0.0) { if ($dayAmount <= 0.0) {
continue; continue;
} }
$isSaturday = $splitSaturdays && '6' === $cursor->format('N'); if ($splitSaturdays && 6 === $dayOfWeek) {
if ($isSaturday && $splitSaturdays) {
$takenSaturdays += $dayAmount; $takenSaturdays += $dayAmount;
} else { } else {
$takenDays += $dayAmount; $takenDays += $dayAmount;