Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/hr/CHARTER.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ It is the minimum HR backbone a 20–200-person company actually uses on day one
| Self-referential relationship | `hr_employee.manager_id → hr_employee` |
| Hierarchy object | `hr_department.parent_id → hr_department` |
| State machine + approval | `hr_time_off_request`: draft → submitted → approved/rejected |
| Field-level sensitivity | `hr_employee` salary / contract fields visible only to HR + self |
| Field-level sensitivity | `hr_employee` salary / contract fields visible to HR Admins only (the `hr_employee` profile denies them). Self-visibility ("see my own comp, not a peer's") needs a row-aware field predicate and is a documented fork — do not claim it ships. |
| Sharing rule | time-off request rows visible only to requester + approval chain |
| Expiry-driven flow | `hr_document.expires_at` triggers reminder 30 days out |
| Profiles | `hr_employee` (self-service) vs `hr_admin` (full access) |
Expand Down
16 changes: 14 additions & 2 deletions packages/hr/src/flows/time_off_submitted.flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@ export const TimeOffSubmittedFlow: Flow = {
outputVariable: 'emp',
},
},
{
id: 'route_to_manager',
type: 'update_record',
label: 'Route to Manager',
config: {
objectName: 'hr_time_off_request',
filter: { id: '{record.id}' },
// Record the approver so the "My Pending Approvals" surfaces resolve.
fields: { approver: '{emp.manager}' },
},
},
{
id: 'notify_manager',
type: 'notify',
Expand All @@ -64,7 +75,8 @@ export const TimeOffSubmittedFlow: Flow = {
edges: [
{ id: 'e1', source: 'start', target: 'get_request', type: 'default' },
{ id: 'e2', source: 'get_request', target: 'get_employee', type: 'default' },
{ id: 'e3', source: 'get_employee', target: 'notify_manager', type: 'default' },
{ id: 'e4', source: 'notify_manager', target: 'end', type: 'default' },
{ id: 'e3', source: 'get_employee', target: 'route_to_manager', type: 'default' },
{ id: 'e4', source: 'route_to_manager', target: 'notify_manager', type: 'default' },
{ id: 'e5', source: 'notify_manager', target: 'end', type: 'default' },
],
};
7 changes: 6 additions & 1 deletion packages/hr/src/objects/hr_document.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export const EmployeeDocument = ObjectSchema.create({
label: 'Expired?',
expression: F`record.expires_at != null && record.expires_at < today()`,
}),
expiry_status: Field.formula({
label: 'Expiry Status',
description: 'Single severity band for sorting/colour-coding the document list.',
expression: F`record.expires_at == null ? "none" : (record.expires_at < today() ? "expired" : (record.expires_at <= (today() + 30) ? "expiring_30d" : (record.expires_at <= (today() + 60) ? "expiring_60d" : "valid")))`,
}),
notes: Field.markdown({ label: 'Notes' }),
},

Expand All @@ -70,5 +75,5 @@ export const EmployeeDocument = ObjectSchema.create({
indexes: [{ fields: ['employee'] }, { fields: ['doc_type'] }, { fields: ['expires_at'] }],

titleFormat: tmpl`{{record.name}}`,
compactLayout: ['name', 'employee', 'doc_type', 'expires_at'],
compactLayout: ['name', 'employee', 'doc_type', 'expires_at', 'expiry_status'],
});
8 changes: 5 additions & 3 deletions packages/hr/src/objects/hr_time_off_request.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import type { Hook, HookContext } from '@objectstack/spec/data';

/**
* Time-off automation hook — derives the approver from the employee's
* manager on submit, and clears decision metadata when a request returns
* to draft.
* Time-off automation hook — stamps lifecycle timestamps and clears decision
* metadata when a request returns to draft. The approver is routed to the
* employee's manager by `time_off_submitted.flow.ts` (which already resolves
* the manager to notify), not here — a hook can't do that cross-object read
* safely in the standalone sandbox.
*/
const timeOffHook: Hook = {
name: 'hr_time_off_automation',
Expand Down
2 changes: 2 additions & 0 deletions packages/hr/src/objects/hr_time_off_request.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export const TimeOffRequest = ObjectSchema.create({
{ label: 'Vacation', value: 'vacation', color: '#3B82F6', default: true },
{ label: 'Sick', value: 'sick', color: '#EF4444' },
{ label: 'Personal', value: 'personal', color: '#8B5CF6' },
{ label: 'Parental', value: 'parental', color: '#06B6D4' },
{ label: 'Bereavement', value: 'bereavement', color: '#475569' },
{ label: 'Unpaid', value: 'unpaid', color: '#6B7280' },
],
}),
Expand Down
10 changes: 9 additions & 1 deletion packages/hr/src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,14 @@ export const en: TranslationData = {
employee: { label: 'Employee' },
leave_type: {
label: 'Leave Type',
options: { vacation: 'Vacation', sick: 'Sick', personal: 'Personal', unpaid: 'Unpaid' },
options: {
vacation: 'Vacation',
sick: 'Sick',
personal: 'Personal',
parental: 'Parental',
bereavement: 'Bereavement',
unpaid: 'Unpaid',
},
},
start_date: { label: 'Start Date' },
end_date: { label: 'End Date' },
Expand Down Expand Up @@ -126,6 +133,7 @@ export const en: TranslationData = {
expires_at: { label: 'Expires At' },
is_expiring_soon: { label: 'Expiring Soon?' },
is_expired: { label: 'Expired?' },
expiry_status: { label: 'Expiry Status' },
notes: { label: 'Notes' },
},
_views: {
Expand Down
10 changes: 9 additions & 1 deletion packages/hr/src/translations/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,14 @@ export const zhCN: TranslationData = {
employee: { label: '员工' },
leave_type: {
label: '假期类型',
options: { vacation: '年假', sick: '病假', personal: '事假', unpaid: '无薪假' },
options: {
vacation: '年假',
sick: '病假',
personal: '事假',
parental: '育儿假',
bereavement: '丧假',
unpaid: '无薪假',
},
},
start_date: { label: '开始日期' },
end_date: { label: '结束日期' },
Expand Down Expand Up @@ -118,6 +125,7 @@ export const zhCN: TranslationData = {
expires_at: { label: '到期日期' },
is_expiring_soon: { label: '即将到期?' },
is_expired: { label: '已过期?' },
expiry_status: { label: '到期状态' },
notes: { label: '备注' },
},
_views: {
Expand Down
Loading