diff --git a/pom.xml b/pom.xml index f13558a..ab54160 100644 --- a/pom.xml +++ b/pom.xml @@ -88,9 +88,9 @@ org.apache.maven.plugins maven-compiler-plugin - 8 - 8 - + 10 + 10 + diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java new file mode 100644 index 0000000..7c47b05 --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdmin.java @@ -0,0 +1,79 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.CheckoutLine; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +import java.util.ArrayList; +import java.util.List; + +/** + * Управляет кассирами на кассах: следит, кто когда ушел отдыхать, + * когда может работать снова, кого именно сажать на кассу. + * Используется в стратегии "не увольнять сразу" + */ +@Slf4j +public class CheckoutAdmin { + + private List employees; + private List timesheet; + private boolean notFailed = false; + + public CheckoutAdmin(List employees) { + this.employees = new ArrayList<>(employees); + init(); + } + + private void init() { + if (employees == null || employees.isEmpty()) { + timesheet = new ArrayList<>(); + return; + } + timesheet = new ArrayList<>(employees.size()); + employees.forEach(it -> timesheet.add(new EmployeeWorkTable(it))); + } + + public Employee sendToWorkAny(int currentTime) { + var candidate = timesheet.stream() + .filter(it -> it.isReadyToWork(currentTime)) + .findAny().orElse(null); + if (candidate == null) { + if (notFailed || currentTime % 120 == 0) { + log.warn("Not enough employees to work at=" + currentTime + ", " + timesheet); + } + notFailed = false; + return null; + } + notFailed = true; + candidate.startWork(currentTime); + return candidate.getEmployee(); + } + + // TODO add tests + // FIXME учесть уволенных + public void considerNew(List employeeList, + List checkoutLines, + Integer tickCount) { + var newWorkers = new ArrayList<>(employeeList); + newWorkers.removeAll(employees); + employees.addAll(newWorkers); + for (Employee newbie : newWorkers) { + var alreadyAssigned = checkoutLines.stream() + .anyMatch(line -> newbie.getId().equals(line.getEmployeeId())); + final EmployeeWorkTable newbieWorkTable = new EmployeeWorkTable(newbie); + if (alreadyAssigned) { + newbieWorkTable.startWork(tickCount); + } + timesheet.add(newbieWorkTable); + log.info(newbieWorkTable.toString()); + } + } + + public List getEmployees() { + return employees; + } + + public List getTimesheet() { + return timesheet; + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java new file mode 100644 index 0000000..145d538 --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/EmployeeWorkTable.java @@ -0,0 +1,97 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +class EmployeeWorkTable { + private final static int HOUR_IN_TICKS = 60; + private final static int WORK_SHIFT_IN_TICKS = 8 * HOUR_IN_TICKS; + private final static int REST_TIME_IN_TICKS = 16 * HOUR_IN_TICKS; + + @NonNull + private Employee employee; + private Integer startedWork = null; + + public EmployeeWorkTable(Employee employee) { + this.employee = employee; + } + + // probably change return type + public boolean startWork(int currentTime) { + if (isReadyToWork(currentTime)) { + startedWork = currentTime; + return true; + } + return false; + } + + /** + * @return time (in ticks) when this employee started his last work shift. + * Null if it never works. + */ + @Nullable + public Integer getStartedWork() { + return startedWork; + } + + @NonNull + public Employee getEmployee() { + return employee; + } + + /** + * @return time (in ticks) when this employee will finish his work shift. + * Null if it never works + */ + @Nullable + public Integer getFinishedWork() { + if (startedWork == null) { + return null; + } + return startedWork + WORK_SHIFT_IN_TICKS; + } + + /** + * @return time (in ticks) when this employee could start to work. + * Null if it never works and could start immediately + */ + @Nullable + public Integer getReadyToWork() { + if (startedWork == null) { + return null; + } + return getFinishedWork() + REST_TIME_IN_TICKS; + } + + public boolean isReadyToWork(int currentTime) { + if (startedWork == null) { + return true; + } + return getReadyToWork() <= currentTime; + } + + public static int getWorkShiftInTicks() { + return WORK_SHIFT_IN_TICKS; + } + + public static int getRestTimeInTicks() { + return REST_TIME_IN_TICKS; + } + + public static int getHourInTicks() { + return HOUR_IN_TICKS; + } + + @Override + public String toString() { + final StringBuffer sb = new StringBuffer("EmployeeWorkTable{"); + sb.append("employee(").append(employee.getId()).append(")"); + sb.append(" ").append(employee.getFirstName()); + sb.append(", exp=").append(employee.getExperience()); + sb.append(", $").append(employee.getSalary()); + sb.append(", started=").append(startedWork); + sb.append('}'); + return sb.toString(); + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java new file mode 100644 index 0000000..e703d6f --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/HRDepartment.java @@ -0,0 +1,49 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.EmployeeRecruitmentOffer; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.HireEmployeeCommand; + +import java.util.ArrayList; +import java.util.List; + +/** + * Отвечает за найим и увольнение + */ +@Slf4j +public class HRDepartment { + + private List recruitmentAgency; + private List employees; + private int checkoutCount; // сколько всего касс + + public HRDepartment(List recruitmentAgency, List employees, int checkoutCount) { + this.recruitmentAgency = recruitmentAgency; + this.employees = employees; + this.checkoutCount = checkoutCount; + } + + + public List getRecruitmentAgency() { + return recruitmentAgency; + } + + public List firstHire() { + List result = new ArrayList<>(recruitmentAgency.size()); + for (int i = 0; i < 3 && i < recruitmentAgency.size(); i++) { + var offer = recruitmentAgency.get(i); + result.add(new HireEmployeeCommand() + .experience(HireEmployeeCommand.ExperienceEnum.fromValue(offer.getEmployeeType())) + + ); + + } + // TODO получать реальный id + result.get(0).checkoutLineId(1); + log.debug("Всего предложений:" + recruitmentAgency.size()); + log.debug(recruitmentAgency.toString()); + log.debug(result.toString()); + return result; + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java new file mode 100644 index 0000000..f625b7e --- /dev/null +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/Merchandaizer.java @@ -0,0 +1,147 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import lombok.extern.slf4j.Slf4j; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Выкладывает товар на полки, определяет наценки. + * Будет ли заниматься заказом товара - пока не знаю + */ +@Slf4j +public class Merchandaizer { + + public enum Margin { + DOLLAR_ONE, PERCENT_10, PERCENT_20, PERCENT_50, PERCENT_100; + + public Margin next() { + switch (this) { + case DOLLAR_ONE: + return PERCENT_10; + case PERCENT_10: + return PERCENT_20; + case PERCENT_20: + return PERCENT_50; + case PERCENT_50: + return PERCENT_100; + case PERCENT_100: + return PERCENT_100; + default: + return DOLLAR_ONE; // never happens + } + } + } + + private static final Integer DEFAULT_QUANTITY = 100; + private List racks; + private List stock; + private List productsOnRack; + private List racksToPutOff; + + public Merchandaizer(List racks, List stock) { + this.racks = new ArrayList<>(racks); + this.stock = new ArrayList<>(stock); + this.stock.sort(Comparator.comparing(Product::getStockPrice)); + } + + public List removeSold() { + var result = racksToPutOff.stream().map(id -> + new PutOffRackCellCommand().rackCellId(id) + ).collect(Collectors.toList()); + return result; + } + + public List inspectRacks(CurrentWorldResponse world) { + racksToPutOff = new ArrayList<>(); + racks = world.getRackCells(); + stock = world.getStock(); + stock.sort(Comparator.comparing(Product::getStockPrice).reversed()); + productsOnRack = racks.stream() + .filter(r -> r.getProductId() != null) + .map(RackCell::getProductId) + .collect(Collectors.toList()); + + List result = new ArrayList<>(); + + racks.stream() + .filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)) + .forEach(rack -> { + PutOnRackCellCommand command; + if (rack.getProductId() == null) { + command = putNextFromStock(rack); + } else { + command = addSame(rack); + } + result.add(command); + }); + return result; + } + + private PutOnRackCellCommand addSame(RackCell rack) { + Product productToPutOnRack = stock.stream() + .filter(it -> it.getId().equals(rack.getProductId())) + .findAny() + .orElseThrow(() -> new IllegalStateException("Product is lost on stock:" + rack.getProductId())); + + final Integer inStock = productToPutOnRack.getInStock(); + int rackCapacity = rack.getCapacity(); + double sellPrice = definePrice(productToPutOnRack); + return new PutOnRackCellCommand().productId(productToPutOnRack.getId()) + .rackCellId(rack.getId()) + .productQuantity(Math.min(inStock, rackCapacity)) + .sellPrice(sellPrice); + } + + + private PutOnRackCellCommand putNextFromStock(RackCell rack) { + Product productToPutOnRack; + productToPutOnRack = stock.stream() + .filter(product -> !productsOnRack.contains(product.getId()) + && product.getInStock() > 0) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Store is empty")); + productsOnRack.add(productToPutOnRack.getId()); + racksToPutOff.add(rack.getId()); + final Integer inStock = productToPutOnRack.getInStock(); + int rackCapacity = rack.getCapacity(); + double sellPrice = definePrice(productToPutOnRack); + return new PutOnRackCellCommand().productId(productToPutOnRack.getId()) + .rackCellId(rack.getId()) + .productQuantity(Math.min(inStock, rackCapacity)) + .sellPrice(sellPrice); + } + + private double definePrice(Product product) { + if (product.getSellPrice() == null) { + return product.getStockPrice() + 1; + } + return product.getSellPrice() * 1.1; // add 10% + } + + public List inspectStore() { + List result = new ArrayList<>(); + stock.stream().filter(it -> it.getInStock().equals(0)) + .forEach(product -> result.add(new BuyStockCommand() + .productId(product.getId()) + .quantity(DEFAULT_QUANTITY) + )); + if (result.size() > 5) { + return result; + } else { + return Collections.emptyList();// избегаем мелких закупок + } + } + + public List initialBuyIn() { + List buyStockCommands = new ArrayList<>(50); + stock.forEach(it -> buyStockCommands.add( + new BuyStockCommand().productId(it.getId()).quantity(DEFAULT_QUANTITY) + )); + return buyStockCommands; + } +} diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java index c50db83..6303e7f 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayer.java @@ -10,8 +10,7 @@ import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.api.PerfectStoreEndpointApi; import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; @Component @@ -20,6 +19,13 @@ public class PerfectStorePlayer implements ApplicationListener customersOnCheckline = new HashSet<>(); + private Set awaitingCustomers = new HashSet<>(); + private Map basketProducts = new HashMap<>(); // productId->product + private HRDepartment hrDepartment; + private CheckoutAdmin checkoutAdmin; + private Merchandaizer merch; + public PerfectStorePlayer(@Value("${rs.endpoint:http://localhost:9080}") String serverUrl) { this.serverUrl = serverUrl; } @@ -31,96 +37,163 @@ public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { PerfectStoreEndpointApi psApiClient = new PerfectStoreEndpointApi(apiClient); - log.info("Игрок готов. Подключаемся к серверу.."); + log.debug("Игрок готов. Подключаемся к серверу.."); awaitServer(psApiClient); log.info("Подключение к серверу успешно. Начинаем игру"); + CurrentWorldResponse currentWorldResponse = null; try { - CurrentWorldResponse currentWorldResponse = null; int cnt = 0; do { cnt += 1; - if (cnt % 120 == 0) { + if (cnt % (60 * 4) == 0) { log.info("Пройден " + cnt + " тик"); +// log.info("Ответ с предыдущего шага="+currentWorldResponse); + } + + CurrentTickRequest request = createNextMove(currentWorldResponse); + if (cnt > 1 && cnt <= 3) { + log.info(request.getBuyStockCommands().toString()); + log.info(request.getPutOnRackCellCommands().toString()); } if (currentWorldResponse == null) { currentWorldResponse = psApiClient.loadWorld(); + // logging world + log.debug("Total employees=" + currentWorldResponse.getEmployees().size()); + log.debug("Кассы" + currentWorldResponse.getCheckoutLines()); + var productAssortment = currentWorldResponse.getStock().size(); + var productCountList = currentWorldResponse.getStock(). + stream().sorted(Comparator.comparingDouble(Product::getStockPrice)) + .map(Product::getInStock).collect(Collectors.toList()); + log.debug("Видов товаров=" + productAssortment + ", штук=" + productCountList + + ", стоит=" + currentWorldResponse.getStockCosts()); + log.debug("Полок=" + currentWorldResponse.getRackCells().size()); + // end of logging + + // init state + hrDepartment = new HRDepartment(currentWorldResponse.getRecruitmentAgency(), + currentWorldResponse.getEmployees(), + currentWorldResponse.getCheckoutLines().size() + ); + checkoutAdmin = new CheckoutAdmin(currentWorldResponse.getEmployees()); + merch = new Merchandaizer(currentWorldResponse.getRackCells(), + currentWorldResponse.getStock()); + + // generate initial commands + request = new CurrentTickRequest(); + request.hireEmployeeCommands(hrDepartment.firstHire()); + request.buyStockCommands(merch.initialBuyIn()); } - CurrentTickRequest request = new CurrentTickRequest(); - - List hireEmployeeCommands = new ArrayList<>(); - request.setHireEmployeeCommands(hireEmployeeCommands); - // Смотрим на каких кассах нет кассира (либо не был назначен, либо ушел с кассы отдыхать), нанимаем новых кассиров и ставим на эти кассы. - // Нанимаем самых опытных! - // TODO hire only if here are too many customers - currentWorldResponse.getCheckoutLines().stream().filter(line -> line.getEmployeeId() == null).forEach(line -> { - HireEmployeeCommand hireEmployeeCommand = new HireEmployeeCommand(); - hireEmployeeCommand.setCheckoutLineId(line.getId()); - hireEmployeeCommand.setExperience(HireEmployeeCommand.ExperienceEnum.SENIOR); - hireEmployeeCommands.add(hireEmployeeCommand); - }); - request.setHireEmployeeCommands(hireEmployeeCommands); - - // готовимся закупать товар на склад и выставлять его на полки - ArrayList buyStockCommands = new ArrayList<>(); - request.setBuyStockCommands(buyStockCommands); - - ArrayList putOnRackCellCommands = new ArrayList<>(); - request.setPutOnRackCellCommands(putOnRackCellCommands); - - List stock = currentWorldResponse.getStock(); - List rackCells = currentWorldResponse.getRackCells(); - - // Обходим торговый зал и смотрим какие полки пустые. Выставляем на них товар. - currentWorldResponse.getRackCells().stream().filter(rack -> rack.getProductId() == null || rack.getProductQuantity().equals(0)).forEach(rack -> { - Product producttoPutOnRack = null; - if (rack.getProductId() == null) { - List productsOnRack = rackCells.stream().filter(r -> r.getProductId() != null).map(RackCell::getProductId).collect(Collectors.toList()); - productsOnRack.addAll(putOnRackCellCommands.stream().map(c -> c.getProductId()).collect(Collectors.toList())); - producttoPutOnRack = stock.stream().filter(product -> !productsOnRack.contains(product.getId())).findFirst().orElse(null); - } - else { - producttoPutOnRack = stock.stream().filter(product -> product.getId().equals(rack.getProductId())).findFirst().orElse(null); - } - - Integer productQuantity = rack.getProductQuantity(); - if (productQuantity == null) { - productQuantity = 0; - } - - // Вначале закупим товар на склад. Каждый ход закупать товар накладно, но ведь это тестовый игрок. - Integer orderQuantity = rack.getCapacity() - productQuantity; - if (producttoPutOnRack.getInStock() < orderQuantity) { - BuyStockCommand command = new BuyStockCommand(); - command.setProductId(producttoPutOnRack.getId()); - command.setQuantity(100); - buyStockCommands.add(command); - } - - // Далее разложим на полки. И сформируем цену. Накинем 10 рублей к оптовой цене - PutOnRackCellCommand command = new PutOnRackCellCommand(); - command.setProductId(producttoPutOnRack.getId()); - command.setRackCellId(rack.getId()); - command.setProductQuantity(orderQuantity); - if (producttoPutOnRack.getSellPrice() == null) { - command.setSellPrice(producttoPutOnRack.getStockPrice() + 10); - } - putOnRackCellCommands.add(command); - - }); - - currentWorldResponse = psApiClient.tick(request); + currentWorldResponse = psApiClient.tick(new CurrentTickRequest()); + collectDataFromAnswer(currentWorldResponse); } while (!currentWorldResponse.isGameOver()); // Если пришел Game Over, значит все время игры закончилось. Пора считать прибыль log.info("Я заработал " + (currentWorldResponse.getIncome() - currentWorldResponse.getSalaryCosts() - currentWorldResponse.getStockCosts()) + "руб."); - - } catch (ApiException e) { + log.info(currentWorldResponse.getStock().toString()); + log.info(currentWorldResponse.getRackCells().toString()); + log.info("В корзинах:" + basketProducts.values()); + log.info("Sold products count=" + basketProducts.size()); + var awaitingCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)).count(); + var atCheckoutCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.AT_CHECKOUT)).count(); + var inHallCustomersCount = currentWorldResponse.getCustomers().stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.IN_HALL)).count(); + log.info(String.format("Customers entered to checkline=%s, at checkout=%s, awaiting=%s, in hall=%s, total=%s", + customersOnCheckline.size(), + awaitingCheckoutCustomersCount, + atCheckoutCustomersCount, + inHallCustomersCount, + currentWorldResponse.getCustomers().size() + )); + + } catch (ApiException | RuntimeException e) { log.error(e.getMessage(), e); + closeStore(psApiClient, currentWorldResponse); + } + + } + + private void closeStore(PerfectStoreEndpointApi psApiClient, CurrentWorldResponse currentWorldResponse) { + CurrentTickRequest request = new CurrentTickRequest(); + do { + try { + currentWorldResponse = psApiClient.tick(request); + } catch (ApiException e) { + log.error("Failed to process empty request: " + e.getMessage()); + } + } while (!currentWorldResponse.isGameOver()); + } + + private void collectDataFromAnswer(CurrentWorldResponse worldResponse) { + var customers = worldResponse.getCustomers(); + var awaitingCheckoutCustomers = customers.stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.WAIT_CHECKOUT)). + collect(Collectors.toList()); + var atCheckoutCustomers = customers.stream(). + filter(it -> it.getMode().equals(Customer.ModeEnum.AT_CHECKOUT)). + collect(Collectors.toList()); + var newAwaitingCheckoutProducts = awaitingCheckoutCustomers.stream(). + filter(it -> !awaitingCustomers.contains(it)). + map(it -> it.getBasket()). + collect(ArrayList::new, List::addAll, List::addAll); + for (ProductInBasket product : newAwaitingCheckoutProducts) { + if (basketProducts.containsKey(product.getId())) { + var countedProduct = basketProducts.get(product.getId()); + countedProduct.setProductCount(countedProduct.getProductCount() + product.getProductCount()); + } else { + basketProducts.put(product.getId(), product); + } + } + awaitingCustomers.addAll(awaitingCheckoutCustomers); + customersOnCheckline.addAll(atCheckoutCustomers); + + } + + private CurrentTickRequest createNextMove(CurrentWorldResponse worldResponse) { + if (worldResponse == null) { // первый ход обрабатывается отдельно + return null; + } + CurrentTickRequest request = new CurrentTickRequest(); + + checkoutAdmin.considerNew(worldResponse.getEmployees(), + worldResponse.getCheckoutLines(), + worldResponse.getCurrentTick()); + inspectChecklines(worldResponse, request); + + // готовимся закупать товар на склад и выставлять его на полки + final List putOnRackCellCommands = merch.inspectRacks(worldResponse); + final List buyStockCommands = merch.inspectStore(); + final List putOffRackCellCommands = merch.removeSold(); + request.setPutOffRackCellCommands(putOffRackCellCommands); + request.setPutOnRackCellCommands(putOnRackCellCommands); + request.buyStockCommands(buyStockCommands); + System.out.println("balbla"); + return request; + } + + /** + * Здесь будет вся работа по управлению кассами: слежение за свободными кассами, ротация кассиров + * + * @param worldResponse + * @param request + */ + private void inspectChecklines(CurrentWorldResponse worldResponse, CurrentTickRequest request) { + // Стратегия "одна работающая касса", кассиры не увольняются + var singleCheckline = worldResponse.getCheckoutLines().get(0); + if (singleCheckline.getEmployeeId() == null) { + var employee = checkoutAdmin.sendToWorkAny(worldResponse.getCurrentTick()); + if (employee != null) { + request.addSetOnCheckoutLineCommandsItem( + new SetOnCheckoutLineCommand().checkoutLineId(singleCheckline.getId()). + employeeId(employee.getId()) + ); + } } } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java index 14eca47..c9c70bf 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/EmployeeRecruitmentOffer.java @@ -12,15 +12,10 @@ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Кадровое агенство. Справочная информация о том, каких сотрудников можно нанять и по какой ставке */ @@ -112,6 +107,16 @@ public int hashCode() { @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Type=").append((employeeType)); + sb.append(", experience: ").append((experience)); + sb.append(", salary: ").append((salary)); + sb.append("}"); + return sb.toString(); + } + + /*@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class EmployeeRecruitmentOffer {\n"); @@ -121,7 +126,7 @@ public String toString() { sb.append(" salary: ").append(toIndentedString(salary)).append("\n"); sb.append("}"); return sb.toString(); - } + }*/ /** * Convert the given object to string with each line indented by 4 spaces diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java index d0de3e3..642fc6c 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/Product.java @@ -12,15 +12,10 @@ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Продукт (товар) находится либо на складе либо может быть выложен на полку в магазине. Один продукт может лежать только на одной полке. Продукт на склад можно докупить по закупочной цене. И выставить на полку по любой цене. Чем дороже цена, тем сложнее покупателям купить товар. */ @@ -158,14 +153,11 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("class Product {\n"); - - sb.append(" id: ").append(toIndentedString(id)).append("\n"); - sb.append(" name: ").append(toIndentedString(name)).append("\n"); - sb.append(" stockPrice: ").append(toIndentedString(stockPrice)).append("\n"); - sb.append(" inStock: ").append(toIndentedString(inStock)).append("\n"); - sb.append(" sellPrice: ").append(toIndentedString(sellPrice)).append("\n"); - sb.append("}"); + sb.append("{").append((id)).append("}"); + sb.append(" ").append((name)); + sb.append(" stock $").append((stockPrice)); + sb.append(" inStock=").append((inStock)); + sb.append(" sell $=").append((sellPrice)); return sb.toString(); } diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java index 9e2c663..b9b4e2d 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/ProductInBasket.java @@ -124,7 +124,7 @@ public boolean equals(java.lang.Object o) { return Objects.equals(this.id, productInBasket.id) && Objects.equals(this.productName, productInBasket.productName) && Objects.equals(this.prie, productInBasket.prie) && - Objects.equals(this.productCount, productInBasket.productCount); + Objects.equals(this.productCount, productInBasket.productCount); } @Override @@ -134,6 +134,17 @@ public int hashCode() { @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("product{").append(id).append("}"); + sb.append(" ").append(productName); + sb.append(" $").append(prie); + sb.append(" Count=").append(productCount); + sb.append(", "); + return sb.toString(); + } + + /*@Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class ProductInBasket {\n"); @@ -144,7 +155,7 @@ public String toString() { sb.append(" productCount: ").append(toIndentedString(productCount)).append("\n"); sb.append("}"); return sb.toString(); - } + }*/ /** * Convert the given object to string with each line indented by 4 spaces diff --git a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java index 0a1de7f..4be5cc6 100644 --- a/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java +++ b/src/main/java/ru/hilariousstartups/javaskills/psplayer/swagger_codegen/model/RackCell.java @@ -2,25 +2,20 @@ * OpenAPI definition * No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen) * - * OpenAPI spec version: v0 - * + * OpenAPI spec version=v0 * - * NOTE: This class is auto generated by the swagger code generator program. + * + * NOTE=This class is auto generated by the swagger code generator program. * https://github.com/swagger-api/swagger-codegen.git * Do not edit the class manually. */ package ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model; -import java.util.Objects; -import java.util.Arrays; -import com.google.gson.TypeAdapter; -import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; import io.swagger.v3.oas.annotations.media.Schema; -import java.io.IOException; + +import java.util.Objects; /** * Продуктовая полка. На полке может находиться товар только одного вида, либо она может быть пустой. Полка может хранить определенное максимальное количество товаров. Также полки разнятся по заметности для покупателя (от 1 - самая незаметная, до 5 - максимально на виду */ @@ -180,15 +175,12 @@ public int hashCode() { @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("class RackCell {\n"); - - sb.append(" id: ").append(toIndentedString(id)).append("\n"); - sb.append(" visibility: ").append(toIndentedString(visibility)).append("\n"); - sb.append(" capacity: ").append(toIndentedString(capacity)).append("\n"); - sb.append(" productId: ").append(toIndentedString(productId)).append("\n"); - sb.append(" productName: ").append(toIndentedString(productName)).append("\n"); - sb.append(" productQuantity: ").append(toIndentedString(productQuantity)).append("\n"); - sb.append("}"); + sb.append("{").append((id)).append("}"); + sb.append(" visibility=").append((visibility)); + sb.append(" capacity=").append((capacity)); + sb.append(" productId=").append((productId)); + sb.append(" productName=").append((productName)); + sb.append(" productQuantity=").append((productQuantity)); return sb.toString(); } diff --git a/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java b/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java new file mode 100644 index 0000000..e27a468 --- /dev/null +++ b/src/test/java/ru/hilariousstartups/javaskills/psplayer/CheckoutAdminTest.java @@ -0,0 +1,93 @@ +package ru.hilariousstartups.javaskills.psplayer; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.CheckoutLine; +import ru.hilariousstartups.javaskills.psplayer.swagger_codegen.model.Employee; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static ru.hilariousstartups.javaskills.psplayer.EmployeeWorkTable.getRestTimeInTicks; +import static ru.hilariousstartups.javaskills.psplayer.EmployeeWorkTable.getWorkShiftInTicks; + +public class CheckoutAdminTest { + + private final static List EMPLOYEE_LIST = Arrays.asList( + new Employee().id(1).experience(10).firstName("E1").salary(100), + new Employee().id(2).experience(40).firstName("E2").salary(300), + new Employee().id(3).experience(70).firstName("E3").salary(560) + + ); + + private CheckoutAdmin service = new CheckoutAdmin(EMPLOYEE_LIST); + + @BeforeEach + public void setup() { + System.out.println("---next test------"); + } + + @Test + public void addNewEmployee() { + var newbie = new Employee().id(10).experience(15).firstName("new").salary(250); + var employees = new ArrayList<>(EMPLOYEE_LIST); + employees.add(newbie); + service.considerNew(employees, Collections.emptyList(), 1); + final int elementsCount = employees.size(); + assertEquals(elementsCount, service.getEmployees().size()); + assertEquals(elementsCount, service.getTimesheet().size()); + final EmployeeWorkTable actual = service.getTimesheet().get(elementsCount - 1); + assertEquals(newbie, actual.getEmployee()); + assertNull(actual.getStartedWork()); + assertNull(actual.getFinishedWork()); + assertNull(actual.getReadyToWork()); + } + + @Test + public void addWorkingEmployee() { + final int employeeId = 10; + var newbie = new Employee().id(employeeId).experience(15) + .firstName("new").salary(250); + var employees = new ArrayList<>(EMPLOYEE_LIST); + employees.add(newbie); + List checkouts = Arrays.asList(new CheckoutLine().employeeId(employeeId)); + final int hireTime = 1; + service.considerNew(employees, checkouts, hireTime); + final int elementsCount = employees.size(); + assertEquals(elementsCount, service.getEmployees().size()); + assertEquals(elementsCount, service.getTimesheet().size()); + final EmployeeWorkTable actual = service.getTimesheet().get(elementsCount - 1); + assertEquals(newbie, actual.getEmployee()); + assertEquals(hireTime, actual.getStartedWork()); + assertEquals(hireTime + getWorkShiftInTicks(), actual.getFinishedWork()); + assertEquals(hireTime + getWorkShiftInTicks() + getRestTimeInTicks(), + actual.getReadyToWork()); + } + + @Test + public void workersPerDay() { + int minutesInDay = 60 * 24; + assertEquals(minutesInDay, getWorkShiftInTicks() + getRestTimeInTicks()); + } + + @Test + void assignNextEmployeeToWork() { + final int firstShift = 2; + final int secondShift = firstShift + getWorkShiftInTicks(); + final int thirdShift = firstShift + 2 * getWorkShiftInTicks(); + final int fourthShift = firstShift + 3 * getWorkShiftInTicks(); + var first = service.sendToWorkAny(firstShift); + System.out.println("after first " + service.getTimesheet()); + var second = service.sendToWorkAny(secondShift); + System.out.println("after second = " + service.getTimesheet()); + var third = service.sendToWorkAny(thirdShift); + System.out.println("after third = " + service.getTimesheet()); + + var fourth = service.sendToWorkAny(fourthShift); + assertEquals(first, fourth); + } +} diff --git a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java index 3591699..9cce6a5 100644 --- a/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java +++ b/src/test/java/ru/hilariousstartups/javaskills/psplayer/PerfectStorePlayerApplicationTests.java @@ -1,12 +1,9 @@ package ru.hilariousstartups.javaskills.psplayer; -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - //@SpringBootTest class PerfectStorePlayerApplicationTests { -// @Test + // @Test void contextLoads() { }