Skip to content

Commit

Permalink
Merge pull request #3 from dladlk/feature/1-ordering
Browse files Browse the repository at this point in the history
add to basker, basket page, order header
  • Loading branch information
dladlk authored Dec 1, 2021
2 parents d193599 + e829f11 commit 36ad3f4
Show file tree
Hide file tree
Showing 22 changed files with 811 additions and 313 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ dist/
log/

# Skip VisualStudio Code folders
.vscode/
.vscode/

.tomcat/
172 changes: 85 additions & 87 deletions cm-api/src/main/java/dk/erst/cm/api/item/ProductService.java
Original file line number Diff line number Diff line change
@@ -1,101 +1,99 @@
package dk.erst.cm.api.item;

import java.time.Instant;
import java.util.List;
import java.util.Optional;

import dk.erst.cm.api.dao.mongo.ProductRepository;
import dk.erst.cm.api.data.Product;
import dk.erst.cm.api.data.ProductCatalogUpdate;
import dk.erst.cm.xml.ubl21.model.CatalogueLine;
import dk.erst.cm.xml.ubl21.model.NestedSchemeID;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.query.TextCriteria;
import org.springframework.stereotype.Service;

import dk.erst.cm.api.dao.mongo.ProductRepository;
import dk.erst.cm.api.data.Product;
import dk.erst.cm.api.data.ProductCatalogUpdate;
import dk.erst.cm.xml.ubl21.model.CatalogueLine;
import dk.erst.cm.xml.ubl21.model.NestedSchemeID;
import java.time.Instant;
import java.util.List;
import java.util.Optional;

@Service
public class ProductService {

private ProductRepository productRepository;

@Autowired
public ProductService(ProductRepository itemRepository) {
this.productRepository = itemRepository;
}

public Product saveCatalogUpdateItem(ProductCatalogUpdate catalog, CatalogueLine line) {
String lineLogicalId = line.getLogicalId();
String productCatalogId = catalog.getProductCatalogId();

String itemLogicalId = productCatalogId + "_" + lineLogicalId;

boolean deleteAction = line.getActionCode() != null && "Delete".equals(line.getActionCode().getId());

Product product;
Optional<Product> optional = productRepository.findById(itemLogicalId);
if (optional.isPresent()) {
product = optional.get();
product.setUpdateTime(Instant.now());
product.setVersion(product.getVersion() + 1);
} else {
product = new Product();
product.setId(itemLogicalId);
product.setCreateTime(Instant.now());
product.setUpdateTime(null);
product.setVersion(1);
}
product.setDocumentVersion(ProductDocumentVersion.PEPPOL_CATALOGUE_3_1);
product.setProductCatalogId(productCatalogId);
product.setStandardNumber(getLineStandardNumber(line));
product.setDocument(line);

if (deleteAction) {
if (optional.isPresent()) {
productRepository.delete(product);
}
return null;
}
productRepository.save(product);

return product;
}

private String getLineStandardNumber(CatalogueLine line) {
if (line != null && line.getItem() != null) {
if (line.getItem().getStandardItemIdentification() != null) {
NestedSchemeID sn = line.getItem().getStandardItemIdentification();
if (sn.getId() != null && sn.getId().getId() != null) {
return sn.getId().getId().toUpperCase();
}
}
}
return null;
}

public long countItems() {
return productRepository.count();
}

public Page<Product> findAll(String searchParam, Pageable pageable) {
Page<Product> productList;
if (!StringUtils.isEmpty(searchParam)) {
TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matching(searchParam);
productList = productRepository.findAllBy(textCriteria, pageable);
} else {
productList = productRepository.findAll(pageable);
}
return productList;
}

public Optional<Product> findById(String id) {
return productRepository.findById(id);
}

public List<Product> findByStandardNumber(String standardNumber) {
return productRepository.findByStandardNumber(standardNumber);
}
private final ProductRepository productRepository;

@Autowired
public ProductService(ProductRepository itemRepository) {
this.productRepository = itemRepository;
}

public Product saveCatalogUpdateItem(ProductCatalogUpdate catalog, CatalogueLine line) {
String lineLogicalId = line.getLogicalId();
String productCatalogId = catalog.getProductCatalogId();
String itemLogicalId = productCatalogId + "_" + lineLogicalId;
boolean deleteAction = line.getActionCode() != null && "Delete".equals(line.getActionCode().getId());
Product product;
Optional<Product> optional = productRepository.findById(itemLogicalId);
if (optional.isPresent()) {
product = optional.get();
product.setUpdateTime(Instant.now());
product.setVersion(product.getVersion() + 1);
} else {
product = new Product();
product.setId(itemLogicalId);
product.setCreateTime(Instant.now());
product.setUpdateTime(null);
product.setVersion(1);
}
product.setDocumentVersion(ProductDocumentVersion.PEPPOL_CATALOGUE_3_1);
product.setProductCatalogId(productCatalogId);
product.setStandardNumber(getLineStandardNumber(line));
product.setDocument(line);
if (deleteAction) {
if (optional.isPresent()) {
productRepository.delete(product);
}
return null;
}
productRepository.save(product);
return product;
}

private String getLineStandardNumber(CatalogueLine line) {
if (line != null && line.getItem() != null) {
if (line.getItem().getStandardItemIdentification() != null) {
NestedSchemeID sn = line.getItem().getStandardItemIdentification();
if (sn.getId() != null && sn.getId().getId() != null) {
return sn.getId().getId().toUpperCase();
}
}
}
return null;
}

public long countItems() {
return productRepository.count();
}

public Page<Product> findAll(String searchParam, Pageable pageable) {
Page<Product> productList;
if (!StringUtils.isEmpty(searchParam)) {
TextCriteria textCriteria = TextCriteria.forDefaultLanguage().matching(searchParam);
productList = productRepository.findAllBy(textCriteria, pageable);
} else {
productList = productRepository.findAll(pageable);
}
return productList;
}

public Optional<Product> findById(String id) {
return productRepository.findById(id);
}

public Iterable<Product> findAllByIds(Iterable<String> ids) {
return productRepository.findAllById(ids);
}

public List<Product> findByStandardNumber(String standardNumber) {
return productRepository.findByStandardNumber(standardNumber);
}
}
57 changes: 57 additions & 0 deletions cm-frontend/src/components/AddToBasket.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {Button} from "@material-ui/core";
import React, {useEffect, useRef} from "react";
import CircularProgress from "@material-ui/core/CircularProgress";
import {ProductBasketStatus} from "./BasketData";


