Convenience Store Challenge

Місцевий магазин має проблеми з системою реєстрації платежів. Власник попросив вас поглянути на нього та запропонувати деякі ідеї щодо оновлення. Дивлячись на дерев’яні рами з рядами намистин, ви думаєте, це буде складним завданням застосувати сучасний підхід до цього місця та реорганізувати процеси. На щастя, ви розробник Python, який знає, як працювати з різними доходами та результатами даних. Через деякий час обговорювалися обсяги робіт і часові рамки та були написані деякі примітки, щоб розділити весь проект на окремі частини для поетапної реалізації.

Ваші нотатки ось,

Товари та кошик

Бажано почати з основ - об’єктів, з якими ви збираєтеся працювати. На першому етапі головною метою є впровадження моделей даних - класів для роботи з даними в майбутньому.

Товар

Цей клас представляє товари, які можна придбати в магазині.

  1. Кожен екземпляр продукту повинен мати такі атрибути:

    • name - назва товару (прик. «apple», «juice»)

    • price - ціна за одну одиницю товару (прик. 3655, 500, 12999)

    • unit - розмір одиниці окремого товару (прик. 1, 0.500, 12)

    Наприклад: яблуко коштує 1059 за кожні 0,1 кг. Це означає, що name зберігає "apple", price є 1059 і `` unit`` дорівнює 0,1.

  2. Клас Product повинен реалізовувати метод get_total для обчислення загальної ціни на вказану кількість товару для покупки. Бажану кількість буде передано як необов’язковий аргумент числового типу (int або float). Якщо аргумент кількості пропущено, просто використовуйте значення атрибута unit.

Пояснення коду

class conv_store.Product(name: str, price: int, unit: int | float)

Реалізація моделі товару

Змінні:
  • name – назва товару

  • price – вартість однієї одиниці товару (прик. 3655, 500, 12999)

  • unit – розмір одиниці окремого товару (прик. 1, 0.500, 12)

Цей клас представляє товари, які можна придбати в магазині.

get_total(quantity: int | float | None = None) int

Повертає загальну ціну за вказану кількість товару

Параметри:

quantity (int | float, optional) – кількість для покупки, за замовчуванням None

Повертає:

вартість вказаної кількості товару

Тип повернення:

int

Якщо аргумент кількості пропущено, замість нього слід використовувати значення атрибута одиниці.

Тестові приклади

product_obj = Product()
product_obj.name = "candy"
product_obj.price = 1059   # 1059 coins
product_obj.unit = 0.1     # for each 0.1

assert product_obj.get_total(0.7) == 7413  # purchase 0.7 units
assert product_obj.get_total() == 1059

Кошик для покупок

Цей клас представляє контейнер для продуктів. Його основна відповідальність - зберігати інформацію про покупки та їх кількість.

  1. Кожен екземпляр кошика повинен зберігати дані про об’єкти Продукт у ньому та відповідне значення кількості для кожного окремого продукту.

  2. ShoppingCart має реалізувати метод add_product, щоб помістити вказану кількість у кошик. Аргумент quantity є необов’язковим, якщо пропущено, просто використовує натомість значення Product.unit.

  3. ShoppingCart має застосувати метод get_total для обчислення загальної ціни всього вмісту кошика.

Пояснення коду

class conv_store.ShoppingCart

Реалізація моделі кошика

Змінні:
  • products – товар, доданий до екземпляра кошика

  • quantities – відповідна кількість для товару у кошику

Загалом кошик для покупок – це контейнер для продуктів. Екземпляри цього класу обробляють продукт і відповідну кількість для кожного товару в екземплярі кошика для покупок.

add_product(product: Product, quantity: int | float | None = None) None

Додає товар до кошика

Параметри:
  • product – екземпляр товару, щоб додати до кошика

  • quantity (int | float, optional) – кількість продукту для додавання. За замовчуванням значення одиниці продукту.

Цей метод додає екземпляр продукту та відповідне значення кількості до кошика.

get_total() int

Повертає загальну вартість усіх товарів у кошику

Повертає:

загальна вартість кошика

Тип повернення:

int

Тестові приклади

product_obj = Product()
product_obj.name = "juice"
product_obj.price = 3655
product_obj.unit = 1
cart_obj = ShoppingCart()
cart_obj.add_product(product_obj, 3)  # put 3 packs of juice to cart
cart_obj.add_product(product_obj)     # add one more (unit = 1)

assert cart_obj.get_total() == 14620  # 3655 x 4

Ініціалізація, представлення та приведення типів

Важко встановлювати властивості одну за одною, також неінформативноотримувати представлення рядків об’єктів за замовчуванням. Настав час це виправити.

  1. Product має бути ініціалізований усіма необхідними даними, без значень за замовчуванням.

  2. Застосуйте ShoppingCart.__init__, щоб розділити продукти та кількість між різними візками.

  3. Надайте представлення, зрозумілі людині. Наприклад:

    • Product('juice', 35.66, 1)

    • <ShoppingCart>

  4. Під час приведення екземпляра продукту до типу str він повинен дорівнювати значенню атрибута name.

  5. Під час приведення екземпляра продукту до типу float він має дорівнювати значенню його атрибута price.

  6. Під час приведення екземпляра кошика для покупок до типу float він має дорівнювати загальному значенню ціни.

  7. Під час трансляції екземпляра кошика для покупок до bool вважайте його True, якщо принаймні один продукт приєднаний до поточного кошика.

  8. Запровадити підтримку оператора рівності для ваших об’єктів:

    • вважати продукти рівними, якщо всі їхні властивості однакові

    • вважати кошики рівними, якщо продукти та відповідна кількість однакові

