Tags Service

The labeling and classification engine for Quinyx WFM — attach metadata to shifts, tasks, absences, and punches

services/manage/tags Java 21 · Spring Boot 3.x MySQL · Kafka · Hazelcast

Three Core Concepts

The building blocks of the tagging system

Tag Categories

The folders that organize tags. Each customer account defines their own categories.

Name — e.g. "Store Locations", "Cost Centers"
Tag Type — one of 4 types (see below)
Color — hex code for visual display in UI
External ID — for integration partners
Tag Types
COST_CENTER PROJECT ACCOUNT EXTENDED

Tags

The actual labels within a category. Each tag belongs to exactly one category.

Name — e.g. "Downtown Store", "Night Shift Bonus"
Code — short identifier for quick reference
Validity dates — start & end for time-limited tags
Unique scheduling — exclusivity flag
Coordinates — lat/lng + radius for geofencing
Periods — operating hours/time budgets
Custom fields — flexible key-value metadata

Tag Connections

The glue that links tags to schedule items — shifts, tasks, absences, and punches.

tagId — which tag is connected
connectionId — which item it's attached to
connectionType — what kind of item
Connection Types
SHIFT SHIFT_TYPE SHIFT_TASK SHIFT_ABSENCE ABSENCE_TASK PUNCH BASE_SCHEDULE_SHIFT BASE_SCHEDULE_TASK *_DELETED variants

Think of it like this: A Tag Category is a drawer (e.g. "Cost Centers"). A Tag is a sticker in that drawer (e.g. "Marketing Dept"). A Tag Connection is the act of sticking that sticker onto a shift, task, or punch on the schedule.


Data Model

5 tables · MySQL
tag_categories
PK id INT
name VARCHAR(255)
tagType VARCHAR(40)
color VARCHAR(7)
externalId VARCHAR(60)
tags
PK id INT
FK tagCategoryId INT
name VARCHAR(255)
code VARCHAR(50)
externalId VARCHAR(60)
information TEXT
uniqueScheduling TINYINT
periods TEXT (JSON)
coordinates TEXT (JSON)
customFields TEXT (JSON)
startDate DATE
endDate DATE
tag_connections
PK id INT
FK tagId INT
connectionId VARCHAR(255)
tagConnectionType VARCHAR(40)
maps to domain
maps to group
tag_categories_mapping
PK id INT
FK categoryId INT
domainId INT
tags_mapping
PK id INT
FK tagId INT
groupId INT

Key relationships: Categories are mapped to domains via tag_categories_mapping. Tags are mapped to groups (units) via tags_mapping. Connections link tags to schedule items. The mapping tables enable multi-tenancy — one tag definition can be shared across groups in the hierarchy.


Organization Hierarchy & Scope

How tags inherit through the org structure
DOMAIN
Acme Corp
UNIT
Stockholm Store
UNIT
Gothenburg Store
SECTION
Deli Dept
SECTION
Bakery Dept

Scope Rules

Categories
Created & managed at domain level only. Requires ACCOUNT permission.
Tags
Created at domain or unit level. Cannot be created at section level. Requires TAGS or SETTINGS permission.
Connections
Created by internal services (schedule, shifts). Managed automatically when shifts/tasks are created or edited.
Reading tags
Hierarchy-aware: querying a unit returns its own tags AND parent domain tags. Tags flow downward.

Who Talks to Tags?

Consumers & communication patterns

Quinyx Web UI

Managers create & edit tags and categories through the web application.

External API

Schedule Service

Attaches tags to shifts & tasks during scheduling. Reads tag info for display.

Internal API

Other Services

Create, move, & delete tag connections asynchronously via event commands.

Kafka Events

Integrations

Payroll & ERP systems sync tags using external IDs instead of Quinyx IDs.

Integration API

API Endpoints

3 API layers
External API For Quinyx Web UI — authenticated, permission-checked
MethodEndpointDescription
GET/external/groups/{groupId}/tagsList all tags for a group (paginated, filterable by name)
GET/external/groups/{groupId}/tags/{tagId}Get a specific tag
POST/external/groups/{groupId}/tagsCreate a new tag (unit or domain only)
PUT/external/groups/{groupId}/tags/{tagId}Update an existing tag
DELETE/external/groups/{groupId}/tags/{tagId}Delete a tag
GET/external/accounts/{groupId}/categoriesList all tag categories for an account
POST/external/accounts/{groupId}/categoriesCreate a tag category (domain-level only)
PUT/external/accounts/{groupId}/categories/{id}Update a tag category
DELETE/external/accounts/{groupId}/categories/{id}Delete a tag category
GET/external/groups/{groupId}/tag-info/by-idGet tag info by IDs (max 1000)
GET/external/groups/{groupId}/tag-info/searchSearch tags by name (paginated)
Internal API For other Quinyx services — service-to-service
MethodEndpointDescription
POST/internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id}Replace tag connections (upsert pattern)
GET/internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id}Get connections for a specific item
POST/internal/groups/{groupId}/tag-connections/types/{type}/lookupBulk lookup connections (streaming response)
DELETE/internal/groups/{groupId}/tag-connections/types/{type}/connection-ids/{id}Delete connections for an item
POST/internal/groups/{groupId}/tag-connections/copyCopy tag connections
POST/internal/groups/{groupId}/tag-connections/moveMove tag connections
Integration API For external systems — uses external IDs
MethodEndpointDescription
GET/integration/categoriesList all tag categories
GET/integration/categories/{extId}Get a category by external ID
GET/integration/v2/categories/{extId}/tagsList tags in category (cursor-paginated)
GET/integration/categories/{catExtId}/tags/{tagExtId}Get a tag by external IDs
POST/integration/categories/{extId}/tagsCreate a tag via external IDs
PUT/integration/categories/{catExtId}/tags/{tagExtId}Update a tag via external IDs
DELETE/integration/categories/{catExtId}/tags/{tagExtId}Delete a tag via external IDs

