Skip to content
Open
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
74 changes: 74 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,93 @@ login:
# url: 'https://url-to-validate-login-scans/validate' # URL to validate CampusID login scans
# api_key: 'replace_me'

global_return_directives:
- binId: fatal
color: red
priority: 100
label:
de: "Rote Regal"
en: "red shelf"
message:
de: "Bitte wenden Sie sich an einem Bibliothekmitarbeiter."
en: "Please go to the main desk"
sort_order: 1
# when defines when a book is assigned to the bin
# any if only one condition must be true
# all if all conditions must be true
# and the rules can be:
# - in to check if the field value is in the specified list
# - not_in to check if the field value is not in the specified list
# - equals to check if the field value equals the specified value
# - to check if the field exists
when:
any:
- field: process_type
in: ["LOST_LOAN", "LOST_AND_PAID", "CLAIMED_RETURNED_LOAN", "MISSING"]
all:
- field: library_code
in: ["TWIST", "OTHER"]
- field: process_type
in: ["HOLD_SHELF"]
- binId: warning
color: yellow
label:
de: "Gelbe Regal"
en: "Yellow shelf"
message:
de: "Bitte legen Sie das Medium in das blaue Regal."
en: "Please place this item in the blue shelf."
sort_order: 1
when:
any:
- field: process_type
in: ["HOLD_SHELF", "LOST_AND_PAID", "CLAIMED_RETURNED_LOAN", "MISSING"]
- field: destination_library_code
exists: true
- field: has_request
equal: true
- field: process_type
not_in: ["LOST_LOAN", "MISSING"]
- binId: main
color: blue
label:
de: "Blaue Regal"
en: "Blue shelf"
message:
de: "Bitte legen Sie das Medium in das blaue Regal."
en: "Please place this item in the blue shelf."
sort_order: 1
when:
always: true

checkout:
# Optional image for the checkout list empty state; supports /absolute/path or https:// URL
#no_media_found_image: '/branding/place_book.png'
profiles:
# The profile id is used via the checkout_profile_id URL param on checkout pages
# Each return directive must at least define one return directive with binId: main,
# and to link the bin just add the bin if to the list of return_directives
# Untouched configuration of the bin will default to the logic defined in global return directives
- id: terminal1
type: alma
library: MAIN_LIBRARY
circulation_desk: SELF-CHECKOUT-CIRC1
return_directives:
- binId: main
- binId: warning
- binId: fatal
label:
de: "Blaue Regal"
en: "Blue shelf"
message:
de: "Bitte legen Sie das Medium in das blaue Regal."
en: "Please place this item in the blue shelf."
- id: terminal2
type: alma
library: MAIN_LIBRARY
circulation_desk: SELF-CHECKOUT-CIRC2
return_directives:
- binId: main