export default function AddToBasket(props) {

const {changeBasket, basketData, product} = props;

const [state, setState] = React.useState(basketData.getProductBasketStatus(product.id));

const getButtonTitle = () => {
switch (state) {
case ProductBasketStatus.Adding:
return 'Adding to basket';
case ProductBasketStatus.Added:
return 'Remove from basket';
default:
return 'Add to basket';
}
}

const isProgress = () => {
return state === ProductBasketStatus.Adding;
}

// TODO: Remove - temporary code to imitate slow adding
const timerRef = useRef(null);
const handleClick = () => {
if (state === ProductBasketStatus.Empty) {
setState(ProductBasketStatus.Adding);
timerRef.current = setTimeout(() => {
changeBasket(product.id, 1);
setState(ProductBasketStatus.Added)
}, 300);
} else if (state === ProductBasketStatus.Added) {
changeBasket(product.id, 0);
setState(ProductBasketStatus.Empty);
}
}

useEffect(() => {
return () => clearTimeout(timerRef.current)
}, []);

return (
<>
<Button variant="outlined" size={"small"} color="primary" onClick={() => handleClick()}>
{isProgress() && (
<CircularProgress size={'1rem'} style={{marginRight: '5px'}}/>
)}
{getButtonTitle()}
</Button>
</>
)
}
26 changes: 26 additions & 0 deletions cm-frontend/src/components/BasketBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import {makeStyles} from '@material-ui/core/styles';
import ShoppingBasketIcon from '@material-ui/icons/ShoppingBasket';

const useStyles = makeStyles((theme) => ({
basket: {
padding: theme.spacing(0, 2),
},
basketIcon: {
color: theme.palette.common.white
},
}));

export default function BasketBar() {
const classes = useStyles();

return (

<div className={classes.basket}>
<div className={classes.basketIcon}>
<ShoppingBasketIcon/>
</div>
</div>

)
}
48 changes: 48 additions & 0 deletions cm-frontend/src/components/BasketData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
export const ProductBasketStatus = {
Empty: 'empty',
Adding: 'adding',
Added: 'added',
}

export function createBasketData() {

class BasketData {
constructor(orderLines = {}, orderLinesCount = 0) {
this.orderLines = orderLines;
this.orderLinesCount = orderLinesCount;
}

isEmpty() {
return this.orderLinesCount === 0;
}

getOrderLineList() {
return Object.keys(this.orderLines).map((productId) => {
return {productId: productId, quantity: this.orderLines[productId]}
});
}

changeBasket(productId, quantity) {
let newOrderLines = {...this.orderLines};
let newOrderLinesCount = this.orderLinesCount;
if (productId in newOrderLines) {
if (quantity !== 0) {
newOrderLines[productId] += quantity;
} else {
delete newOrderLines[productId]
newOrderLinesCount--;
}
} else {
newOrderLines[productId] = quantity;
newOrderLinesCount++;
}
return new BasketData(newOrderLines, newOrderLinesCount);
}

getProductBasketStatus(productId) {
return productId in this.orderLines ? ProductBasketStatus.Added : ProductBasketStatus.Empty;
}
}

return new BasketData();
}
61 changes: 61 additions & 0 deletions cm-frontend/src/components/OrderHeader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {Grid, Paper, TextField, Typography} from "@material-ui/core";
import {makeStyles} from "@material-ui/core/styles";

export default function OrderHeader() {

const useStyles = makeStyles((theme) => ({
paper: {
padding: theme.spacing(2),
marginBottom: theme.spacing(2),
},
formHeader: {
paddingTop: theme.spacing(1),
paddingLeft: theme.spacing(2),
textAlign: "left",
fontSize: '1em',
},
form: {
padding: theme.spacing(2),
display: "flex",
flex: "1",
flexDirection: "row",
justifyContent: "space-between",
},
input: {
paddingInline: theme.spacing(0.5),
}
}));

const classes = useStyles();

function DataInput(props) {
return <TextField className={classes.input} size={"small"} {...props} />
}

function DataBlock(props) {
return <Grid item sm={12} md={6}>
<Paper>
<div className={classes.formHeader}><Typography variant="h6">{props.name}</Typography></div>
<form className={classes.form} noValidate autoComplete="on">
{props.children}
</form>
</Paper>
</Grid>

}

return <Paper className={classes.paper}>
<Grid container spacing={2}>
<DataBlock name={"Buyer company"}>
<DataInput label="Registration name" defaultValue="My Company ApS" required/>
<DataInput label="Legal identifier" defaultValue="DK11223344" required/>
<DataInput label="Party identifier" defaultValue="7300010000001" required/>
</DataBlock>
<DataBlock name={"Buyer contact"}>
<DataInput label="Person name" defaultValue="John Dohn"/>
<DataInput label="Email" defaultValue="[email protected]"/>
<DataInput label="Telephone" defaultValue="+45 11223344"/>
</DataBlock>
</Grid>
</Paper>
}
Loading

0 comments on commit 36ad3f4

Please sign in to comment.