Cogover Learning

API reference @cogover/sdk: Cơ cấu tổ chức

Trang này thuộc API reference của @cogover/sdk.

Cơ cấu tổ chức

org đọc phòng ban, vị trí và nhân sự của workspace: ai thuộc phòng ban nào, vị trí nào áp dụng ở đâu và ai quản lý ai. Mọi method đều chỉ đọc. Câu trả lời về cấu trúc được lấy từ bản sao cơ cấu tổ chức đã cache nên rất nhanh; thay đổi thực hiện trong Cogover sẽ có hiệu lực sau vài giây. Thêm withDisplay: true để nhận thêm tên phòng ban, vị trí theo ngôn ngữ và, với nhân sự, tên hiển thị, email, avatar và mã nhân sự. Các field khác của nhân sự như số điện thoại hay custom field được đọc bằng data.object("personnel"), nơi quyền record được áp dụng.

interface OrgApi {
  readonly personnel: OrgPersonnelApi;
  readonly departments: OrgDepartmentsApi;
  readonly positions: OrgPositionsApi;
  me(options?: OrgReadOptions): Promise<OrgPersonnel | null>;
  isInDepartment(personnelId: string, departmentId: string,
    options?: OrgIsInDepartmentOptions): Promise<boolean>;
  isManagerOf(managerId: string, personnelId: string,
    options?: OrgIsManagerOfOptions): Promise<boolean>;
}
 
interface OrgPersonnelApi {
  get(id: string, options?: OrgReadOptions): Promise<OrgPersonnel | null>;
  getMany(ids: readonly string[], options?: OrgReadOptions):
    Promise<OrgGetManyResult<OrgPersonnel>>;
  managerChain(id: string, options?: OrgPersonnelChainOptions):
    Promise<readonly OrgManagerChain[]>;
}
 
interface OrgDepartmentsApi {
  get(id: string, options?: OrgReadOptions): Promise<OrgDepartment | null>;
  tree(options?: OrgTreeOptions): Promise<readonly OrgDepartmentNode[]>;
  ancestors(id: string, options?: OrgReadOptions): Promise<readonly OrgDepartment[]>;
  members(id: string, options?: OrgDepartmentMembersOptions): Promise<OrgPage<OrgMember>>;
  managers(id: string, options?: OrgManagerOptions): Promise<readonly OrgManagerTier[]>;
  managerChain(id: string, options?: OrgManagerOptions): Promise<OrgManagerChain>;
}
 
interface OrgPositionsApi {
  list(options?: OrgReadOptions): Promise<readonly OrgPosition[]>;
  get(id: string, options?: OrgReadOptions): Promise<OrgPosition | null>;
  members(id: string, options?: OrgPositionMembersOptions): Promise<OrgPage<OrgMember>>;
}

Mọi method trả dữ liệu đều có overload thứ hai: khi options.withDisplay là literal true, kiểu kết quả là biến thể <true> của các kiểu bên dưới và các field hiển thị luôn có mặt. Khi không có option này, các field hiển thị không tồn tại trên kiểu.

interface OrgReadOptions {
  readonly withDisplay?: boolean;
  readonly language?: string;       // ví dụ "vi-VN"; mặc định là ngôn ngữ của invocation
}
 
interface OrgPageOptions extends OrgReadOptions {
  readonly includeSubDepartments?: boolean;
  readonly accountOnly?: boolean;
  readonly limit?: number;          // mặc định 500, tối đa 2000; 200 khi có withDisplay
  readonly cursor?: string;
}
interface OrgDepartmentMembersOptions extends OrgPageOptions { readonly positionId?: string; }
interface OrgPositionMembersOptions extends OrgPageOptions { readonly departmentId?: string; }
interface OrgManagerOptions extends OrgReadOptions { readonly accountOnly?: boolean; }
interface OrgPersonnelChainOptions extends OrgManagerOptions { readonly departmentId?: string; }
interface OrgTreeOptions extends OrgReadOptions {
  readonly rootId?: string;
  readonly depth?: number;          // 1 đến 100; mặc định lấy mọi cấp
}
interface OrgIsInDepartmentOptions { readonly includeSubDepartments?: boolean; }
interface OrgIsManagerOfOptions {
  readonly departmentId?: string;
  readonly directOnly?: boolean;
}
 