# Security gate display configuration
gate:
Expand Down
9 changes: 9 additions & 0 deletions src/lib/lms/lms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,17 @@ export const MediaItemSchema = v.object({
publisher: v.optional(v.string()),
cover: v.optional(v.string()),
status: v.optional(v.string()),
process_type: v.optional(v.string()),
library: v.optional(v.string()),
library_code: v.optional(v.string()),
destination_library_code: v.optional(v.string()),
location: v.optional(v.string()),
location_code: v.optional(v.string()),
destination_location_code: v.optional(v.string()),
circulation_desk: v.optional(v.string()),
circulation_desk_code: v.optional(v.string()),
has_request: v.optional(v.boolean()),
request_type: v.optional(v.string()),
shelfmark: v.optional(v.string()),
loanDate: v.optional(v.string()),
dueDate: v.optional(v.string()),
Expand Down
104 changes: 101 additions & 3 deletions src/lib/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,41 @@ export interface LoginValidationConfig {
campus_id?: LoginValidationCampusIdConfig;
}

export interface ShelfCheck {
check: string,
value: string
}

export interface ReturnRule {
field: string;
in?: string[];
not_in?: string[];
equals?: string | boolean | number;
exists?: boolean;
}

export interface ReturnCondition {
any?: ReturnRule[];
all?: ReturnRule[];
always?: boolean;
}

export interface ReturnDirective {
binId: string;
priority: number;
message: Record<string, string>;
label: Record<string, string>;
color: string;
sort_order: number;
when?: ReturnCondition
}

export interface CheckoutProfileConfig {
id: string;
type?: string;
library: string;
circulation_desk: string;
return_directives: ReturnDirective[]
}

export interface CheckoutConfig {
Expand Down Expand Up @@ -96,6 +126,7 @@ export interface LMSConfig {
checkout?: CheckoutConfig;
theme?: ThemeConfig;
middleware_instances: MiddlewareInstanceConfig[];
global_return_directives?: ReturnDirective[];
}

const DEFAULT_LOGIN_MODE: LoginMode = 'username_password';
Expand Down Expand Up @@ -130,6 +161,26 @@ const DEFAULT_THEME_CONFIG: ThemeConfig = {
},
logo: undefined
};
const DEFAULT_RETURN_DIRECTIVE_COLOR_CONFIG: string = "blue";
const DEFAULT_RETURN_DIRECTIVE_MESSAGE_CONFIG: Record<string, string> = {
de: "Bitte legen Sie das Medium in das blaue Regal.",
en: "lease place this item in the blue shelf."
}
const DEFAULT_RETURN_DIRECTIVE_LABEL_CONFIG: Record<string, string> = {
de: "Blaue Regal",
en: "Blue shelf"
}
const DEFAULT_RETURN_DIRECTIVES_CONFIG: ReturnDirective[] = [
{
binId: "main",
priority: 0,
message: DEFAULT_RETURN_DIRECTIVE_MESSAGE_CONFIG,
label: DEFAULT_RETURN_DIRECTIVE_LABEL_CONFIG,
color: DEFAULT_RETURN_DIRECTIVE_COLOR_CONFIG,
sort_order: 2,
when: {always: true}
}
]

function normalizeCssColor(value: unknown): string | undefined {
if (typeof value !== 'string') return undefined;
Expand Down Expand Up @@ -220,6 +271,44 @@ function parseTaggingConfig(data: LMSConfig): TaggingConfig {
};
}

function parseGlobalReturnDirectiveConfig(data: LMSConfig): ReturnDirective[] {
if (data.global_return_directives === undefined) return DEFAULT_RETURN_DIRECTIVES_CONFIG;

return data.global_return_directives.map((returnDirective) => {
return {
binId: returnDirective.binId,
priority: returnDirective.priority,
label: returnDirective.label,
message: returnDirective.message,
color: returnDirective.color,
when: returnDirective.when,
sort_order: returnDirective.sort_order
}
})
}

function parseSpecialReturnDirectiveConfig(global: ReturnDirective[], override: Record<string, any>): ReturnDirective {
const globalReturnDirectiveOfBin = global.find((returnDirective) => returnDirective.binId === override.binId);
if (globalReturnDirectiveOfBin === undefined) {
throw new Error(`Global return directive not found for binId ${override.binId}`);
}
const priority = override.priority === undefined ? globalReturnDirectiveOfBin.priority: override.priority;
const label = override.label === undefined ? globalReturnDirectiveOfBin.label : override.label;
const message = override.message === undefined ? globalReturnDirectiveOfBin.message : override.message;
const color = override.color === undefined ? globalReturnDirectiveOfBin.color : override.color;
const sortOrder = override.sort_order === undefined ? globalReturnDirectiveOfBin.sort_order : override.sort_order;
const when = override.when === undefined ? globalReturnDirectiveOfBin.when : override.when;
return {
binId: override.binId,
priority: priority,
label: label,
message: message,
color: color,
when: when,
sort_order: sortOrder
}
}

function parseCheckoutProfiles(data: LMSConfig): CheckoutProfileConfig[] {
if (data.checkout?.profiles === undefined) return DEFAULT_CHECKOUT_CONFIG.profiles ?? [];

Expand All @@ -228,6 +317,7 @@ function parseCheckoutProfiles(data: LMSConfig): CheckoutProfileConfig[] {
}

const lmsType = typeof data.lms?.type === 'string' ? data.lms.type : '';
const globalReturnDirectives = parseGlobalReturnDirectiveConfig(data);

return data.checkout.profiles.map((profile, index) => {
if (!profile || typeof profile !== 'object') {
Expand All @@ -236,9 +326,16 @@ function parseCheckoutProfiles(data: LMSConfig): CheckoutProfileConfig[] {

const id = typeof profile.id === 'string' ? profile.id.trim() : '';
const library = typeof profile.library === 'string' ? profile.library.trim() : '';
const circulationDesk =
typeof profile.circulation_desk === 'string' ? profile.circulation_desk.trim() : '';
const circulationDesk = typeof profile.circulation_desk === 'string' ? profile.circulation_desk.trim() : '';
const type = typeof profile.type === 'string' ? profile.type.trim() : lmsType;
var returnDirectives;
if (profile.return_directives === undefined) {
returnDirectives = globalReturnDirectives;
} else {
returnDirectives = profile.return_directives.map((returnDirective) => {
return parseSpecialReturnDirectiveConfig(globalReturnDirectives, returnDirective)
});
}

if (!id || !library || !circulationDesk) {
throw new Error(
Expand All @@ -255,7 +352,8 @@ function parseCheckoutProfiles(data: LMSConfig): CheckoutProfileConfig[] {
id,
type,
library,
circulation_desk: circulationDesk
circulation_desk: circulationDesk,
return_directives: returnDirectives
};
});
}
Expand Down
Loading