Internal staff now land on /my/clock with no customer sidebar; new
finalized-payslip portal under /my/clock/payslips (inline paystub from
payslip.line_ids + PDF). Customers' portal is unchanged. Live on entech.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A shared portal_employee_navbar template broke fusion_planning, which
xpath-inherits each clock page's inline fclk-nav-bar to inject its
Schedule tab (anchored on a[@href='/my/clock/timesheets']). Revert to the
original inline-nav pattern on all four pages and append the Payslips item
to each — zero changes needed in fusion_planning.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reminders, absence detection, late/early penalties, and auto-clock-out are now
driven by each employee's real schedule (posted planner entry -> recurring
shift), never the global 9-5 default. Employees who aren't scheduled get no
reminders/absence. Overtime past the scheduled end is never cut off — auto
clock-out only fires at a max-shift safety cap (default raised 12 -> 16h). Team
leads build the planner in draft and Post it (publishes + emails employees).
- hr.employee._get_fclk_day_plan: explicit `scheduled` flag; posted-only planner
entries (drafts ignored), else recurring shift covering that weekday, else
not-scheduled; sources 'schedule'/'shift'/'none'.
- fusion.clock.shift: day_mon..day_sun weekday pattern + covers_weekday().
- fusion.clock.schedule: draft/posted state + posted_date; planner edits reset
to draft; fclk_email_posted_week notification.
- Rewrote the reminder / absence / auto-clock-out crons: schedule-gated,
per-employee savepoints, OT-aware cap, weekend hardcode removed.
- Penalties + all three clock-in paths skip days the employee isn't scheduled.
- shift_planner: Post Week route + planner Post button + draft count.
- Migration backfills pre-existing schedule entries to 'posted' so they keep
driving automation after upgrade.
- Tests: resolver matrix, cron gating, OT cap; fixed the existing planner test
for the new state/source semantics.
Design: docs/superpowers/specs/2026-05-30-schedule-driven-attendance-design.md
Frontend footprint kept at zero to avoid colliding with the concurrent
employee-portal (payslips) work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
entech runs the enterprise hr_payroll module (not the custom fusion_payroll),
whose hr.payslip lacks employee_cpp/ytd_* fields. Render the inline paystub
from payslip.line_ids (name + total) so it works on any payroll provider.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Odoo 19's _get_report() resolves a dotted string report_ref through
env.ref() as an XML ID, which lands on the QWeb view rather than the
ir.actions.report action. Pass the action id (matches every other
_render_qweb_pdf call site in the repo).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Separate internal employees from the customer portal: suppress the
fusion_plating_portal sidebar for internal users, redirect them to the
clock page, and add a finalized-payslip view (inline paystub + optional
PDF) under /my/clock/payslips in fusion_clock.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Posted-schedule/recurring-shift drives reminders, absence, penalties, and
auto-clock-out (never the global 9-5 default); overtime never cut (auto-close
only at a safety cap); team-lead draft->post workflow with employee notify.
Frontend footprint kept at zero to avoid colliding with the concurrent
fusion_plating employee-portal session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The website module injects a fixed "frontend->backend" nav
(.o_frontend_to_backend_nav — the floating apps-grid/edit button) on every
frontend page for any internal user. Since the kiosk account is an internal
user, that button let a kiosk user tap through to the Odoo backend.
Hide it with a page-scoped inline style in the kiosk template head, so it's
suppressed only on /fusion_clock/kiosk/nfc and the real website keeps its nav.
Live as 19.0.3.11.8 (verified the rule is in the rendered template).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Privacy/space housekeeping for the kiosk verification selfies. A new daily cron
(_cron_fusion_wipe_old_photos) deletes the photo attachments on attendances
whose clock-in is older than fusion_clock.photo_retention_days (default 60).
Only the images are removed — attendance records, worked hours and penalties
are kept. Clearing the attachment-backed binary reclaims filestore space.
- Configurable in Settings → Fusion Clock → NFC Kiosk ("Auto-Wipe Photos After
(days)"); set 0 to disable.
- Wipes all three photo fields (NFC check-in/out + legacy portal photo),
batched with per-batch savepoints.
- tests/test_photo_retention.py covers wipe-old / keep-recent / retention=0.
Verified live on entech (19.0.3.11.7) via a rollback-only dry run: a 70-day
shift's photos were wiped (record + 8h hours preserved) while a 5-day shift's
photo was kept; nothing persisted. 0 attendances currently exceed 60 days.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The kiosk captures and stores a photo on every tap (x_fclk_check_in_photo /
x_fclk_check_out_photo on hr.attendance), but no view displayed those — the
form only showed the legacy portal field x_fclk_checkin_photo, so the NFC
photos were invisible in the UI. Add a "Verification Photos" group showing the
clock-in and clock-out photos (plus the legacy portal photo), each hidden when
empty. (The activity log has no image field — photos live on the attendance.)
Live as 19.0.3.11.6.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The result card showed x_fclk_net_hours = worked_hours − break − early-out
penalty minutes. Tapping out before the scheduled end adds a 15-min early-out
penalty to the break field, so short shifts clamped to 0 → "Worked 0h 0m".
Show GROSS attendance.worked_hours (the actual clock-in → clock-out elapsed
time) instead, and format adaptively (Xh Ym / Ym / Ys) so brief shifts and
quick tests don't all read 0. Net-of-deductions stays in the payroll reports.
Live as 19.0.3.11.5 (verified worked_hours computes correctly in the DB).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root-caused on live entech (not guessed):
- The kiosk runs as a non-HR operator (uid 141) who gets AccessError reading
hr.employee images, so /web/image served a placeholder. Point the result-card
avatar at hr.employee.public/avatar_128 — verified readable as the operator,
returns the real photo. (Odoo's own UI uses .public for employee images.)
- The Odoo profile/preferences avatar is res.users → res.partner.image_1920,
which the capture never wrote. Propagate the captured photo to the linked
user's partner image so the profile updates too.
- Enlarge the capture oval (it was small): stage 62vh/520px, guide width 64%.
Live as 19.0.3.11.4. Also backfilled the existing test photo to the user's
partner image so the profile shows it without re-capturing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Profile photo DID save (verified: image_1920 attachment persists); the
"doesn't update" was a browser-cache miss. Add ?unique=<write_date> to the
result-card avatar URL so a freshly-captured photo shows on clock in/out.
- Capture now starts a 10-second countdown (time to get into frame) then
auto-snaps; the button toggles to Cancel while counting.
- Face guide is now a VERTICAL oval (aspect-ratio 3/4) over a portrait stage —
it was rendering horizontal. Faces are taller than wide.
Deployed live to entech (LXC 111) as 19.0.3.11.3; frontend bundle verified to
compile clean and contain the new rules.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Odoo's Sass compiler evaluates the built-in min() function and errors with
"Incompatible units: 'px' and 'vw'" on `width: min(86vw, 380px)`, which broke
the entire web.assets_frontend bundle (kiosk + all portal pages unstyled).
Equivalent, compiler-safe: `width: 86vw; max-width: 380px;`.
Verified: forced a fresh frontend bundle compile on entech — no Incompatible
-units error, served CSS contains the compiled --pin rule. Live as 19.0.3.11.2.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The --pin panel used width:auto, so in the centred flex overlay it
collapsed to its content width and crushed the 3-column numpad. Give it
a definite width (min(86vw, 380px)) and make the keys proper tappable
squares (min-height 60px, 1.6rem font).
Deployed live to entech (LXC 111) as 19.0.3.11.1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NFC kiosk:
- Add "📷 Photo" action to every Manage-page employee row and to the
post-enroll result card, so a manager can set/replace a profile photo
at any time (previously only surfaced when the employee had no image).
- Slim the Manager PIN pad: dedicated --pin panel variant (max-width 360px,
reduced padding) with a tighter numpad, removing the oversized whitespace.
Deployed live to entech (LXC 111) as 19.0.3.11.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Kiosk work across this session (19.0.3.6.0 -> 19.0.3.10.0):
- Program-from-unknown-tap: amber prompt -> Manager PIN -> pick/create employee
-> binds the captured UID (no re-tap). Reassign moves a card between employees.
- Manager page (gear, when unlocked): search employees + tag status; assign/re-tag,
clear tag, archive employee, + new employee. Server-gated by the enroll password.
- Screen lock: kiosk starts locked (tap-only); Unlock -> Manager PIN, Lock button;
PIN remembered for the session so the gear never re-prompts.
- Sounds: pleasant + loud sine chimes (rising in / descending out) + a low "denied"
tone for wrong/unknown taps. Gated by fusion_clock.enable_sounds.
- Guided profile-photo capture for employees with no picture (clock-in or enroll):
live camera + oval face guide -> capture -> preview -> save to hr.employee.
- PIN no longer re-renders per digit; centered result card; 12h time; clock-out shows
"Worked Xh Ym this shift"; modern clock idle icon; faster animations/result timers;
session keep-alive so the kiosk login never expires.
- New endpoints: create_employee, clear_tag, delete_employee (archive), verify_pin,
save_profile_photo; enroll gains force-reassign.
- Docs: fusion_clock is now developed in Claude Code (dropped Cursor references).
Spec/plan under fusion_clock/docs/superpowers/. Deployed live on entech
(odoo-entech / LXC 111 on pve-worker5), v19.0.3.10.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- PWA manifest on the NFC kiosk page so it installs as a full-screen
home-screen app (Chrome "Install" / Safari "Add to Home Screen").
- Dedicated "Kiosk Operator" permission + gated "Fusion Clock Kiosk"
top-level app (act_url -> /fusion_clock/kiosk/nfc). Kiosk controllers
accept Manager OR Kiosk Operator; all kiosk data ops already run sudo.
- Fix 403: read the company kiosk location via sudo on page-load and tap
(Kiosk Operator has no fusion.clock.location ACL).
- Odoo 19 permissions UX: ir.module.category + res.groups.privilege so
User/Team Lead/Manager and Kiosk Operator appear as application-access
dropdowns on the user form (no developer mode). Short group display names.
- Docs: note res.groups.privilege as the Odoo 19 category_id replacement.
Deployed live to entech (odoo-entech / LXC 111 on pve-worker5). v19.0.3.6.0.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Default-hide six order-line columns that aren't part of the plating
view (Product/product_template_id, Description Template, Specification,
Delivered Qty, Invoiced Qty, Taxes) by flipping them to optional="hide".
They stay available via the optional-columns toggle. Default-visible set
is now Customer-Facing, Part, Process/Recipe, Thickness, Serial, Job #,
Effective Deadline, Qty, Unit, Unit Price, Amount — for both quotations
and sales orders.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Remove the unused Customer Specification field and the redundant
Delivery Date (Lead Time covers it) from the customer-facing SO
confirmation and invoice PDFs (portrait + landscape). SO info row goes
5->4 columns (Delivery Date gone); the Customer Job # / Spec / Delivery
Method row goes 3->2 (Spec gone). Internal docs (traveller, sticker) and
the CoC process "Specification(s)" section are left untouched.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tooling/additional charge lines (any product line with no part catalog)
no longer print in the parts table — they render in the totals block
under the subtotal with their entered label + amount. Subtotal is now
parts-only; tax + grand total are unchanged (the charge is still a real
taxed line in the data). Applies to SO confirmation and invoice, both
portrait and landscape. Also aligns the invoice S/N cell to the SO's
multi-serial rendering.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a cohesive, restrained colour layer using the existing express
tokens (light/dark aware): faint plum gradient washes on the PO card,
legend bar, table header, and Order Summary header; filled accent
gradient pills (EXPRESS / CAD); accent rules on the section title,
summary header, and Grand Total footer. Adds an $xpr-accent-tint token
plus four composed gradient tokens.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The right-aligned value column squeezed the Additional Charge and Tax
dropdowns to a sliver. Move each picker into the (wider) label column,
stacked under its label at full width, so every value cell is now a
single amount that lines up cleanly in the right column under the
vertical divider.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Switch the summary rows from a flex space-between layout to a fixed
two-column grid (label | value) so a vertical divider on the label
cell's right edge lines up across every row. Values are right-aligned
into a clean amount column; the Grand Total footer keeps the divider at
the heavier rule weight.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Restyle the Order Summary card into a clean bordered table — a tinted
"Order Summary" caption bar, a divider line under every row, and an
accent-tinted Grand Total footer with a strong top rule. Uses the
existing light/dark express tokens so it renders correctly in both
colour schemes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Express order entry now has a single "Lot Order" toggle on the header
instead of a per-line "Lot" checkbox. When on, every line shows Lot
Total and prices as a flat lot (unit price derived = lot total / qty,
qty preserved for production); when off, the Lot Total column is hidden
and lines price per unit as usual. Keeps the order summary clean for the
common per-unit case.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bite-sized TDD plan: charge-type model + config UI, wizard charge/tax fields,
totals = one tax on (subtotal+charge), per-line lot pricing, SO-create tax on
all lines + typed charge line, and the express summary/line view changes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Direct/Express order entry: a searchable/creatable fp.additional.charge.type
replaces the fixed Tooling Charge; one order-level account.tax applies to
(subtotal + charge); per-line lot pricing (flat lot total, derived unit price,
qty preserved). Reordered summary. Quotes out of scope.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Grant Odoo Billing (account.group_account_invoice) to group_fp_manager via
implied_ids; Quality Manager + Owner inherit it. Billing only (not Accountant);
the SO-origin workflow gate in fusion_plating_jobs is unchanged, so managers
invoice from the Sale Order's Create Invoice action. Tests assert Manager/Owner
get Billing and Shop Manager does not.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Read-only per-part version history (version#, reference, customer-facing,
order, by/when) below the curated templates list.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wizard line (direct + express) and SO line now pre-fill BOTH internal +
customer-facing from the part's latest version (fallback to
default_specification_text), without clobbering typed text.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Each part-bearing line writes a deduped version (final order# + date) via
_fp_save_description_version, after the parent-number rename so the title
reflects the confirmed order number.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bite-sized TDD plan: version model + part load/save helpers, save-on-confirm
hook, wizard + SO-line auto-load, and the part Descriptions-tab history list.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Dedicated fp.part.description.version model: latest auto-loads both internal +
customer-facing into a new order line; on SO confirm, a changed description
saves a new version titled "S#### · date". Browsable per-part history;
default_specification_text kept synced. SO surfaces only (not quotes).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The order-line onchange still auto-ticks "Save as Default" (so a new part's
spec is remembered next time) — only the explanatory popup is removed, per
client request. The ticked checkbox on the line is the cue now.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>