Factorizer/Cluster ERP WikiCluster ERP Türkçe
Home / Business modules

Explanation

Intranet access, audience, and retention

How the intranet scopes personal records, trims content by access grants, builds audience segments, resolves the acting person, and retains analytics.

This page explains the cross-cutting rules of the intranet module. src/cluster_erp/modules/intranet/access.py scopes personal records and resolves the content viewer, src/cluster_erp/modules/intranet/context.py resolves the acting person, src/cluster_erp/modules/intranet/retention_handlers.py ages out analytics events, and src/cluster_erp/modules/intranet/validation.py normalises form input. Access grants and audience segments live in src/cluster_erp/modules/intranet/content/services.py.

In short

  • Access and audience are two separate axes. Access grants decide who may see content; segments decide who is targeted (src/cluster_erp/modules/intranet/models.py:8-11).
  • Personal records (leave, requests, deliveries, the inbox) use three tiers: read_all sees everything, others see their own records and their team's, and a caller without a party sees nothing (src/cluster_erp/modules/intranet/access.py:1-21).
  • The acting person is the party of the signed-in user's employee link (src/cluster_erp/modules/intranet/context.py:1-30).
  • Read analytics of a post are shown only in aggregate, with unique counts below a minimum group size suppressed (src/cluster_erp/modules/intranet/content/services.py:1327-1355).

How it works

Acting person

resolve_party_for_user reads employee_id from the signed-in user and asks GetPartyForEmployee for the party. A login without an employee link resolves to no party (src/cluster_erp/modules/intranet/context.py:20-30). For personal writes, effective_party_guarded uses the caller's own party; naming another party needs the privileged permission the route passes, for example intranet_leave_request.read_all (src/cluster_erp/modules/intranet/access.py:147-170). A named party must also be an active employee (src/cluster_erp/modules/intranet/access.py:124-144).

Row scope for personal records

resolve_scope returns a scope from the core row-scope helpers (src/cluster_erp/modules/intranet/access.py:189-220):

Caller Sees Source
holds <entity>.read_all every record src/cluster_erp/modules/intranet/access.py:209-210
has a party own records and the team, read live through GetTeamPartyIds src/cluster_erp/modules/intranet/access.py:173-186, src/cluster_erp/modules/intranet/access.py:211-218
has no party nothing src/cluster_erp/modules/intranet/access.py:213-216

The resolved scope is also published to the database row-level security backstop (src/cluster_erp/modules/intranet/access.py:202-219). The mechanism is described on field and row access and multi-tenancy. Official deliveries add one rule: a confidential delivery is visible only to its recipient and to read_all holders (src/cluster_erp/modules/intranet/delivery/services.py:40-64).

Access grants for content

An access grant (IntranetAclGrant) attaches to a space or a post and names a realm: everyone, role, party, or segment, with can_view, can_edit, and can_publish flags (src/cluster_erp/modules/intranet/models.py:78-79, src/cluster_erp/modules/intranet/models.py:379-408).

  • Listing joins the grant table as an SQL predicate. A post with no view grants on itself or its space is visible to every reader; otherwise the reader must match a grant by party, role, or segment (src/cluster_erp/modules/intranet/content/services.py:585-626).
  • resolve_acl_viewer collects the reader's party, role ids, and segment ids; holders of intranet_content.read_all skip the trimming (src/cluster_erp/modules/intranet/access.py:78-121).
  • Publish grants narrow the editorial permission: with no publish grant on the space or post, the permission alone decides; with grants, the actor must match one (src/cluster_erp/modules/intranet/content/services.py:628-660).
  • Grants are set by an upsert per subject, realm, and id, and removed by setting deleted_at (src/cluster_erp/modules/intranet/content/services.py:544-583). The routes are GET, POST /acl/ and DELETE /acl/{grant_id} (src/cluster_erp/modules/intranet/content/routes.py:515-546).

Audience segments

flowchart LR
 F["audience builder fields"] --> A["_assemble_rule"]
 A --> R["rule include and exclude"]
 R --> B["rebuild_members"]
 P["DirectoryService._live_people"] --> B
 Q["GetProjectMemberPartyIds"] --> B
 B --> M["IntranetSegmentMember rows"]
 M --> U["publication recipients, ACL segment realm, event audience"]

