cancel
Showing results for 
Search instead for 
Did you mean: 

CAP Node.js - fetch data during custom service implementation

phidoe
Explorer
0 Kudos

Hi community,

I am new to CAP and working on a learning project that displays employee appointments in a calendar app.

I am trying to add some data to my appointment entity upon a read request. Within my custom service implementation I try to do 2 things:
(1) Add some calendar type data that is randomly calculated (determines the color of the calendar entry in the app).
(2) The appointment entity contains the employee id. During the service implementation I would like to query some more employee data and add it to the appointment entity.

schema:

entity Vacations {
  key ID : UUID;
   startDate  : Date; 
   endDate : Date;
   employeeID: String;
   employee : Association to Employees;
  note : String; 
}

entity Employees {
    key ID : UUID;
    firstName : String; 
    lastName : String; 
    vacations : Composition of many Vacations on vacations.employeeID = $self.ID;
}

service.cds

@impl: 'srv/appointments-service.js'
service appointments {
    entity appointments as projection on praxissuite.Vacations {
        *,
        null as type : String,
        null as description : String
    };
    entity Employee as projection on praxissuite.Employees;
}

service.js

module.exports = (srv) => {
  const { appointments, Employees } = cds.entities("pd.praxissuite");

  srv.after("READ", "appointments", (each) => {
    each.type = createAppointmentType();
    console.log(each.type);

    let q1 = readEmployee();
    each.description = q1.firstname;

    console.log(q1);
    console.log(each);
  });

  async function readEmployee(employeeID) {
    const q1 = await SELECT.one.from(Employees);
    return q1;
  }

  function createAppointmentType() {
    const max = 20;
    const randomNumber = Math.floor(Math.random() * max);
    let type = "";

    switch (randomNumber) {
      case 1:
        type = "Type01";

        break;

      case 2:
        type = "Type02";

        break;

      case 3:
        type = "Type03";

        break;

      case 4:
        type = "Type04";

        break;

      case 5:
        type = "Type05";

        break;

      case 6:
        type = "Type06";

        break;

      case 7:
        type = "Type07";

        break;

      case 8:
        type = "Type08";

        break;

      case 9:
        type = "Type09";

        break;

      case 10:
        type = "Type10";

        break;

      case 11:
        type = "Type11";

        break;

      case 12:
        type = "Type12";

        break;

      case 13:
        type = "Type13";

        break;

      case 14:
        type = "Type14";

        break;

      case 15:
        type = "Type15";

        break;

      case 16:
        type = "Type16";

        break;

      case 17:
        type = "Type17";

        break;

      case 18:
        type = "Type18";

        break;

      case 19:
        type = "Type19";

        break;

      case 20:
        type = "Type20";

        break;

      default:
        type = "Type06";

        break;
    }

    return type;
  }
};

Adding the type to the appointment works fine. I can't manage to query employee data and add it to the appointment as description. The select only returns the query, not its result.

The documentation says to return the query result I need to await the Select call, but I am not sure how to do this properly, since when I change the srv.on... part and the function within to also be async, the type data is not added to the appointment entity anymore.

Hope someone can bring some light into the darkness. Thanks a lot in advance.

Br,

Philipp

phidoe
Explorer
0 Kudos

Don't know why it is happening, but once I save, the text within the code sections is always moved outside. Sorry for that.

gregorw
Active Contributor

You can edit your post to fix it. Try to paste first to Notepad and then from there into the code section.

phidoe
Explorer
0 Kudos

Thanks for the hint. Original question was updated.

Accepted Solutions (1)

Accepted Solutions (1)

UBrand251
Participant

Hi Philipp,

the issue may be caused by parameter "each" in your srv.AFTER event handler that you use like an idividual appointment. Acc'dg to docs (below) the event handler is a function with params "req" and "results", the latter being a collection.

CAP - Method Srv.After (request)

Instead of "each", try "appointments" and access individual records in the function body either with a for-loop or by calling

appointments.map( appt => { <do sth.> } )

phidoe
Explorer
0 Kudos

Hi Ulrich,

thank you a lot for your answer.

Ahh shame on me, of course you're right. However I still don't get it to work. Here's what my service implementation looks like now:

