Add Goals module with CRUD functionality and OpenAPI documentation

This commit is contained in:
Jeppe Bundgaard
2026-01-26 17:55:52 +01:00
parent 9c236afdeb
commit bf16481b29
3 changed files with 400 additions and 58 deletions
+252 -58
View File
@@ -70,6 +70,8 @@ tags:
description: Role and permission management
- name: Self-Serve
description: Self-serve lane operations and questions
- name: Goals
description: Department goals management
paths:
# Authentication Endpoints
@@ -2574,6 +2576,7 @@ paths:
items:
$ref: '#/components/schemas/Booking'
/order-bookings:
get:
tags:
@@ -2588,89 +2591,205 @@ paths:
responses:
'200':
description: Success
# Goals Endpoints
/goals/department:
get:
tags:
- Goals
summary: List or get department goals
description: |
Retrieve a list of department goals or a single goal when `id` is provided.
Access control:
- A user may only access goals where the goal's `departments` set is a subset of the user's departments.
- Users with the `superuser` permission may access all goals.
operationId: listDepartmentGoals
parameters:
- name: id
in: query
required: false
schema: { type: integer }
description: When provided, returns the single goal with this id (if accessible)
- $ref: '#/components/parameters/PageParam'
- $ref: '#/components/parameters/PerPageParam'
- $ref: '#/components/parameters/SearchParam'
- $ref: '#/components/parameters/FiltersParam'
responses:
'200':
description: Goals retrieved successfully
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/DepartmentGoal'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
post:
tags:
- Bookings
summary: Create order booking
operationId: createOrderBooking
- Goals
summary: Create department goal
description: |
Create a new department goal.
Access control:
- The provided `departments` must be a subset of the user's departments unless the user has `superuser`.
operationId: createDepartmentGoal
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [department, reg_1, datetime, items]
properties:
customer_number: {type: integer}
department: {type: integer}
reg_1: {type: string}
reg_2: {type: string}
reg_3: {type: string}
datetime: {type: string, format: date-time}
note: {type: string}
reference: {type: string}
po: {type: string}
pickup: {type: boolean}
items:
type: array
items:
type: object
required: [id, quantity]
properties:
id: {type: integer}
quantity: {type: integer}
$ref: '#/components/schemas/DepartmentGoalCreate'
responses:
'200':
description: Success
'201':
description: Department goal created successfully
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentGoal'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
put:
tags:
- Bookings
summary: Update order booking
operationId: updateOrderBooking
- Goals
summary: Update department goal
description: |
Update an existing department goal by `id`.
Access control:
- The creator (`created_by`) may update regardless of department membership.
- Otherwise the user must satisfy the same subset rule as for read access, and any new `departments` provided must also be a subset unless the user has `superuser`.
operationId: updateDepartmentGoal
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id]
properties:
id: {type: integer}
customer_number: {type: integer}
department: {type: integer}
reg_1: {type: string}
reg_2: {type: string}
reg_3: {type: string}
datetime: {type: string, format: date-time}
note: {type: string}
reference: {type: string}
po: {type: string}
pickup: {type: boolean}
order_id: {type: integer}
items:
type: array
items:
type: object
required: [id, quantity]
properties:
id: {type: integer}
quantity: {type: integer}
$ref: '#/components/schemas/DepartmentGoalUpdate'
responses:
'200':
description: Success
description: Department goal updated successfully
content:
application/json:
schema:
$ref: '#/components/schemas/DepartmentGoal'
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
delete:
tags:
- Bookings
summary: Delete order booking
operationId: deleteOrderBooking
- Goals
summary: Delete department goal
description: |
Delete a department goal by `id`.
Access control:
- The creator (`created_by`) may delete regardless of department membership.
- Otherwise the user must satisfy the subset rule or have `superuser`.
operationId: deleteDepartmentGoal
parameters:
- name: id
in: query
required: true
schema: {type: integer}
schema: { type: integer }
description: ID of the goal to delete
responses:
'200':
description: Success
description: Department goal deleted successfully
'400': { $ref: '#/components/responses/BadRequest' }
'401': { $ref: '#/components/responses/Unauthorized' }
'403': { $ref: '#/components/responses/Forbidden' }
'404': { $ref: '#/components/responses/NotFound' }
'/order-bookings':
post:
tags:
- Bookings
summary: Create order booking
operationId: createOrderBooking
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [department, reg_1, datetime, items]
properties:
customer_number: {type: integer}
department: {type: integer}
reg_1: {type: string}
reg_2: {type: string}
reg_3: {type: string}
datetime: {type: string, format: date-time}
note: {type: string}
reference: {type: string}
po: {type: string}
pickup: {type: boolean}
items:
type: array
items:
type: object
required: [id, quantity]
properties:
id: {type: integer}
quantity: {type: integer}
responses:
'200':
description: Success
put:
tags:
- Bookings
summary: Update order booking
operationId: updateOrderBooking
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [id]
properties:
id: {type: integer}
customer_number: {type: integer}
department: {type: integer}
reg_1: {type: string}
reg_2: {type: string}
reg_3: {type: string}
datetime: {type: string, format: date-time}
note: {type: string}
reference: {type: string}
po: {type: string}
pickup: {type: boolean}
order_id: {type: integer}
items:
type: array
items:
type: object
required: [id, quantity]
properties:
id: {type: integer}
quantity: {type: integer}
responses:
'200':
description: Success
delete:
tags:
- Bookings
summary: Delete order booking
operationId: deleteOrderBooking
parameters:
- name: id
in: query
required: true
schema: {type: integer}
responses:
'200':
description: Success
/order-bookings/complete:
post:
@@ -5966,3 +6085,78 @@ components:
description:
type: string
description: Human-readable description
GoalsCriteria:
type: object
description: Goal evaluation criteria
properties:
type:
type: string
description: Criteria type (e.g., PRODUCT)
example: PRODUCT
target:
type: number
description: Target value for the goal
example: 100
start:
type: string
format: date-time
description: Start of the evaluation window (ISO 8601)
end:
type: string
format: date-time
description: End of the evaluation window (ISO 8601)
users:
type: array
description: List of user customer numbers included in the criteria
items: { type: integer }
departments:
type: array
description: List of department IDs included in the criteria
items: { type: integer }
products:
type: array
description: List of product IDs included in the criteria
items: { type: integer }
DepartmentGoal:
type: object
properties:
id:
type: integer
created_by:
type: integer
description: ID of the user who created the goal
departments:
type: array
items: { type: integer }
criteria:
$ref: '#/components/schemas/GoalsCriteria'
created_at:
type: string
description: Creation timestamp
updated_at:
type: string
description: Update timestamp
DepartmentGoalCreate:
type: object
required: [departments, criteria]
properties:
departments:
type: array
items: { type: integer }
criteria:
$ref: '#/components/schemas/GoalsCriteria'
DepartmentGoalUpdate:
type: object
required: [id]
properties:
id:
type: integer
departments:
type: array
items: { type: integer }
criteria:
$ref: '#/components/schemas/GoalsCriteria'
@@ -17,6 +17,7 @@ import {Roles} from "@/components/session/token/SessionUser/Objects/Roles.vue";
import {Permissions} from "@/components/session/token/SessionUser/Objects/Permissions.vue";
import {CollectedOrderInvoices} from "@/components/session/token/SessionUser/Objects/CollectedOrderInvoices.vue";
import {DepartmentDailyReports} from "@/components/session/token/SessionUser/Objects/DepartmentDailyReports.vue";
import {Goals} from "@/components/session/token/SessionUser/Objects/Goals.vue";
import {Notifications} from "@/components/session/token/SessionUser/Objects/Notifications.vue";
import {Bookings} from "@/components/session/token/SessionUser/Objects/Bookings.vue";
import {Orders} from "@/components/session/token/SessionUser/Objects/Orders.vue";
@@ -296,6 +297,7 @@ export const SessionUser = {
permissions: Permissions,
collectedOrderInvoices: CollectedOrderInvoices,
department_daily_reports: DepartmentDailyReports,
goals: Goals,
notifications: Notifications,
orders: Orders,
forms: Forms,
@@ -0,0 +1,146 @@
<script>
import { ObjectsGlobal } from "@/components/session/token/SessionUser/Objects/ObjectsGlobal.vue";
import { authenticatedRequest } from "@/components/session/authenticatedRequest.vue";
/**
* The Goals object
*/
export const Goals = {
meta: {
title: "Mål",
icon: "fas fa-bullseye",
description: "Afdelingsmål",
endpoint: "/goals/department",
labels: {
single: "mål",
multiple: "mål"
},
},
columns: {
id: {
label: "ID",
type: "number",
sortable: true,
creation: {
required: false
}
},
created_by: {
label: "Oprettet af",
type: "number",
sortable: true,
creation: {
required: false
}
},
departments: {
label: "Afdelinger",
type: "array",
sortable: true,
creation: {
required: true
}
},
criteria: {
label: "Kriterier",
type: "object",
sortable: false,
creation: {
required: true
}
},
created_at: {
label: "Oprettet",
type: "date",
sortable: true,
creation: {
required: false
}
},
updated_at: {
label: "Opdateret",
type: "date",
sortable: true,
creation: {
required: false
}
},
},
add: async (departments, criteria) => {
return ObjectsGlobal.add.object(
Goals.meta.endpoint,
{
departments: departments,
criteria: criteria
}
);
},
set: {
departments: async (id, departments) => {
return ObjectsGlobal.set.column(
Goals.meta.endpoint,
id,
"departments",
departments
)
},
criteria: async (id, criteria) => {
return ObjectsGlobal.set.column(
Goals.meta.endpoint,
id,
"criteria",
criteria
)
},
},
get: {
all: async () => {
return ObjectsGlobal.get.objects(Goals.meta.endpoint);
},
single: async (id) => {
return ObjectsGlobal.get.object(Goals.meta.endpoint, parseInt(id));
}
},
update: async (id, departments, criteria) => {
return authenticatedRequest(Goals.meta.endpoint, "PUT", {
id: parseInt(id),
departments: departments,
criteria: criteria
});
},
delete: async (id) => {
return ObjectsGlobal.delete.object(Goals.meta.endpoint, parseInt(id));
},
functions: {
showDeleteObjectForm: (id, onAfterSubmit = null) => {
return ObjectsGlobal.showDeleteObjectForm(Goals, id, onAfterSubmit);
},
},
/**
* Show the create object form
* @param onAfterSubmit
* @param lockedValues
* @returns {Promise<SweetAlertResult<Awaited<any>>>}
*/
showCreateObjectForm: (onAfterSubmit = null, lockedValues = {}) => {
return ObjectsGlobal.showCreateObjectForm(Goals, onAfterSubmit, lockedValues);
},
/**
* Show the edit object field form
* @param id
* @param column
* @param value
* @param onAfterSubmit
* @returns {Promise<SweetAlertResult<Awaited<any>>>}
*/
showEditObjectFieldForm: (id, column, value, onAfterSubmit = null) => {
return ObjectsGlobal.showEditObjectFieldForm(
Goals,
id,
column,
value,
onAfterSubmit
);
}
};
</script>