Загрузка данных


Вот пояснения по каждому классу: что делает и что сказать, если спросят.

Money
Один статический метод format() переводит копейки в строку «1250.50 руб.». Вынесен отдельно, чтобы не дублировать форматирование в Product, Order и ShoppingCart: в требованиях есть «не дублировать логику».

Product (товар)

	•	Все поля private. id сделан final, потому что по условию идентификатор не должен меняться.
	•	Цена хранится в long в копейках, а не в double, чтобы не было ошибок округления (0.1+0.1+0.1 ≠ 0.3).
	•	В конструкторе проверки: пустое имя, отрицательная цена или остаток → IllegalArgumentException.
	•	Склад нельзя менять напрямую, только через increaseStock и decreaseStock. Это инкапсуляция.
	•	decreaseStock возвращает false, если товара не хватает.
	•	isAvailable(q) проверяет, есть ли нужное количество.
	•	setPrice нужен, чтобы показать, что смена цены не влияет на старый заказ.

Customer (покупатель)

	•	Имя и email проверяются в сеттерах, а конструктор вызывает эти же сеттеры. Так проверка написана один раз.
	•	Внутри лежит List<Order> orders, потому что у покупателя может быть несколько заказов.
	•	getOrders() возвращает копию списка, чтобы снаружи его нельзя было испортить.

CartItem (позиция корзины)

	•	Хранит ссылку на Product и количество.
	•	getSubtotal() = цена × количество. Цена берётся из товара в данный момент.
	•	decreaseQuantity не даст уйти в ноль или минус: количество должно быть положительным.

ShoppingCart (корзина)

	•	Внутри ArrayList<CartItem>.
	•	addProduct: сначала проверка quantity > 0. Потом приватный метод findItem ищет, есть ли уже такой товар. Если есть, количество увеличивается, а не создаётся дубль. Склад проверяется с учётом того, что уже лежит в корзине.
	•	changeQuantity считает разницу между новым и текущим количеством и вызывает increase или decrease.
	•	removeProduct, clear, getTotal (сумма всех getSubtotal), printCart для вывода.
	•	Ошибки выводятся сообщением и возвращают false, программа не падает.

OrderItem (позиция заказа)

	•	Все поля final: это «снимок» товара на момент заказа (id, название, цена, количество).
	•	Ссылки на Product нет специально. Если цена в каталоге поменяется, в заказе останется старая.

OrderStatus
enum с тремя состояниями: NEW, PAID, CANCELLED. Так надёжнее, чем строки: опечатку компилятор не пропустит.

Order (заказ)

	•	items и totalInCents объявлены final и считаются один раз в конструкторе, поэтому после оформления их не изменить.
	•	В конструкторе список копируется (new ArrayList<>(items)), чтобы внешний список не влиял на заказ.
	•	markAsPaid() работает только из статуса NEW.
	•	cancel() запрещает отмену оплаченного и уже отменённого заказа.
	•	printOrder() выводит всю информацию.

PaymentMethod (абстрактный класс)

	•	abstract boolean pay(Order): у каждого способа оплаты своя реализация.
	•	printPaymentInfo обычный метод, общий для всех наследников, чтобы не дублировать.
	•	Поле name задаётся через конструктор, а наследники передают его через super(...).

CardPayment, CashPayment, OnlinePayment
Наследники PaymentMethod (отношение «является»: оплата картой является способом оплаты). Каждый переопределяет pay() с @Override, выводит своё сообщение и возвращает true. Реальной оплаты нет, по условию это имитация.

OrderService (сервис)
Здесь вся бизнес-логика, чтобы её не было в main.

	•	createOrder по шагам из задания: корзина не пуста → проверка склада по всем товарам → создание OrderItem → списание со склада → new Order → заказ добавляется покупателю → корзина очищается.
	•	Склад сначала проверяется целиком и только потом списывается. Поэтому если хоть одного товара не хватает, остатки не меняются.
	•	processPayment(order, paymentMethod): проверка статуса NEW, вызов pay(), при успехе markAsPaid(). Это главный пример полиморфизма: метод принимает PaymentMethod, а реально вызывается pay() карты, наличных или онлайна. Без instanceof и приведения типов.
	•	cancelOrder: если order.cancel() вернул true, товары возвращаются на склад. Повторная отмена вернёт false, поэтому товар дважды не вернётся.
	•	У сервиса есть каталог, чтобы по productId из OrderItem найти товар и вернуть его на склад.
	•	nextOrderId раздаёт заказам номера по порядку.

Main
Только демонстрация, логики нет.

	1.	Пункты 1–11 сценария по порядку: товары, покупатели, корзины, изменение и удаление, заказы, оплата картой и онлайн, отмена с возвратом на склад, итоговые остатки.
	2.	Цикл по List<PaymentMethod>, где каждому способу создаётся новый заказ. Это демонстрация полиморфизма.
	3.	Блок «Проверка некорректных ситуаций» по всем 10 пунктам из раздела 6.
	4.	Вспомогательные printTitle и printStock нужны только для красивого вывода.

Если спросят «где тут ООП»:

	•	Инкапсуляция: private поля и доступ только через методы с проверками.
	•	Наследование: три способа оплаты наследуются от PaymentMethod.
	•	Полиморфизм: processPayment работает с любым наследником через общий тип.
	•	Абстракция: абстрактный класс PaymentMethod.