Тестові приклади

candy = Product("candy", 1059, 0.1)
sweet = Product("candy", 1059, 0.1)
juice = Product("juice", 3655, 1)
cart_1 = ShoppingCart()
cart_2 = ShoppingCart()
cart_1.add_product(candy, 1)
cart_1.add_product(sweet, 0.5)
cart_2.add_product(juice)

assert cart_1.get_total() == 15885
assert str(candy) == "candy"
assert float(candy) == 10.59
assert float(cart_2) == 36.55
assert candy == sweet
assert sweet != juice
assert cart

Опрацювання платежів

Власник попросив вас запровадити гнучку платіжну систему. Придбання картки покупок складається з кількох кроків:

  • перевірка кошика - він не повинен бути порожнім або вже придбаним

  • підтвердження платежу - різні типи платежу вимагають різних перевірок

  • купівля кошика

Наразі в магазині доступні два типи оплати: готівка та кредитна картка, але їх можна продовжити в будь-який час.

  1. Оновіть клас ShoppingCart для обробки стану purchased. Зробіть цю властивість protected, оскільки до неї не слід звертатися поза екземпляром картки.

  2. Реалізуйте клас PaymentValidator з is_valid, який не приймає аргументів і повертає значення логічного типу. Це абстрактний клас для майбутнього використання.

  3. Реалізуйте клас PaymentProcessor з методом purchase, який бере об’єкт ShoppingCart і нічого не повертає. Це абстрактний клас для майбутнього використання.

  4. Успадкувати CashPaymentValidator від базового валідатора. Екземпляри цього класу вважаються завжди дійсними.

  5. Успадкувати CodeValidator від основного валідатора.

    • Екземпляри цього класу створено за допомогою аргументу security_code.

    • Метод is_valid має запитати у клієнта код безпеки та перевірити його зі збереженим значенням. Якщо коди однакові, оплата вважається дійсною.

  6. Створіть CashPaymentProcessor, який поєднує поведінку CashValidator і PaymentProcessor. Під час купівлі в кошику з’являються повідомлення «Обробка готівкового платежу…» і «Рахунок у кошику : {float total}» слід роздрукувати.

  7. Створіть CardPaymentProcessor, який поєднує поведінку CodeValidator і PaymentProcessor. Під час купівлі кошику слід роздрукувати повідомлення «Обробка платежу карткою…» і «Код безпеки : {code}».

Тестові приклади

cart = ShoppingCart()
cart.add_product(Product("juice", 3655, 1), 1)

cash_processor = CashPaymentProcessor()
cash_processor.purchase(cart)  # Cart bill: 36.55

card_processor = CardPaymentProcessor("1234")
card_processor.purchase(cart)  # Security code: 1234

Більше покращень для кошиків для покупок

  1. Зробіть ваш ShoppingCart справжнім контейнером

    • Реалізуйте len(cart_obj) і змусьте його повертати кількість продуктів у кошику.

    • Реалізуйте поведінку cart[...], щоб повертати tuple, що містить товар та відповідну кількість (type hint: Tuple[Product, Union[int, float]]).

  2. Зробіть ваш ShoppingCart повторюваним - дозвольте йому надавати примірник продукту та відповідну кількість для кожної ітерації.

  3. Уникайте дублювання продуктів. Якщо хтось намагається покласти продукт у кошик, і цей продукт уже там присутній, не робіть цього вдруге - змініть натомість відповідне значення кількості.

  4. Застосуйте метод remove_product, щоб повністю видалити деякі продукти з кошика.

  5. Застосуйте sub_product, щоб зменшити кількість продукту. Якщо кількість дорівнює 0 (нуль) або менше - вилучіть продукт із кошика.

Тестові приклади

candy = Product("candy", 1059, 0.1)
sweet = Product("candy", 1059, 0.1)
juice = Product("juice", 3655, 1)
cart = ShoppingCart()
cart.add_product(candy, 0.75)
cart.add_product(sweet, 0.75)
cart.add_product(juice, 3)

assert len(cart) == 2
assert cart[0] == candy, 1.5  # this may use other value as key
for cart_item, purchase in zip(cart, ((candy, 1.5), (juice, 3))):
    assert cart_item == purchase

cart.remove_product(candy)
assert len(cart) == 1
cart.sub_product(juice, 2)
assert cart[0][1] == 2
cart.sub_product(juice, 2)
assert not cart

Тестування програмного забезпечення

Додати автотести для моделей ShoppingCart і Product.

  1. Тести мають бути розташовані всередині каталогу «tests».

  2. Для тестування використовуватимуться бібліотеки pytest і coverage.

  3. Залежності проекту потрібно оновити.