interface OrgPersonnelDisplay {
  readonly name: string;
  readonly email: string | null;
  readonly avatar: string | null;   // URL công khai
  readonly code: string | null;
}
interface OrgNameDisplay { readonly name: string; }
 
type OrgMembership<D extends boolean = false> = {
  readonly personnelId: string;
  readonly departmentId: string;
  readonly positionId: string | null;
  readonly level: number;           // 0 nhân viên, 1 cấp quản lý cao nhất, 2 cao nhì, ...
  readonly primary: boolean;
} & (D extends true
  ? { readonly departmentName: string | null; readonly positionName: string | null }
  : {});
 
type OrgPersonnel<D extends boolean = false> = {
  readonly id: string;
  readonly accountId: string | null;          // null: chưa có tài khoản workspace
  readonly memberships: readonly OrgMembership<D>[];
} & (D extends true ? { readonly display: OrgPersonnelDisplay | null } : {});
 
type OrgMember<D extends boolean = false> = OrgMembership<D> & {
  readonly accountId: string | null;
} & (D extends true ? { readonly display: OrgPersonnelDisplay | null } : {});
 
type OrgDepartment<D extends boolean = false> = {
  readonly id: string;
  readonly parentId: string | null;
  readonly level: number;                     // 1 với phòng ban gốc
  readonly childIds: readonly string[];
  readonly positionIds: readonly string[];
} & (D extends true ? { readonly display: OrgNameDisplay | null } : {});
 
type OrgDepartmentNode<D extends boolean = false> = OrgDepartment<D> & {
  readonly children: readonly OrgDepartmentNode<D>[];
};
 
type OrgPosition<D extends boolean = false> = {
  readonly id: string;
  readonly departmentOnly: boolean;
  readonly departmentIds: readonly string[];
  readonly excludedDepartmentIds: readonly string[];
} & (D extends true ? { readonly display: OrgNameDisplay | null } : {});
 
type OrgManagerTier<D extends boolean = false> = {
  readonly departmentId: string;
  readonly level: number;
  readonly distance: number;                  // 0 phòng xuất phát, 1 phòng cha, ...
  readonly personnelIds: readonly string[];
} & (D extends true ? {
  readonly departmentName: string | null;
  readonly personnel: readonly { readonly id: string; readonly display: OrgPersonnelDisplay | null }[];
} : {});
 
interface OrgManagerChain<D extends boolean = false> {
  readonly fromDepartmentId: string;
  readonly personnelLevel: number;
  readonly tiers: readonly OrgManagerTier<D>[];
  readonly vacantDepartmentIds: readonly string[];
  readonly truncated: boolean;
}
 
interface OrgPage<T> {
  readonly items: readonly T[];
  readonly total: number;
  readonly nextCursor?: string;
}
 
interface OrgGetManyResult<T> {
  readonly items: readonly T[];
  readonly missingIds: readonly string[];
}
export default defineScript(async ({ org, input }) => {
  const order = input as { ownerId: string; departmentId: string };
  // Chỉ cần cấu trúc: trả lời mà không đọc record.
  const [chain] = await org.personnel.managerChain(order.ownerId, {
    departmentId: order.departmentId,
    accountOnly: true,
  });
  const approverIds = chain?.tiers[0]?.personnelIds ?? [];
 
  // Cần tên để hiển thị: thêm tối đa một lần đọc cho mỗi loại dữ liệu.
  const me = await org.me({ withDisplay: true });
  return {
    approverIds,
    greeting: me?.display?.name,
    departments: me?.memberships.map(m => m.departmentName) ?? [],
  };
});