module.exports = (srv) => {

const {

appointments,

Employees

} = cds.entities('pd.praxissuite');

srv.after('READ', 'appointments', async (appointments) => {

console.log(appointments);

await appointments.map( async appt => {

appt.type = createAppointmentType();

const employee = await readEmployee(appt.employeeID);

appt.description = employee.firstName;

})

console.log(appointments);

});

async function readEmployee(employeeID) {

const employee = await SELECT.one.from(Employees).where({ID:employeeID});

console.log(employee);

return employee;

};

I left out the createAppointmentType. The console output is:

[
  {
    ID: '5c7486d2-4ad0-4310-a852-a2dc231e1728',
    description: null,
    employeeID: 'feab1bd0-575a-4712-bf2e-4fd034650324',
    endDate: '2023-10-14',
    startDate: '2023-10-01',
    type: null
  }
]
[
  {
    ID: '5c7486d2-4ad0-4310-a852-a2dc231e1728',
    description: null,
    employeeID: 'feab1bd0-575a-4712-bf2e-4fd034650324',
    endDate: '2023-10-14',
    startDate: '2023-10-01',
    type: 'Type02'
  }
]
{
  ID: 'feab1bd0-575a-4712-bf2e-4fd034650324',
  firstName: 'Philipp',
  lastName: 'Doelker'
}

So getting the appointment type and adding it to the result array works.

Also fetching the employee data works, but I still can't manage to get in into my response. It seems the response is returned before the select is finished and the query result is handed over to my "after READ" implementation.

phidoe
Explorer
0 Kudos

This is working, but I don't get why...

for (let i = 0; i < appointments.length; i++) {

	let appt = appointments[i];

	appt.type = createAppointmentType();

	const employee = await readEmployee(appt.employeeID);

	appt.description = employee.firstName;

}

Answers (1)

Answers (1)

Willem_Pardaens
Product and Topic Expert
Product and Topic Expert

You don't need custom code for that, you can just model it in the service:

entity appointments as projection on praxissuite.Vacations {
  *,
  employee.ID as employeeID : String,
  null as type : String,
  employee.firstName as description : String
};

srv.after('READ', 'appointments', each => {
  each.type = createAppointmentType();
}
phidoe
Explorer
0 Kudos

Very good hint, thank a lot!

I tried:

@impl: 'srv/appointments-service.js'
service appointments {
    entity appointments as projection on praxissuite.Vacations {
        *,
        employee.ID        as employeeID  : String,
        null               as type        : String,
        employee.firstName as description : String
    };

    entity Employee     as projection on praxissuite.Employees;
}
Leading to syntax error:
Element “employeeID” has not been found in the elements of the query; please use REDIRECTED TO with an explicit ON-conditionThen I changed it to:
@impl: 'srv/appointments-service.js'
service appointments {
    entity appointments as projection on praxissuite.Vacations {
        *,
        null as type : String,
        employee : redirected to Employee,
        employee.firstName as description : String
    };
    entity Employee as projection on praxissuite.Employees;
}
Which resolves the syntax error but doesn't fill the description attribute...
Willem_Pardaens
Product and Topic Expert
Product and Topic Expert
0 Kudos

I feel you're making this far more complex than it has to be. I tested the below on my side, and it works great.

Try to use managed associations where possible, they have implicit foreign keys so is less work to manage. There is now also a new feature allowing you to use Calculated Elements, so no need to have a custom service layer for non-database elements.

cds:

using {cuid} from '@sap/cds/common';

context praxissuite {
    entity Vacations : cuid {
        startDate   : Date;
        endDate     : Date;
        note        : String;
        employee    : Association to Employees;
        description : String = employee.firstName;
        type        : String = null;
    }

    entity Employees : cuid {
        firstName : String;
        lastName  : String;
        vacations : Composition of many Vacations
                        on vacations.employee = $self;
    }
}

service appointments {
    entity appointments as projection on praxissuite.Vacations;
}
js:
module.exports = srv => {
    srv.after('READ', srv.entities.appointments, each => {
        const max = 20;
        each.type = `Type${Math.floor(Math.random() * max + 1).toString()}`;
    });
}