Event-Driven Patterns (Kafka)

Asynchronous communication

Incoming Commands

Events consumed by the Tags service

Create Tag Connection
Attaches tags to a new shift/task/punch
Move Tag Connections
Reassigns connections when shifts move
Delete Customer Data
GDPR / account deletion cleanup
Delete Group Data
Org restructure cleanup
Delete Time Punch
Punch correction cleanup

Error Handling (DLQ)

When event processing fails

Skipped silently: Tags module not enabled, or validation fails (BadRequest, ForbiddenException)
Sent to DLQ: Unexpected errors (DB failure, network issues) are published to a dead-letter topic for investigation
DLQ publishers: FailedCreateTagConnection, FailedGroupDeleted, FailedMoveTagConnections

Key design choice: Missing group or unit IDs in a command are treated as malformed and throw an exception — they don't silently fail.


Business Rules

Key constraints & behaviors to know

Domain-level category management

Tag categories can only be created, updated, and deleted at the domain (account) level. Individual stores/units cannot define their own category types.

Unit or domain tag creation only

Tags can be created at unit or domain level — not at section level. The API returns 400 Bad Request if attempted at a section.

Soft-delete audit trail

When items are deleted, tag connections change type to *_DELETED variants (e.g. SHIFTSHIFT_DELETED) — preserving history.

Unique scheduling enforcement

Tags with uniqueScheduling = true prevent multiple shifts from using the same tag simultaneously — enforcing resource exclusivity.

Validity date windows

Tags can have start and end dates. Time-limited tags are used for seasonal projects, temporary cost centers, or campaign tracking.

Hierarchy-aware tag reads

Querying tags for a group returns tags from the group itself AND all parents up to the domain. Tags flow downward through inheritance.

Replace (not append) connection pattern

When updating tag connections for an item, the system deletes all existing connections and creates new ones — it's a full replace, not an append.

Validation no-duplicate categories

Tag categories must be unique per domain. Validation also enforces field length limits on name, code, external ID, and information fields.


Where Can Users Use Tags Today?

Web, Mobile & Reports
Tag Settings & Management Full UI
Create & Edit Categories
Name, type, color, external ID
Create, Edit, Copy & Delete Tags
All tag properties supported
Coordinates & Geofencing
Lat/lng, radius in meters
Periods, Custom Fields & Dates
Time budgets, key-value pairs, validity
Where Do Tags Appear?
Area Can Add Tags? Can View Tags? How It Works
Shift Types
YES YES Managers pick tags when editing a shift type. This is the main way tags get onto the schedule.
Shifts on Schedule
NO YES Shift detail panel shows tags, but they're inherited from the shift type — can't add per-shift tags from the UI.
Time Punches
NO YES Punch detail panel shows tags when present — display only, inherited from associated shift.
Absences (Timecard)
NO YES Absence rows in the timecard show tags — display only.
Tasks (within shifts)
NO NO The backend supports tagging individual tasks, but no UI exists for it.
Base Schedule
NO NO Backend supports base schedule tags, but no UI exists for viewing or adding them.
Reports
YES Tags Follow-Up report — compare budgeted vs. assigned vs. punched hours per tag over a date range.
Platform Coverage

Manager Web Portal

Full tag management + viewing on schedule, punches, absences. Tags Follow-Up report.

MANAGE + VIEW

Mobile Apps (Android & iOS)

Tags shown on shifts, tasks, and stories. Tag coordinates mapped to locations. No tag editing.

VIEW ONLY

Employee Web Portal

Feature flag exists (mobile_time_tag) but no tag components built. Employees can't see tags.

NOT AVAILABLE

Untapped Capabilities

Backend ready, no UI yet

The backend supports 12 connection types for tagging items, but the UI only lets managers assign tags through one path: Shift Types. Everything else is either view-only or invisible. These are areas where new UI could unlock value without any backend work.

Per-Shift Tag Editing

Today: shifts inherit tags from their shift type. Managers can't override or add tags to a specific shift. The backend already supports it.

Task-Level Tagging

The backend supports tags on individual tasks within shifts, but there's no UI anywhere to add, view, or manage task tags.

Base Schedule Tags

Tags can be attached to base schedule shifts and tasks in the backend, but the base schedule UI doesn't show or edit tags at all.

Employee View of Tags

A feature flag (mobile_time_tag) exists in the employee portal, but no UI was built. Employees currently can't see tags on their shifts.

Punch Tag Editing

Punches display inherited tags but managers can't assign tags directly to punches. The backend supports PUNCH connections.

Absence Tag Editing

Absence shifts in the timecard show tags (read-only). The backend supports both SHIFT_ABSENCE and SHIFT_ABSENCE_TASK connections — no editing UI.

How Tags Flow Today
Manager creates
Tag in Settings
Manager assigns
Tag to Shift Type
Shifts inherit tags
automatically
Punches & Absences
display tags (read-only)
The only active editing point is at the Shift Type level — everything downstream is inherited and read-only.