Dữ liệu hiển thị

  • withDisplay thêm display vào nhân sự, phòng ban và vị trí, và thêm departmentName/positionName vào membership. Giá trị là null khi không đọc được record tương ứng, ví dụ record vừa bị xoá.
  • language chọn bản dịch tên phòng ban và vị trí, ví dụ "vi-VN" hoặc "en-US"; tag không có vùng ("vi") khớp với bản dịch đầu tiên của ngôn ngữ đó. Mặc định là ngôn ngữ trong membership của user thực hiện invocation, sau đó đến ngôn ngữ của user, rồi của workspace; nếu không có bản dịch phù hợp thì dùng tên đã lưu.
  • name của nhân sự là họ tên đầy đủ mà workspace hiển thị; email là email tài khoản, nếu không có thì là email công việc; avatar là URL ảnh công khai.
  • Dữ liệu hiển thị chỉ gồm các field trên và mọi thành viên workspace đều xem được, giống như trong ô chọn người dùng. Mỗi lần gọi tốn tối đa một lần đọc cho mỗi loại dữ liệu và mỗi 200 ID; các giá trị vừa đọc được cache.

org.me(options?)

Trả nhân sự của user thực hiện invocation, hoặc null với invocation system hay inbound. Method này tương đương org.personnel.get với invocation.user.membership.personnelId.

org.personnel.get(id, options?) và org.personnel.getMany(ids, options?)

get trả một nhân sự đang hoạt động kèm mọi membership phòng ban, phòng ban chính đứng đầu, hoặc null khi ID không phải nhân sự đang hoạt động của workspace. getMany đọc tối đa 200 ID trong một lời gọi: ID trùng được gộp, items giữ thứ tự request và ID không tồn tại được liệt kê trong missingIds. Nhân sự chưa có tài khoản workspace có accountId: null; người đã rời workspace không được trả về.

org.departments.get(id, options?), tree(options?) và ancestors(id, options?)

  • get trả một phòng ban đang hoạt động hoặc null. childIds theo thứ tự hiển thị; positionIds là các vị trí áp dụng cho phòng ban.
  • tree trả các phòng ban gốc (hoặc chỉ rootId) kèm phòng ban con. depth: 1 chỉ trả phòng gốc; dưới độ sâu yêu cầu, children rỗng nhưng childIds vẫn liệt kê phòng con. Cây có hơn 5000 phòng ban sẽ ném ValidationError; hãy thu hẹp bằng rootId hoặc depth.
  • ancestors trả phòng cha trước, phòng gốc sau cùng.
  • Phòng ban có phòng cha đã ngừng hoạt động hoặc đã xoá được trả như phòng gốc (parentId: null).

org.departments.members(id, options?) và org.positions.members(id, options?)

Trả một trang membership: quản lý trước theo cấp, sau đó đến nhân viên. Nhân sự thuộc nhiều phòng ban trong phạm vi được liệt kê một lần cho mỗi phòng ban. includeSubDepartments thêm mọi phòng con theo thứ tự cây, positionId chỉ giữ người giữ vị trí đó, departmentId giới hạn người giữ vị trí trong một phòng ban, và accountOnly chỉ giữ nhân sự có tài khoản workspace. Truyền nextCursor làm cursor để đọc trang tiếp; total đếm mọi trang. Kích thước trang mặc định 500 và tối đa 2000, hoặc tối đa 200 khi có withDisplay.

org.positions.list(options?) và org.positions.get(id, options?)

Trả các vị trí đang hoạt động, vị trí tạo trước đứng trước. Vị trí chỉ áp dụng cho phòng ban riêng (departmentOnly: true) áp dụng cho departmentIds; vị trí còn lại áp dụng cho mọi phòng ban trừ excludedDepartmentIds.

Quản lý và chuỗi quản lý

level của membership đánh dấu quản lý: 0 là nhân viên, 1 là cấp quản lý cao nhất của phòng ban, 2 là cấp cao nhì, và cứ thế tiếp. Nhiều người có thể cùng cấp.

  • org.departments.managers(id, options?) trả quản lý của riêng phòng ban đó, mỗi cấp một tier, cấp 1 đứng đầu.
  • org.departments.managerChain(id, options?) trả các quản lý phía trên một nhân viên của phòng ban, gần nhất trước, đến tận phòng ban gốc.
  • org.personnel.managerChain(id, options?) trả một chuỗi cho mỗi phòng ban của nhân sự, phòng ban chính đứng đầu; departmentId chỉ giữ chuỗi bắt đầu từ phòng ban đó. Kết quả rỗng với nhân sự không tồn tại.

