Chapter 1 · Purpose and foundations
Employment, time and payroll: scope and standards
The HR record, the time clock and the payroll provider are three separate systems. This module owns the first two: employment history with effective dating, time clocks with punch edits, overtime rules by state, and timesheets approved for export. Payroll stays with ADP, Gusto, Paychex or Paylocity because tax withholding and filing are too complex to keep in-house.
25 min19 tables5 roles7 governing standards
By the end of this chapter you can
- Say what employment records with effective dating keep, and why overwriting job history is a pitfall.
- Explain why punch edits keep the original time and reason, and what FLSA requires.
- Name the seven standards this module is built against and what each one is used for.
What M21 People, time and attendance does
This module owns the people who work at a plant, their jobs and pay, their time clocks, time off, shift schedules, hiring and the export to payroll. It replaces the HR database, time-and-attendance platform, and recruiting tool a business usually pays for separately: ADP Workforce Now, UKG Pro (formerly Kronos), BambooHR, Rippling, Gusto, Paychex, Paycom, Paylocity and Workday are the most common.
What it does not do: payroll calculation, tax withholding and tax filing stay with the payroll provider. Compensation stays restricted to the HR manager role, with field-level access control so a supervisor cannot see another person's pay rate.
The employment record with effective dating
An employee's job and pay change over time: a hire, a promotion, a raise, a transfer, a demotion. The pitfall is overwriting the old job when recording a new one. Effective dating keeps every change as a separate row with effective_from and effective_to dates, so nothing is ever overwritten and the history is complete.
Time clocks and punch edits
A punch is a time-clock entry: in, out, break start, break end, or job change. When a supervisor corrects a punch, a new punch is inserted with source manual_edit. Its punched_at is the corrected time and its original_punched_at is the time first recorded, with edited_by set to the supervisor and edit_reason filled in. When the first value is simply missing, as with a forgotten clock-out, original_punched_at stays empty and the reason says why. The original punch is never changed or deleted.
Why this matters: the Fair Labor Standards Act (29 CFR 516) requires that employers keep accurate records of hours worked. An audit trail of punches, original and corrected, is the proof.
Seven governing standards
| Standard | Use in this module |
|---|---|
| FLSA: 29 CFR 516 (records) and 29 CFR 778 (overtime) | Hours-worked records and overtime calculation. Payroll records are kept at least three years and time cards two (29 CFR 516.5 and 516.6). |
| State wage-and-hour law | Daily overtime and meal and rest breaks (for example California). |
| FMLA: 29 CFR 825 | Leave eligibility and tracking, kept as hr_time_off_request.fmla_designated. |
| 8 CFR 274a.2 (Form I-9) | Employment eligibility verification and how long Form I-9 is kept: scans stored with a keep-until date. This module also limits them to the HR manager. |
| EEOC EEO-1 Component 1 | Workforce demographic reporting. Restricted data; this course stores none in an hr_ table. |
| HR Open Standards | HR data interchange schemas. |
| NIST SP 800-122 | Protecting personally identifiable information. NIST SP 800-122 is the guide to protecting such data; this module simply never stores Social Security, bank account or card numbers. |
Knowledge check
Which standard sets how long Form I-9 files must be kept?
Knowledge check
A person works five 9-hour days (45 hours) in a state with no daily overtime rule. How many overtime hours under federal FLSA?
References
- U.S. eCFR: 29 CFR Part 516, records to be kept by employers. https://www.ecfr.gov/current/title-29/part-516
- U.S. eCFR: 29 CFR Part 778, overtime compensation. https://www.ecfr.gov/current/title-29/part-778
- U.S. Department of Labor: Fact Sheet #23, overtime pay requirements of the FLSA. https://www.dol.gov/agencies/whd/fact-sheets/23-flsa-overtime
- California Department of Industrial Relations: overtime FAQ. https://www.dir.ca.gov/dlse/faq_overtime.htm
- U.S. eCFR: 29 CFR Part 825, the Family and Medical Leave Act. https://www.ecfr.gov/current/title-29/part-825
- U.S. eCFR: 8 CFR 274a.2, verification of employment eligibility (Form I-9). https://www.ecfr.gov/current/title-8/section-274a.2
- U.S. Equal Employment Opportunity Commission: EEO-1 data collection. https://www.eeoc.gov/data/eeo-1-data-collection
- NIST SP 800-122: Guide to protecting the confidentiality of PII. https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-122.pdf
Chapter 2 · Schema and workflows
The data model and overtime calculation
19 tables hold employment records with effective dating, time punches with edit trails, overtime rules by state, and timesheets approved for export. Three design rules keep the record honest: effective dating never overwrites history, field-level access control hides restricted data, and punch edits keep the original time and reason.
30 min19 tablesEffective datingField-level access control
By the end of this chapter you can
- Name the key hr_ tables and say what each one holds.
- Explain effective dating and why a closed assignment stays visible.
- Say how overtime is calculated using federal FLSA and state rules, and which data is restricted.
The 19 tables
The module uses 19 tables to hold employment (hr_employment, hr_position, hr_job_assignment, hr_compensation), time off (hr_time_off_policy, hr_time_off_balance, hr_time_off_request), time punches and rules (hr_time_punch, hr_overtime_rule), timesheets (hr_timesheet, hr_payroll_batch), scheduling (hr_schedule_shift), recruiting (hr_requisition, hr_candidate, hr_application, hr_interview, hr_offer), onboarding (hr_onboarding_task) and performance (hr_review).
The table below is the full definition: every column the module adds to the standard ones (id, tenant_id, created_at, created_by, updated_at, updated_by, row_version, archived_at, ext), with its type and whether it is required (marked req). Types are written as in the specification: text, int, num (a decimal number), money, bool, date, ts (a timestamp with time zone) and json; an arrow means a foreign key to that table; a list after a colon is the allowed values, enforced as check constraints. unique means unique per tenant. hr_schedule_shift.required_skill_ref is a soft link to trn_skill in module M07: the column holds the id and the database does not enforce it. It is what your agent builds from in Session 2.
| Table | Holds | Columns: type, req = required |
|---|---|---|
| hr_employment | Employment relationship of a person | person_id → core_person req; employee_no text req, unique; employment_type text req: full_time, part_time, temporary, seasonal, intern; flsa_status text req: exempt, non_exempt; hire_date date req; original_hire_date date; termination_date date; termination_reason_code_id → core_reason_code; status text req: pre_hire, active, on_leave, terminated; work_site_id → core_site; manager_person_id → core_person; pay_group text; collective_agreement text |
| hr_position | Budgeted position | position_no text req, unique; title text req; org_unit_id → core_org_unit; job_role_id → core_job_role; reports_to_position_id → hr_position; budgeted_fte num; status text req: open, filled, frozen, eliminated |
| hr_job_assignment | Effective-dated assignment | employment_id → hr_employment req; position_id → hr_position; job_role_id → core_job_role; org_unit_id → core_org_unit; cost_center_id → core_cost_center; effective_from date req; effective_to date; fte num; shift_definition_id → core_shift_definition; change_reason text req: hire, transfer, promotion, demotion, reorg, correction |
| hr_compensation | Effective-dated pay (restricted) | employment_id → hr_employment req; effective_from date req; pay_basis text req: hourly, salary; rate money req; currency text req; reason text req: hire, merit, promotion, market, cola, correction |
| hr_time_off_policy | Leave policy | name text req, unique; accrual_method text req: per_pay_period, per_hour_worked, annual_grant, none; accrual_rate num; max_balance_hours num; carryover_max_hours num; waiting_period_days int; jurisdiction text |
| hr_time_off_balance | Balance per employee and policy | employment_id → hr_employment req; policy_id → hr_time_off_policy req; balance_hours num req; as_of date req |
| hr_time_off_request | Leave request | employment_id → hr_employment req; policy_id → hr_time_off_policy req; start_at ts req; end_at ts req; hours num req; status text req: pending, approved, denied, cancelled, taken; approver_id → core_person; decided_at ts; fmla_designated bool |
| hr_schedule_shift | Published shift assignment | employment_id → hr_employment; work_center_id → core_equipment; shift_instance_id → core_shift_instance; start_at ts req; end_at ts req; required_skill_ref → trn_skill (soft link); status text req: draft, published, open, swapped, cancelled |
| hr_time_punch | Clock event with edit trail | employment_id → hr_employment req; punch_type text req: in, out, break_start, break_end, job_change; punched_at ts req; source text req: kiosk, mobile, web, badge, manual_edit; geo json; device_id text; job_ref text; edited_by → core_person; edit_reason text; original_punched_at ts |
| hr_overtime_rule | Overtime/double-time rule by jurisdiction | jurisdiction text req; daily_ot_threshold_h num; weekly_ot_threshold_h num; ot_multiplier num req; daily_dt_threshold_h num; dt_multiplier num; seventh_day_rule bool |
| hr_timesheet | Period timesheet with approval and export | employment_id → hr_employment req; period_start date req; period_end date req; regular_hours num; ot_hours num; dt_hours num; pto_hours num; exceptions json; status text req: open, submitted, approved, exported, reopened; approved_by → core_person; approved_at ts; payroll_batch_id → hr_payroll_batch |
| hr_payroll_batch | Export batch to payroll provider | batch_no text req, unique; provider text req: adp, gusto, paychex, paylocity, paycom, other; period_start date req; period_end date req; file_attachment_id → core_attachment; exported_at ts; status text req: draft, exported, acknowledged, failed |
| hr_requisition | Job requisition | req_no text req, unique; position_id → hr_position; title text req; hiring_manager_id → core_person req; openings int req; pay_range json; status text req: draft, approved, open, on_hold, filled, cancelled; posted_at ts |
| hr_candidate | Candidate (personal data; retention-limited) | first_name text req; last_name text req; email text; phone text; source text; resume_attachment_id → core_attachment; consent_at ts; retain_until date |
| hr_application | Candidate application to a requisition | candidate_id → hr_candidate req; requisition_id → hr_requisition req; stage text req: applied, screen, interview, assessment, offer, hired, rejected, withdrawn; stage_changed_at ts; rejection_reason_code_id → core_reason_code |
| hr_interview | Interview with scorecard | application_id → hr_application req; interviewer_id → core_person req; scheduled_at ts; scorecard json; recommendation text: strong_yes, yes, no, strong_no |
| hr_offer | Offer letter | application_id → hr_application req; pay_basis text req: hourly, salary; rate money req; start_date date; status text req: draft, approved, sent, accepted, declined, rescinded; sent_at ts; accepted_at ts; letter_attachment_id → core_attachment |
| hr_onboarding_task | On/offboarding task | employment_id → hr_employment req; kind text req: onboarding, offboarding; title text req; owner_role text req: hr, manager, it, safety, training, facilities, employee; due_at date; done_at ts; done_by → core_person |
| hr_review | Performance review | employment_id → hr_employment req; cycle_name text req; reviewer_id → core_person req; ratings json; comments text; status text req: draft, submitted, calibrated, shared, acknowledged; acknowledged_at ts |
Effective dating: never overwrite job history
When an employee is promoted, insert a new hr_job_assignment with effective_from set to the promotion date, and close the old assignment by setting effective_to to the promotion date minus one day, so the end date is inclusive. Both rows stay in the database forever, and each new or closed assignment publishes the event hr.job_assignment.changed. The job on any date D is the assignment where effective_from <= D and (effective_to is null or effective_to >= D). For example, a row closed with effective_to 2023-05-31 is still the job on 2023-05-31, and the next row starts on 2023-06-01. A rule written as 'effective_to > D' would return no job at all on the last day of a closed row.
hr_compensation has only effective_from: a pay change is a new row, and the rate in force on a date is the row with the latest effective_from not after that date. Time-off balances work the same way: each change is a new row with its own as_of, and the current balance is the row with the latest as_of.
Overtime calculation
Federal FLSA requires overtime (time-and-a-half, 1.5x) for any hour over 40 in a workweek, a fixed run of seven days that starts at the same day and hour every week. A few states, such as California, add daily overtime: time-and-a-half for any hour over 8 in a day, and double-time (2.0x) for any hour over 12 in a day. Some states, California among them, add a seventh-consecutive-day rule; in California the first 8 hours on that day are paid at 1.5x and any hours beyond 8 at 2x. Store which rule applies in hr_overtime_rule.seventh_day_rule and the multipliers. Overtime applies only to employees whose hr_employment.flsa_status is non_exempt: skip employees whose flsa_status is exempt, because they are not owed overtime, and test that an exempt employee's 48-hour week gives 0 overtime hours.
Each hour is counted once, at the highest rate any rule gives it. Take five 10-hour days (50 hours) under a California-style daily rule: each day has 8 regular hours and 2 overtime hours, which is 40 regular and 10 overtime in the week. The ten hours over 40 in the week are the same ten hours, so the answer is 10 overtime hours, not 20.
The module stores these rules in hr_overtime_rule with jurisdiction, daily_ot_threshold_h, weekly_ot_threshold_h, ot_multiplier (1.5), daily_dt_threshold_h, dt_multiplier (2.0), and seventh_day_rule (true/false).
Restricted data and what is never stored
The Role and Exposure Matrix grants read access to restricted data to the HR manager role only (with one exception for the employee's own leave request). A row-level security rule in the database enforces this: a supervisor running any query that tries to read hr_compensation is refused by the database, not just hidden on a screen.
| Data | Who may see it | Why |
|---|---|---|
| hr_compensation (rate, currency) | HR manager only | Pay is restricted; the payroll export never carries a rate. |
| hr_offer.rate and hr_candidate | HR manager only | Candidate data is personal data, kept only with consent_at and until retain_until. |
| hr_time_off_request.fmla_designated | HR manager and the employee who asked | Leave under FMLA (29 CFR 825) is medical-related. Supervisors see the request without the flag. |
| Form I-9 scans (in core_attachment) | HR manager only | 8 CFR 274a.2 sets how long they are kept; this module also limits who can open them to the HR manager. |
| EEO-1 demographic data | Not stored in this course | EEOC EEO-1 Component 1 data is restricted; if ever kept, it goes in its own table with its own role. |
| Rule the database holds | Where |
|---|---|
| Unique per tenant | hr_employment.employee_no, hr_position.position_no, hr_time_off_policy.name, hr_payroll_batch.batch_no, hr_requisition.req_no |
| Added by this course, not by the specification | One hr_timesheet per employment_id and period_start |
| Never updated or deleted (a trigger refuses it) | hr_time_punch |
| Added to, never updated (no role has update in the Role and Exposure Matrix) | hr_time_off_balance, hr_compensation |
Knowledge check
A job row is closed with effective_to = 2023-05-31 and the next starts on 2023-06-01. Which condition finds the job on 2023-05-31?
Knowledge check
A supervisor opens a time-off request to approve it. Which column must they not see?
References
- HR Open Standards: HR data interchange. https://www.hropenstandards.org/
- U.S. Department of Labor: Wage and Hour Division compliance assistance. https://www.dol.gov/agencies/whd/compliance-assistance
- U.S. Department of Labor: Fact Sheet #28, the Family and Medical Leave Act. https://www.dol.gov/agencies/whd/fact-sheets/28-fmla
- U.S. eCFR: 29 CFR Part 778, overtime compensation. https://www.ecfr.gov/current/title-29/part-778
Chapter 3 · Daily operations and payroll export
Clocking in, approval and export
An employee clocks in and out on a kiosk, phone or web screen. The system calculates regular hours, overtime, and time-off. A supervisor approves the timesheet. The payroll processor exports it as a CSV to the payroll provider. Field-level access control keeps sensitive data protected at every step.
25 minPunch sources: kiosk, mobile, web, badgeOvertime: federal + stateReconciliation required
By the end of this chapter you can
- Explain the clock-in workflow and what sources a punch can come from.
- Say how a timesheet is approved and what checks must pass.
- Describe the payroll export workflow and why reconciliation is required.
Clock-in workflow
An employee clocks in by pressing a button on a kiosk, mobile app, web screen or badge. The system creates an hr_time_punch row with the server's current timestamp (the record time), the punch_type (in, out, break_start, break_end), and the source (kiosk, mobile, web, badge). For a mobile punch it may save the phone's reported location in geo, only if the person allowed it. The system prevents bad states: no 'out' without an 'in' first, no two 'in' punches in a row, and no punch once the timesheet for that period is approved.
When a timesheet is calculated, an 'in' with no 'out' (or an 'out' with no 'in') is written to hr_timesheet.exceptions as a missed-punch exception. The system does not guess; the employee tells the supervisor, who adds a correction. Approval is refused while an exception is open.
Time off and accrual
Each hr_time_off_policy has an accrual method (per_pay_period, per_hour_worked, annual_grant or none), an accrual_rate, a max_balance_hours cap, a carryover_max_hours limit and a waiting_period_days. Nothing accrues before hire_date plus the waiting period, and a balance never goes above the cap. Every change is a new dated hr_time_off_balance row. An employee requests leave, a supervisor or manager approves it (refused if it exceeds the balance), and when the timesheet covering the dates is approved the request becomes taken and the hours are deducted once.
Timesheet approval
The system keeps one timesheet per employee per workweek and recalculates it whenever a punch is added, applying the overtime rules. The employee submits the week from their screen (status submitted), and a supervisor opens the timesheet for their crew and sees the summary (regular_hours, ot_hours, dt_hours, pto_hours) calculated from the punches. The supervisor checks it against their notes or a hand count. If it matches, the supervisor clicks 'approve'. The system sets status to approved, approved_by to the supervisor's person_id, and approved_at to the server timestamp, and publishes the event hr.timesheet.approved. A person cannot approve their own timesheet. To change hours afterwards, the timesheet is reopened (status reopened), which leaves a trace; an exported timesheet cannot be reopened.
You need:
Build a week of punches with the test fixture from Session 4, approve the timesheet on the platform, and see a missed punch block approval. Only the clock-in route must use the server clock; a fixture run with the database owner login may set punched_at.
Outcome: A timesheet with five days of eight-hour shifts is calculated to 40 regular hours; once the sixth day's missing out is corrected at 16:00, it shows 48 hours: 40 regular and 8 overtime. A missed punch blocks approval until it is corrected with a reason. The supervisor approves it, and the database shows approval with supervisor id and timestamp.
Scheduling with a qualification check
A published shift (hr_schedule_shift) names the work centre, the shift, the time and, optionally, the skill it needs (required_skill_ref, a soft link to trn_skill in module M07). Publishing is refused when the employee has no current qualification for that skill. A shift can also be left open (status open) until the supervisor assigns someone; shift swaps and open-shift pickup by employees are in the specification but not built in this course.
Payroll export
The payroll processor selects approved timesheets for one or more periods and clicks 'export'. Before exporting, the system checks that every worked hour has a punch behind it (no phantom hours). The export file is CSV in the layout the provider documents (ADP, Gusto, Paychex, Paylocity, Paycom or other), with columns: employee name, employee_no, period, regular_hours, ot_hours, dt_hours, pto_hours. It never carries a pay rate. The system verifies that the sum of hours in the export file equals the sum of the same hours in the timesheets (reconciliation), marks the timesheets exported, stores the file, and publishes hr.payroll_batch.exported.
Knowledge check
Which check must pass before a payroll batch is exported?
Knowledge check
A person forgot to clock out, and the supervisor adds the missing out. What is original_punched_at on the new row?
References
- U.S. Department of Labor: Fact Sheet #21, recordkeeping requirements under the FLSA. https://www.dol.gov/agencies/whd/fact-sheets/21-flsa-recordkeeping
- U.S. eCFR: 29 CFR 516.2, records to be kept for employees subject to minimum wage or overtime. https://www.ecfr.gov/current/title-29/section-516.2
- U.S. Department of Labor: Fact Sheet #23, overtime pay requirements of the FLSA. https://www.dol.gov/agencies/whd/fact-sheets/23-flsa-overtime
Chapter 4 · Moving in and cutting over
Import, verification and cutover
Employees, job history, time-off balances and open requisitions are imported from the old system. Balances are brought in as dated opening entries and verified before the first pay period. After one full pay period running both systems in parallel, you compare the hours and cut over on a date you set. You then record the saving from cancelled subscriptions.
25 minImport as dated opening entriesVerification before cutoverSensitive data forbidden
By the end of this chapter you can
- Explain why time-off balances are imported as dated opening entries.
- Describe the parallel run comparison and what must reconcile.
- Say how long records are kept, and how the saving is calculated from real invoices.
Import as dated opening entries
Time-off balances are imported as hr_time_off_balance rows with as_of set to the date of the old system's balance report. Before the first pay period, the system generates three reports: (a) employee count by status (pre_hire, active, on_leave, terminated) compared to the old system, (b) time-off policy totals compared to the old system, (c) open requisitions and active candidates compared. Do not proceed to the first pay period until these three reports show matching numbers.
Candidates are personal data and retention-limited: an imported candidate gets consent_at (when the old system recorded it) and retain_until from your written retention rule, and the row is archived once that date has passed. Closed and rejected candidates are not imported unless you are archiving them for history.
Important: never store Social Security, bank account or card numbers. No hr_ table has a column for them, so a check on a column that is not there could never fire. The protection is three locks: the import script refuses a source file with a column named like one of them, a database check refuses digit patterns in the ext column and the free-text fields, and nothing in the module asks for them. NIST SP 800-122 is the guide.
From candidate to hire
A candidate moves through the stages applied, screen, interview, assessment and offer. An hr_interview holds a scorecard and recommendation; an hr_offer holds the rate and moves from draft to accepted. An application cannot move to hired without an accepted offer. On hire the system creates the employment record (status pre_hire), the first job assignment (change_reason hire), the first pay row and a set of onboarding tasks for HR, the manager, IT, safety, training, facilities and the employee, and publishes hr.employment.hired.
The parallel run and reconciliation
For one full pay period, run both the new module and the old system for the same employees. Compare hours only: the headcount, and for each employee regular, overtime, double-time and time-off hours. The payroll provider's own gross-pay report shows the hours it paid; set the hours in your export beside them. This module never calculates pay, so there is no pay amount of its own to compare. The new module should produce the same total hours as the old system within 1%, the tolerance this course uses; set your own with your payroll person. If the numbers differ by more than that, or a difference for one person cannot be explained, do not cut over. Fix the calculation.
Also verify three critical rules: (1) Punch edits keep the original time and reason, (2) Effective dating never overwrites job history, (3) Compensation is invisible to non-HR roles (proven by a field-level RLS test).
You need:
Import employees from the old system and verify the reconciliation reports.
Outcome: Five employees are imported with all fields matching the old system. The reconciliation reports show matching counts. The import refuses a forbidden column, and the database refuses a number pattern in ext.
Cutover, record keeping and saving
Once the parallel run reconciles, choose a cutover date. On that date, tell everyone, stop entries in the old system, and tell the team to use the new module. Watch the first day's punches. If something breaks, roll back.
Before cancelling the old tools, settle how long records must be kept. FLSA requires payroll records for at least three years (29 CFR 516.5) and supporting records such as time cards for two (29 CFR 516.6). Your state may require longer, so check it and keep the old export for the longest period that applies. Form I-9 files follow 8 CFR 274a.2.
After cutover, cancel the old tools. The saving is the old annual cost (from your invoices) minus the new annual cost (usually zero). Write the arithmetic so the owner can see where the number comes from.
Knowledge check
Time-off balances are imported. What is the next step?
Knowledge check
What happens if the parallel run shows hours differ by 2% from the old system?
Knowledge check
How long must time cards be kept under the FLSA recordkeeping rules?
References
- U.S. eCFR: 29 CFR 516.5, records to be preserved for 3 years. https://www.ecfr.gov/current/title-29/section-516.5
- U.S. eCFR: 29 CFR 516.6, records to be preserved for 2 years. https://www.ecfr.gov/current/title-29/section-516.6
- U.S. Citizenship and Immigration Services: I-9 Central. https://www.uscis.gov/i-9-central
Chapter 5 · 12 questions · 80% passes
Final assessment
Twelve questions across the element. Score 80% (10 of 12) to pass. Your LMS records your score and each answer; you can review the chapters and try again.
15 min12 questions≈ 15 minutesRetake allowed
Your result
CivOps AI Academy
People, time and attendance: Employment, time clocks and payroll export
Element M21 complete · Learner
Your LMS records this completion. For the CivOps Foundation certificate, finish the Foundation Course at https://civops.io/learn.