Loosely based on Tickets app walkthrough
This file aims to enhance the tickets app by using a new entity named protocols, which can be appended to tickets, and highlight CDS Associations.
It replaces the class-based approach to extending a CDS service by using a function-based approach and shows how to handle HTTP errors gracefully.
On top of that, it implements custom functions to create and close tickets.
Schema file
namespace com.cpro.tickets;
using {
cuid,
managed,
sap.common.CodeList
} from '@sap/cds/common';
entity Tickets : cuid, managed {
title : String(255) @mandatory;
description : String(1024) @mandatory;
state : Association to State @mandatory;
solvedAt : Timestamp;
closedAt : Timestamp;
protocol : Association to many Protocol
on protocol.ticket = $self;
}
entity Protocol : cuid, managed {
title : String(255) @mandatory;
description : String(1024) @mandatory;
ticket : Association to Tickets @mandatory @assert.target;
}
// Extend a ticket with protocol entries
// extend Tickets with {
// Protocol: Composition of many Protocol on Protocol.ticket = $self;
// }
// Extend Tickets with timestamps
aspect ManagedObject {
createdAt : Timestamp @cds.on.insert: $now @odata.etag;
modifiedAt : Timestamp @cds.on.insert: $now @cds.on.update: $now @odata.etag;
createdBy : String(100) @cds.on.insert: $user;
modifiedBy : String(100) @cds.on.insert: $user @cds.on.update: $user;
}
// Alternatively, 'managed' aspect can be used to create
// 'createdAt', 'createdBy', 'modifiedAt', 'modifiedBy'
// fields automatically
// Code lists are predefined key-value pairs that offer a comprehensive
// way to ensure input values are in a predefined set of values
// Node: Header = 'sap-locale={language}'
entity State : CodeList {
key ID : Integer;
name : localized String(255);
descr : localized String(1000);
}Service.js file
const cds = require("@sap/cds");
class TicketSerivce extends cds.ApplicationService {
init() {
const { Tickets } = this.entities;
// Action /tickets/closeTicket
// On how to implement queries, see
// https://cap.cloud.sap/docs/node.js/core-services#rest-style-api
this.on('closeTicket', async (req) => {
const { ticketId } = req.data;
// Use this.read for automatic error messages
// const ticket = await this.read(Tickets).where({ ID: ticketId });
// Use SELECT for custom error messages
const ticket = await cds.ql.SELECT.one.from(Tickets).where({ ID: ticketId });
if (!ticket) {
return req.error(404, 'TICKET_NOT_FOUND', [ticketId])
}
await this.update(Tickets).with({ state_ID: 5 }).where({ ID: ticketId });
// Alternatively, construct a query and run it
// const query = UPDATE.entity(Tickets).where({ ID: ticketId }).set({ state_ID: 5 });
// await cds.db.run(query)
return { ticketId, Tickets }
});
// Action /tickets/createTicket
// Describes a custom action to create a ticket
this.on('createTicket', async (req) => {
const { title, description } = req.data;
await cds.ql.INSERT.into(Tickets).entries({ title, description, state_ID: 1 });
return { title, description }
})
this.after('READ', Tickets, (results) => {
results.forEach(result => {
// Change the result title whenever the service is read
result.title = `${result.state_ID}: ${result.title.toUpperCase()}`
})
})
return super.init();
}
}
module.exports = TicketSerivce;Service.cds file:
using com.cpro.tickets as db from '../db/schema';
service TicketService @(path: '/tickets') {
entity Tickets as projection on db.Tickets;
entity Protocol as projection on db.Protocol;
action closeTicket(ticketId : db.Tickets:ID) returns {
ID : db.Tickets:ID;
state : db.Tickets:state;
};
action createTicket(title : db.Tickets:title, description : db.Tickets:description) returns {
ID : db.Tickets:ID;
};
}