
CX
29/9/2026
Tóm tắt
blog.share_blog
@cogover/sdk: Cơ cấu tổ chứcTrang này thuộc API reference của @cogover/sdk.
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) ?? [],
};
});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.
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.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.
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:
3, rồi
2, rồi 1. Trưởng phòng không có quản lý trong phòng của mình.
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.
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.
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.
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.
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".
CogoverApiError với code
ORGANIZATION_UNAVAILABLE.
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.

blog.blogs_related
Bắt đầu đổi mới phương thức vận hành và tự chủ hệ thống quản trị công việc của bạn
CÔNG TY CỔ PHẦN STRINGEE
Văn phòng