Chuỗi được tạo theo các quy tắc:

  1. Trong phòng ban xuất phát, quản lý của nhân sự là những người có số cấp nhỏ hơn cấp của chính nhân sự đó (mọi quản lý nếu là nhân viên), gần nhất trước: cấp 3, rồi 2, rồi 1. Trưởng phòng không có quản lý trong phòng của mình.
  2. Sau đó mỗi phòng cha đến tận phòng gốc thêm mọi quản lý của mình, số cấp lớn nhất trước. Mọi quản lý của phòng cha đều đứng trên mọi quản lý của các phòng con.
  3. Mỗi nhân sự chỉ xuất hiện một lần, ở tier gần nhất, và nhân sự xuất phát không bao giờ được liệt kê.
  4. accountOnly: true bỏ qua nhân sự chưa có tài khoản workspace, ví dụ khi chuỗi dùng để chọn người duyệt.
  5. vacantDepartmentIds liệt kê các phòng ban đi qua mà không có quản lý hợp lệ nào; chuỗi tiếp tục với phòng cha của chúng. Phòng của chính trưởng phòng không bị coi là trống.
  6. truncated là true khi một phòng cha đã ngừng hoạt động, nên chuỗi dừng trước phòng gốc thực sự.

Ví dụ, với ban lãnh đạo công ty (CEO cấp 1, phó CEO cấp 2), khối Kỹ thuật bên dưới (giám đốc cấp 1) và phòng Kiểm thử thuộc khối Kỹ thuật (trưởng phòng 1, phó phòng 2, trưởng nhóm 3), chuỗi của một tester là: trưởng nhóm, phó phòng, trưởng phòng, giám đốc khối, phó CEO, CEO. Chuỗi của phó phòng Kiểm thử là: trưởng phòng, giám đốc khối, phó CEO, CEO.

org.isInDepartment(personnelId, departmentId, options?) và org.isManagerOf(managerId, personnelId, options?)

isInDepartment cho biết nhân sự có thuộc phòng ban, hoặc một phòng con của nó khi có includeSubDepartments. isManagerOf cho biết managerId có xuất hiện trong một chuỗi quản lý của personnelId hay không; departmentId chỉ xét chuỗi bắt đầu từ phòng ban đó và directOnly chỉ xét tier gần nhất. Cả hai trả false với ID không tồn tại.

Quy tắc

  • org dùng được trong script, route, record trigger ở cả hai thời điểm, background job và Development Session.
  • org đi theo danh tính mặc định của lần thực thi: lần thực thi có user hoặc danh tính system đều đọc được. Lần thực thi không có user (background job, inbound webhook, hoặc record trigger do một thao tác ghi của hệ thống gây ra) chỉ đọc được khi identity policy đã duyệt của project đặt allowInternalSystem: true; nếu không, mọi lời gọi trừ org.me() ném PermissionDeniedError có details.reason là "IDENTITY_NOT_GRANTED". Khi đó org.me() trả null mà không thực hiện lời gọi nào, vì lần thực thi không có user.
  • Mọi lời gọi trong cùng một lần thực thi đọc cùng một phiên bản cơ cấu tổ chức.
  • ID được SDK kiểm tra và Cogover kiểm tra lại; ID hoặc option không hợp lệ ném ValidationError. Mọi method đều trả về promise, và đối số không hợp lệ làm promise đó bị reject thay vì ném lỗi ngay khi gọi method. Các method get trả null với ID không tồn tại; members, managers, managerChain, ancestors và tree({ rootId }) ném NotFoundError với resource là "department" hoặc "position".
  • Khi không đọc được cơ cấu tổ chức, lời gọi ném CogoverApiError với code ORGANIZATION_UNAVAILABLE.
  • Lần đọc không có withDisplay là một lời gọi cục bộ; lần đọc có withDisplay: true là một capability call (xem Giới hạn của một lần thực thi). Dùng getMany hoặc members thay vì gọi get trong vòng lặp: mỗi lời gọi tính một lần.

© 2026 Cogover LLC