Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestion: Better model for tutorial #2

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

vmikhailenko
Copy link
Collaborator

@vmikhailenko vmikhailenko commented Jul 5, 2023

We can change model a little to follow "better" best practice:

namespace sap.capire.bookstore;

using { Currency, cuid, managed }      from '@sap/cds/common';
using { sap.capire.products.Products } from '@sap/capire-products';

entity Books as projection on Products; extend Products with {
    // Note: we map Books to Products to allow reusing AdminService as is
    author : Association to Authors;
}

entity Authors : cuid {
    firstname : String(111);
    lastname  : String(111);
    books     : Association to many Books on books.author = $self;
}

@Capabilities.Updatable: false
entity Orders : cuid, managed {
    items    : Composition of many {
        book_ID   : UUID;
        amount    : Integer;
        netAmount : Decimal(9,2) @readonly;
    };
    total    : Decimal(9,2) @readonly;
    currency : Currency;
}

I do not remember why we have not done this last year. Probably either me being afraid to rewrite the whole tutorial or something else.

This will require a rewrite of the handler and some changes in the URLs here and there. I did a quick test locally and see no issues.

@vmikhailenko
Copy link
Collaborator Author

Handler:

package com.sap.cap.bookstore.handlers;

import cds.gen.ordersservice.OrdersService_;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.ServiceName;

import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.Collections;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;

import com.sap.cds.ql.Select;
import com.sap.cds.ql.Update;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnUpdate;
import com.sap.cds.services.ErrorStatuses;
import com.sap.cds.services.ServiceException;
import com.sap.cds.services.cds.CqnService;
import com.sap.cds.services.handler.annotations.After;
import com.sap.cds.services.handler.annotations.Before;
import com.sap.cds.services.persistence.PersistenceService;

import cds.gen.ordersservice.Orders;
import cds.gen.ordersservice.Orders_;
import cds.gen.sap.capire.bookstore.Books;
import cds.gen.sap.capire.bookstore.Books_;

@Component
@ServiceName(OrdersService_.CDS_NAME)
public class OrdersService implements EventHandler {
    @Autowired
    PersistenceService db;

    private void validateBookAndDecreaseStock(List<Orders.Items> items) {
        for (Orders.Items item : items) {
            String bookId = item.getBookId();
            Integer amount = item.getAmount();

            // check if the book that should be ordered is existing
            CqnSelect sel = Select.from(Books_.class).columns(b -> b.stock()).where(b -> b.ID().eq(bookId));
            Books book = db.run(sel).first(Books.class)
                    .orElseThrow(() -> new ServiceException(ErrorStatuses.NOT_FOUND, "Book does not exist"));

            // check if order could be fulfilled
            int stock = book.getStock();
            if (stock < amount) {
                throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Not enough books on stock");
            }

            // update the book with the new stock, means minus the order amount
            book.setStock(stock - amount);
            CqnUpdate update = Update.entity(Books_.class).data(book).where(b -> b.ID().eq(bookId));
            db.run(update);
        }
    }

    @Before(event = CqnService.EVENT_CREATE, entity = Orders_.CDS_NAME)
    public void validateBookAndDecreaseStockViaOrders(List<Orders> orders) {
        for (Orders order : orders) {
            if (order.getItems() != null) {
                validateBookAndDecreaseStock(order.getItems());
                calculateNetAmount(order.getItems());
            }
        }
    }

    @After(event = { CqnService.EVENT_READ, CqnService.EVENT_CREATE }, entity = Orders_.CDS_NAME)
    public void calculateTotal(List<Orders> orders) {
        for (Orders order : orders) {
            // calculate net amount for expanded items
            if (order.getItems() != null) {
                calculateNetAmount(order.getItems());
            }

            // get all items of the order
            CqnSelect selItems = Select.from(Orders_.class)
                    .columns(t -> t.expand(o -> o.items())).where(i -> i.ID().eq(order.getId()));
            List<Orders.Items> allItems = db.run(selItems).first(Orders.class)
                    .map(o -> o.getItems()).orElse(Collections.emptyList());

            // calculate net amount of all items
            calculateNetAmount(allItems);

            // calculate and set the orders total
            BigDecimal total = new BigDecimal(0);
            for (Orders.Items item : allItems) {
                total = total.add(item.getNetAmount());
            }
            order.setTotal(total);
        }
    }

    private void calculateNetAmount(List<Orders.Items> items) {
        for (Orders.Items item : items) {
            String bookId = item.getBookId();

            // get the book that was ordered
            CqnSelect sel = Select.from(Books_.class).where(b -> b.ID().eq(bookId));
            Books book = db.run(sel).single(Books.class);

            // calculate and set net amount
            item.setNetAmount(book.getPrice().multiply(new BigDecimal(item.getAmount())));
        }
    }
}

@vmikhailenko vmikhailenko changed the title Better model for tutorial Suggestion: Better model for tutorial Jul 5, 2023
@vmikhailenko vmikhailenko marked this pull request as draft July 5, 2023 15:33
@vmikhailenko vmikhailenko mentioned this pull request Jul 6, 2023
@rjayasinghe
Copy link
Collaborator

I am not sure if it's a general best practice to use inline types. However, for this scenario it makes sense. The OrderItems type is not reused anywhere.

@@ -106,35 +106,32 @@ Now that you have created your bookstore project, you need to define the domain

```CDS
namespace sap.capire.bookstore;

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suppose the remove those blank lines. I understand that it improves readability, but in a tutorial where this is copied, it just increases the size of the document.

renejeglinsky pushed a commit that referenced this pull request Sep 5, 2023
renejeglinsky pushed a commit that referenced this pull request Sep 5, 2023
Merge pull request #2 from sap-tutorials/master
renejeglinsky pushed a commit that referenced this pull request Sep 5, 2023
renejeglinsky pushed a commit that referenced this pull request Feb 19, 2024
renejeglinsky pushed a commit that referenced this pull request Jun 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants