feat(fusion_clock): open shifts + self-assign + bulk apply [B4-B5]

Model: fclk_create_open_shifts/claim_open_shift/release_shift (days-before
cutoff + role eligibility)/bulk_apply. Planner: Open Shift… panel, open-shifts
strip with delete, Apply-to-dept; load includes open shifts. Portal: claim
open shifts + release own upcoming shifts with feedback banners. Tests for
claim/role-gate/release/bulk.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-06-04 21:12:10 -04:00
parent 68aaa132ee
commit 2ad94070c7
9 changed files with 514 additions and 1 deletions

View File

@@ -35,6 +35,9 @@
<button class="btn btn-outline-success" t-on-click="() => this.togglePublishPanel()" t-att-disabled="state.loading or state.saving" title="Publish a custom date range and notify employees">
<i class="fa fa-calendar-check-o me-1"/> Publish…
</button>
<button class="btn btn-outline-secondary" t-on-click="() => this.toggleOpenShiftPanel()" t-att-disabled="state.loading or state.saving" title="Create an open shift employees can claim">
<i class="fa fa-plus me-1"/> Open Shift…
</button>
</div>
</div>
@@ -49,6 +52,35 @@
<button class="btn btn-light btn-sm" t-on-click="() => this.togglePublishPanel()">Cancel</button>
</div>
<div t-if="state.openShift.open" class="fclk-planner__publish-panel">
<label>Date <input type="date" t-att-value="state.openShift.date" t-on-change="(ev) => this.onOpenShiftField('date', ev)"/></label>
<label>Start <input type="time" t-att-value="state.openShift.start" t-on-change="(ev) => this.onOpenShiftField('start', ev)"/></label>
<label>End <input type="time" t-att-value="state.openShift.end" t-on-change="(ev) => this.onOpenShiftField('end', ev)"/></label>
<label>Count <input type="number" min="1" class="fclk-planner__repeat-int" t-att-value="state.openShift.count" t-on-change="(ev) => this.onOpenShiftField('count', ev)"/></label>
<button class="btn btn-secondary btn-sm" t-on-click="() => this.addOpenShift()" t-att-disabled="state.saving">
<i class="fa fa-plus me-1"/> Add Open Shift
</button>
<button class="btn btn-light btn-sm" t-on-click="() => this.toggleOpenShiftPanel()">Cancel</button>
</div>
<div t-if="hasOpenShifts" class="fclk-planner__open-strip">
<div class="fclk-planner__open-strip-title"><i class="fa fa-bullhorn me-1"/> Open Shifts (employees can claim)</div>
<div class="fclk-planner__open-cols">
<t t-foreach="state.days" t-as="day" t-key="'open_' + day.date">
<div class="fclk-planner__open-col" t-if="getOpenShiftsForDay(day.date).length">
<div class="fclk-planner__open-day"><t t-esc="day.weekday"/> <t t-esc="day.label"/></div>
<t t-foreach="getOpenShiftsForDay(day.date)" t-as="op" t-key="op.id">
<div class="fclk-planner__open-chip">
<span><t t-esc="op.label"/></span>
<span t-if="op.role_name" class="fclk-planner__open-role"><t t-esc="op.role_name"/></span>
<button class="fclk-planner__open-del" t-on-click="() => this.deleteOpenShift(op.id)" title="Remove open shift">×</button>
</div>
</t>
</div>
</t>
</div>
</div>
<t t-if="state.error">
<div class="alert alert-danger mx-3 mt-3"><t t-esc="state.error"/></div>
</t>
@@ -254,6 +286,12 @@
t-on-click="() => this.clearRecurrence()">
<i class="fa fa-ban me-1"/> Stop repeat
</button>
<button type="button"
class="btn btn-sm btn-light"
t-on-click="() => this.bulkApplyDept()"
title="Apply this shift to everyone in the same department">
<i class="fa fa-users me-1"/> Apply to dept
</button>
<button type="button"
class="btn btn-sm btn-primary"
t-on-click="() => this.applyEditorRange(true)">