Mini Project: POS System

เอาล่ะ !! หลังจากเราเรียนรู้วิธีการสร้าง API กันมาคร่าว ๆ แล้ว เราจะนำความรู้มาสร้างเป็น Application ของเรากัน โดยขอให้ทุกคนเริ่มต้นด้วย Skeleton Code นี้

main.py
from typing import Union
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()

class Product:
    @staticmethod
    def is_valid_barcode(barcode_no: str) -> bool:
        return len(barcode_no) == 13 and barcode_no.isdigit()
    
    class ProductDTO(BaseModel):
        barcode_no: str
        name: str
        price: float
        
    def __init__(self, barcode_no: str, name: str, price: float) -> None:
        if not Product.is_valid_barcode(barcode_no):
            raise ValueError("Barcode number is invalid")
        
        self.barcode_no = barcode_no
        self.name = name
        self.price = price
        
    def to_dict(self) -> dict:
        return {
            "barcode_no": self.barcode_no,
            "name": self.name,
            "price": self.price
        }

class POSController:
    def __init__(self) -> None:
        self.products_list: list[Product] = []
    
    def add_product(self, product: Product):
        if self.get_product_by_barcode(product.barcode_no):
            raise ValueError(f"Product with the barcode '{product.barcode_no}' already exists")
        
        self.products_list.append(product)
        
    def get_product_by_barcode(self, barcode_no: str) -> Union[Product, None]:
        for product in self.products_list:
            if product.barcode_no == barcode_no:
                return product
        return None
    
    def get_all_products(self) -> list[dict]:
        return [product.to_dict() for product in self.products_list]
    
    def delete_product_by_barcode(self, barcode_no: str):
        product = self.get_product_by_barcode(barcode_no)
        if not product:
            raise ValueError("Product not found")
        self.products_list.remove(product)

controller = POSController()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.post("/products", response_model=Union[Product.ProductDTO, dict])
def create_product(product: Product.ProductDTO):
    try:
        new_product = Product(product.barcode_no, product.name, product.price)
        controller.add_product(new_product)
        return new_product.to_dict()
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/products/{barcode_no}", response_model=Union[Product.ProductDTO, dict])
def get_product_by_barcode(barcode_no: str):
    try:
        product = controller.get_product_by_barcode(barcode_no)
        if not product:
            raise HTTPException(status_code=404, detail="Product not found")
        return product.to_dict()
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.get("/products")
def get_all_products():
    return controller.get_all_products()

@app.delete("/products/{barcode_no}")
def delete_product_by_barcode(barcode_no: str):
    try:
        controller.delete_product_by_barcode(barcode_no)
        return {"message": "Product deleted successfully"}
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

Controller Class 🤖

สำหรับ Best Practice; เราจะไม่ใส่ Bussiness Logic (ตรรกะการทำงานของโปรแกรม) ไว้ในส่วน View (ฟังก์ชันที่ Handle Request) แต่ละให้ View เรียกใช้ Controller เป็น Interface แทน ทำให้ลดความซับซ้อนของโปรแกรมได้ เช่น เมื่อมีการเรียก Endpoint GET /products ฟังก์ชัน get_all_products() จะเรียก Method get_all_products() ใน Controller

สังเกตว่า Logic จะอยู่ใน Controller Class และเรา Abstract Logic ที่ซับซ้อนเหล่านั้นด้วย Method เหมือนกับที่เราใช่ Method ของ FastAPI โดยที่เราไม่ต้องรู้ว่าข้างในมัน Implement ยังไงนั่นแหละ (คิดสภาพว่าเราต้องมา Implement Logic พวกนั้นเองสิ 😰)

Requirements 😎

ตอนนี้เรามี Endpoint ต่อไปนี้

Endpoint
Path Variable / Request Body
Description

GET /

-

Read Root

GET /products

-

Return ข้อมูล Products ทั้งหมดที่มีในระบบ POS

GET /products/{barcode_no}

barcode_no: str

Return ข้อมูล Product ตามหมายเลข barcode_no ใน Path Variable

POST /products

เพิ่ม Product ใหม่เข้าไปในระบบ

DELETE /products/{barcode_no}

barcode_no: str

ลบ Product ตามหมายเลข barcode_no ใน Path Variable

ให้สร้าง Endpoint เพิ่มเติมตามความเหมาะสม โดยให้รองรับระบบต่อไปนี้

POST /orders

  • สามารถซื้อสินค้าได้ โดยการซื้อสินค้ารับ Request Body ตามรูปแบบที่กำหนด การส่งคำสั่งในการซื้อสินค้าหนึ่งครั้ง นับเป็น 1 Order

และ Return เป็นรายละเอียดของ Order ซึ่งต้องมีการแสดงรายละเอียดของสินค้า และราคารวมทั้ง Order ดังตัวอย่าง

GET /products/{barcode_no}/order

  • สามารถตรวจสอบได้ว่า สินค้าใด ๆ มีประวัติการซื้อเป็นอย่างไรบ้าง โดยต้องระบุเวลาที่ซื้อ จำนวนที่ซื้อ และราคารวมต่อ Order และยอดขายทั้งหมดของที่สินค้าประเภทนั้นขายได้ กำหนดให้มีรูปแบบ Response ตามที่กำหนด

GET /customers/{customer_name}/orders

  • สามารถตรวจสอบได้ว่าลูกค้า (customer) ใด ๆ มีการซื้อสินค้าใด ๆ ไปบ้าง และรวมราคาว่าลูกค้าคนนั้นซื้อของในระบบเท่าไร (total_spending)

รูปแบบ Response สามารถปรับเปลี่ยนได้ตามความเหมาะสม แต่ให้ยังคงหลักการและข้อมูลที่ครบถ้วนเช่นเดิม

Last updated

Was this helpful?