In prose: the audience builder stores a rule with include and exclude parts over departments, organisations, named parties, projects, job titles, and locations (src/cluster_erp/modules/intranet/content/services.py:185-205). A person matches when any filled criterion matches; titles and locations compare without case and surrounding spaces; an empty rule matches nobody (src/cluster_erp/modules/intranet/content/services.py:135-179). rebuild_members retires the dynamic members, reads the live employees, resolves project teams through GetProjectMemberPartyIds, and inserts one member row per match. Manual members are kept (src/cluster_erp/modules/intranet/content/services.py:426-506). Publishing a post rebuilds its segment first (src/cluster_erp/modules/intranet/content/services.py:1022-1035). POST /segments/{id}/rebuild and POST /segments/rebuild-all rebuild on demand (src/cluster_erp/modules/intranet/content/routes.py:604-633).

Analytics floor and retention

  • IntranetSettings.min_group_size (default 5) is the floor below which unique-reader counts are suppressed; totals are always shown (src/cluster_erp/modules/intranet/models.py:155-200, src/cluster_erp/modules/intranet/content/services.py:1327-1355).
  • On each RetentionSweep, sweep_retention deletes intranet_analytics_events rows older than analytics_retention_days (default 730). A window of zero or less keeps everything (src/cluster_erp/modules/intranet/retention_handlers.py:1-55). The general retention mechanism is on records module.

Input normalisation

scrub_blank_strings turns blank strings into empty values before validation, so that untouched form fields do not collide with live-row unique indexes (src/cluster_erp/modules/intranet/validation.py:1-25).

Why it is built this way

  • The model docstring calls the merging of access and targeting the most repeated bug of the field, and states that the acknowledgement store is kept apart from analytics (src/cluster_erp/modules/intranet/models.py:8-19).
  • The access.py docstring states that a coarse read permission alone would let anyone read every payslip (src/cluster_erp/modules/intranet/access.py:3-5).
  • The retention docstring states that analytics events have no deleted_at, so the generic purge never saw them (src/cluster_erp/modules/intranet/retention_handlers.py:1-8).

Rules and boundaries

  • Physical deletion in this module is limited to analytics events past their window (src/cluster_erp/modules/intranet/retention_handlers.py:44-52). The project's written engineering rules allow retention purges of event rows whose retention period has ended.
  • Decision actions need elevated permissions on top of .update (src/cluster_erp/modules/intranet/access.py:66-75).

Interaction with other parts

Contract Purpose Source
GetPartyForEmployee acting person src/cluster_erp/modules/intranet/context.py:25-27
GetTeamPartyIds live team closure for the scope src/cluster_erp/modules/intranet/access.py:181-186
GetProjectMemberPartyIds project teams in segment rules src/cluster_erp/modules/intranet/content/services.py:494-502
RetentionSweep daily retention pass src/cluster_erp/modules/intranet/module.py:224-229

Limits and open points

  • Segment membership is a snapshot; it changes on a rebuild or on publish, not on every employee change (src/cluster_erp/modules/intranet/content/services.py:426-433, src/cluster_erp/modules/intranet/content/services.py:1027-1035).
  • Directory names come from one party search with a limit of 10000 (src/cluster_erp/modules/intranet/directory/services.py:74-80).

Terms introduced

Term Code identifier Definition Turkish term Source
access grant IntranetAclGrant A view, edit, or publish right on a space or post for everyone, a role, a party, or a segment. Erişim izni src/cluster_erp/modules/intranet/models.py:379
row scope resolve_scope The set of parties whose records a caller may see. satır kapsamı src/cluster_erp/modules/intranet/access.py:189
minimum group size min_group_size The floor below which unique reader counts are hidden. Asgari grup büyüklüğü src/cluster_erp/modules/intranet/models.py:155-200

Verified against commit 92f8051 on 2026-11-13.

Verified against commit 92f8051 · 2026-11-13