11-Flask

Flask(Part.39)| 【レジ管理アプリケーションのプログラミング(1)】

python| まとめ | 現役エンジニア&プログラミングスクール講師「python」のまとめページです。pythonに関して抑えておきたい知識や文法やにについて記事をまとめています。まとめページの下部には「おすすめの学習書籍」「おすすめのITスクール情報」「おすすめ求人サイト」について情報を掲載中...

目標

  • 全てのテーブル状況を確認できるテンプレートを実装する(日時での抽出)
  • 各テーブルの注文状況を確認できるテンプレートを実装する

レジ管理アプリケーションの作成

レジ管理アプリケーションの作成

ここからは、商店スタッフが利用するためのレジ管理アプリケーションを作成していきます。具体的には、全てのテーブル状況を確認できたり、各テーブルの注文状況を確認でき、ユーザー側では行えない注文済みの商品の取り消しや、会計を完了させる機能を実装します。

feature-registerappブランチの作成

Git Bushを立ち上げdevelopブランチから次のコマンドを入力してfeature-registerappブランチを作成して、ブランチの切り替えを行います。

git checkout -b feature-registerappコマンド

プロンプトに(feature-registerapp)と表示されます。レジ管理アプリケーションはこのブランチで作成していきます。

レジ管理アプリケーションに必要なディレクトリとファイルの作成

Visual Studio Codeのエクスプローラーから appsディレクトリ に registerappディレクトリを作成します。

apps/registerappディレクトリに次のディレクトリとファイルを作成します。※作成するファイルについては、名前や中身について、今後変更することものもあります。

  • static/css/style.css
  • templates/register_base.html
  • tamplates/register_orders_status.html
  • tamplates/register_bill.html
  • tamplates/register_list.html
  • app.py
  • forms.py
  • models.py

レジ管理アプリケーションのプログラミング

この記事では、全てのテーブル状況(伝票)を確認できるテンプレートとロジックを実装します。その時に、ある日時以降の伝票のみを出力できるようにします。この為、伝票の作成時間(created_at)をBillモデルに追加します。

tzdata のインストール

Python の zoneinfo モジュールを使用して”Asia/Tokyo” などの特定のタイムゾーン情報を扱う場合、tzdata が必要となります。

Python 3.9 以降では zoneinfo.ZoneInfo を使ってタイムゾーンを扱えますが、OS によっては tzdata が必要となります。

「pip install tzdata」コマンドの利用

(venv) C:\Users\User\Desktop\FlaskProj>pip install tzdata
と入力します。

models.pyの編集(ordersapp内)

ordersapp内のmodels.pyファイルを次のように編集します。

from datetime import timedelta, timezone

from sqlalchemy.sql import func

from apps.common.db import db

JST = timezone(timedelta(hours=9))


# 伝票(Bill)モデル
class Bill(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    status = db.Column(
        db.String(20), default="paid"
    )  # 伝票のステータス(Order in progress, pending, paid)
    table_id = db.Column(
        db.Integer, db.ForeignKey("table.id"), nullable=False
    )  # テーブルとの関連
    orders = db.relationship("Order", backref="bill", lazy=True, cascade="all, delete")

    table = db.relationship(
        "Table", backref="bill_table", uselist=False, overlaps="table,table"
    )  # 1つのテーブルに1つの伝票
    created_at = db.Column(db.DateTime, default=func.now(), server_default=func.now())

    def __repr__(self):
        return f"<Bill {self.id} - Status: {self.status}>"

    def get_created_at_jst(self):
        """JST に変換して取得"""
        if self.created_at is None:
            return None
        return self.created_at.replace(tzinfo=timezone.utc).astimezone(JST)


# 注文(Order)モデル
class Order(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    table_id = db.Column(db.Integer, db.ForeignKey("table.id"), nullable=False)
    bill_id = db.Column(db.Integer, db.ForeignKey("bill.id"), nullable=True)
    product_id = db.Column(db.Integer, db.ForeignKey("product.id"), nullable=False)
    product_name = db.Column(db.String(100), nullable=False)
    quantity = db.Column(db.Integer, nullable=False)
    total = db.Column(db.Float, nullable=False)  # Float に変更

    table = db.relationship("Table", backref="orders", lazy=True)
    product = db.relationship("Product", backref="orders", lazy=True)

    def __repr__(self):
        return f"<Order {self.id} - Table {self.table_id} - {self.product_name} x {self.quantity}>"
created_at = db.Column(db.DateTime, default=func.now(), server_default=func.now())

SQLAlchemy を使ったデータベースのカラム定義しています。

db.DateTime:日時型のカラムです。

server_default=func.now(): データベース側で、レコードが作成された時点の CURRENT_TIMESTAMP(現在時刻)を設定しています。

server_default=func.now() を指定すると、Python 側で datetime.utcnow() をセットしなくても、データベースの現在時刻が自動的に created_at に格納されます。注意点はデータベース側の時間指定が日本時間かそうでないかによって扱い方が変わる点です。日本時間でない場合は、利用時にプログラム側で日時を修正するプログラムが必要となります。

    def get_created_at_jst(self):
        """JST に変換して取得"""
        if self.created_at is None:
            return None
        return self.created_at.replace(tzinfo=timezone.utc).astimezone(JST)

このメソッドは、取得した時間データを日本標準時に変換するためのメソッドです。self.created_at が None の場合、None を返して処理を終了します。

self.created_at.replace(tzinfo=timezone.utc)

created_at は、デフォルトで naive な datetimeであってタイムゾーン情報がありません。ここで、tzinfo=timezone.utc を設定し、明示的に UTC として扱います。

.astimezone(JST)
JST(日本標準時)に変換する。

created_at は DB に保存される際、通常 UTC で保存されます。利用時には
replace(tzinfo=timezone.utc) で明示的に UTC を指定した後、
.astimezone(JST) で JST に変換します。またJSTはプログラム内で定義する必要があります。(registerapp/app.pyファイルで定義しています。)

app.pyの編集(registerapp内)

app.pyファイルを次のように編集します。

import os
from datetime import datetime
from zoneinfo import ZoneInfo

from flask import Blueprint, render_template, request

from apps.ordersapp.models import JST, Bill, Order

register_bp = Blueprint(
    "register",
    __name__,
    template_folder=os.path.join(os.getcwd(), "apps", "registerapp", "templates"),
    static_folder=os.path.join(os.getcwd(), "apps", "registerapp", "static"),
)


def create_register_app(app):
    """Blueprintを新規作成し、登録する関数"""
    return register_bp


@register_bp.route("/list")
def register_list():
    filter_date_str = request.args.get("filter_date")  # URLパラメータから日付取得
    filter_date = None

    if filter_date_str:
        filter_date_str = filter_date_str.replace("T", " ")
        naive_datetime = datetime.strptime(filter_date_str, "%Y-%m-%d %H:%M")
        # 日本時間(JST)を適用
        filter_date = naive_datetime.replace(tzinfo=JST)
        print(filter_date)

    # Bill を基準に取得(作成日で降順ソート)
    query = Bill.query
    if filter_date:
        query = query.filter(Bill.created_at >= filter_date.astimezone(ZoneInfo("UTC")))

    bills = query.order_by(Bill.created_at.desc()).all()

    # 各 Bill に合計金額を追加
    for bill in bills:
        bill.total_price = sum(order.total for order in bill.orders)

    return render_template("register_list.html", title="伝票一覧", bills=bills)


@register_bp.route("/bill/<int:bill_id>")
def view_bill(bill_id):
    bill = Bill.query.get_or_404(bill_id)
    print(bill)
    # 指定された bill_id に関連する注文を取得
    orders = Order.query.filter_by(bill_id=bill.id).all()
    # print(orders)

    # 合計金額を計算
    total_price = sum(order.total for order in orders)

    return render_template(
        "register_bill.html",
        bill=bill,
        orders=orders,
        total_price=total_price,
        title="明細",
    )
from datetime import datetime
from zoneinfo import ZoneInfo

datetime: 日時の操作に使用します。

zoneinfo.ZoneInfo: タイムゾーンの管理(JST=日本標準時の指定)を行うものです。

datetime の replace(tzinfo=JST) で JST に変換可能となります。

@register_bp.route("/list")
def register_list():
    filter_date_str = request.args.get("filter_date")  # URLパラメータから日付取得
    filter_date = None

    if filter_date_str:
        filter_date_str = filter_date_str.replace("T", " ")
        naive_datetime = datetime.strptime(filter_date_str, "%Y-%m-%d %H:%M")
        # 日本時間(JST)を適用
        filter_date = naive_datetime.replace(tzinfo=JST)
        print(filter_date)

    # Bill を基準に取得(作成日で降順ソート)
    query = Bill.query
    if filter_date:
        query = query.filter(Bill.created_at >= filter_date.astimezone(ZoneInfo("UTC")))

    bills = query.order_by(Bill.created_at.desc()).all()

    # 各 Bill に合計金額を追加
    for bill in bills:
        bill.total_price = sum(order.total for order in bill.orders)

    return render_template("register_list.html", title="伝票一覧", bills=bills)

このルートは伝票の一覧表を出力するためのものです。

request.args.get(“filter_date”) で URL パラメータ filter_date を取得(例: ?filter_date=2025-03-25T12:00)します。

filter_date_str.replace(“T”, ” “) で “T” を ” ” に変換(ISOフォーマットの整形)します。この “T” は、例えば、日付 (2025-03-25) と時刻 (12:00) を区切るとき、「2025-03-25T12:00」のように使われる文字です。

datetime.strptime(filter_date_str, “%Y-%m-%d %H:%M”) で datetime オブジェクトに変換します。

filter_date.replace(tzinfo=JST) で JST(日本標準時)を適用します。

Bill.query から伝票データを取得し、filter_date 以降のデータのみを取得(UTC 変換して比較)します。

bills = query.order_by(Bill.created_at.desc()).all() で作成日の降順に並び替えます。

bill.total_price = sum(order.total for order in bill.orders) で各伝票の合計金額を計算します。

@register_bp.route("/bill/<int:bill_id>")
def view_bill(bill_id):
    bill = Bill.query.get_or_404(bill_id)
    print(bill)
    # 指定された bill_id に関連する注文を取得
    orders = Order.query.filter_by(bill_id=bill.id).all()
    # print(orders)

    # 合計金額を計算
    total_price = sum(order.total for order in orders)

    return render_template(
        "register_bill.html",
        bill=bill,
        orders=orders,
        total_price=total_price,
        title="明細",
    )

このルートは、伝票の詳細を出力するものです。

bill_id を受け取り、該当の伝票 (Bill) を取得します。
Bill.query.get_or_404(bill_id) で存在しない場合は 404 エラーを返します。
Order.query.filter_by(bill_id=bill.id).all() で該当の伝票に紐づく注文 (Order) を取得します。
sum(order.total for order in orders) で合計金額を計算します。

apps.pyの編集(ordersapp内)

ordersapp内のapps.pyファイルを次のように編集します。

import os

from flask import Blueprint, redirect, render_template, request, session, url_for

from apps.common.db import db
from apps.ordersapp.forms import OrderForm
from apps.ordersapp.models import Bill, Order
from apps.productsapp.models import Category, Product
from apps.tablesapp.models import Table

orders_bp = Blueprint(
    "orders",
    __name__,
    template_folder=os.path.join(os.getcwd(), "apps", "ordersapp", "templates"),
    static_folder=os.path.join(os.getcwd(), "apps", "ordersapp", "static"),
)


# カートをテーブルIDごとにセッションから取得するヘルパー関数
def get_cart(table_id):
    if f"cart_{table_id}" not in session:
        session[f"cart_{table_id}"] = []
    return session[f"cart_{table_id}"]


def create_orders_app(app):
    """Blueprintを新規作成し、登録する関数"""
    return orders_bp


@orders_bp.route("/order/table/<int:table_id>", methods=["GET"])
def order_menu(table_id):
    table = Table.query.get_or_404(table_id)
    categories = Category.query.all()
    products = Product.query.all()
    cart = get_cart(table_id)

    # 現在のテーブルの注文履歴を取得
    orders = Order.query.filter_by(table_id=table.id).all()

    # Billが存在しない場合は新規作成
    if table.bill is None:
        bill = Bill(status="Order in progress", table_id=table.id)
        db.session.add(bill)
        table.bill = bill  # TableにBillを関連付け
        db.session.commit()
    else:
        bill = table.bill  # すでに存在するBillを使用

    # Billの状態をデバッグ
    print(f"Bill: {bill}")  # ここでbillを参照

    # 合計額を計算する
    total_amount = sum(order.total for order in orders)

    # Billが正しく作成されているかをデバッグ
    print(
        f"Created Bill ID: {table.bill.id}, Status: {table.bill.status}"
    )  # デバッグ用

    return render_template(
        "order_menu.html",
        products=products,
        cart=cart,
        table=table,
        categories=categories,
        total_amount=total_amount,
        orders=orders,  # 注文履歴を渡す
        title="メニュー一覧",
    )


@orders_bp.route(
    "/order/table/<int:table_id>/product/<int:product_id>", methods=["GET", "POST"]
)
def order_form(table_id, product_id):
    table = Table.query.get_or_404(table_id)
    product = Product.query.get_or_404(product_id)
    form = OrderForm()

    if form.validate_on_submit():
        quantity = form.quantity.data
        total = product.price * quantity

        # カートに追加(テーブルごとにセッションを使用)
        cart = get_cart(table_id)
        cart.append(
            {
                "product_id": product.id,
                "product_name": product.name,
                "product_image": product.image_url,
                "quantity": quantity,
                "total": total,
            }
        )
        session.modified = True  # セッションを変更したことを通知
        return redirect(url_for("orders.order_menu", table_id=table.id))

    return render_template(
        "order_form.html",
        form=form,
        product=product,
        table=table,
        title="注文フォーム",
    )


@orders_bp.route("/order/cart/<int:table_id>", methods=["GET", "POST"])
def order_cart(table_id):
    table = Table.query.get_or_404(table_id)
    cart = get_cart(table_id)
    total_amount = sum(item["total"] for item in cart)

    # テーブルに関連するBillを取得(Billが存在しない場合、新規作成)
    if table.bill is None:
        bill = Bill(status="Order in progress", table_id=table.id)
        db.session.add(bill)
        db.session.commit()  # 新規作成したBillをDBに保存
        table.bill = bill  # TableにBillを関連付け
        db.session.commit()  # 変更をコミット
    else:
        bill = table.bill  # 既に関連付けられているBillを使用

    if request.method == "POST":
        # 注文確定処理
        for item in cart:
            # 注文をデータベースに保存
            order = Order(
                table_id=table.id,
                product_id=item["product_id"],
                product_name=item["product_name"],
                quantity=item["quantity"],
                total=item["total"],
                bill_id=bill.id,  # Billを注文に紐づけ
            )
            db.session.add(order)

        db.session.commit()

        # 注文後、カートをクリア
        session[f"cart_{table_id}"] = []
        session.modified = True

        # 確定後、再度注文情報を表示するためにリダイレクト
        return redirect(url_for("orders.order_menu", table_id=table.id))

    return render_template(
        "order_cart.html",
        cart=cart,
        total_amount=total_amount,
        table=table,
        title="注文確認フォーム",
    )


@orders_bp.route("/order/cart/<int:table_id>/update/<int:product_id>", methods=["POST"])
def update_quantity(table_id, product_id):
    table = Table.query.get_or_404(table_id)
    quantity = int(request.form.get("quantity"))
    cart = get_cart(table_id)

    # 商品情報の取得(商品IDから価格などを取得)
    product = Product.query.get_or_404(product_id)

    # カート内の商品数量を更新
    for item in cart:
        if item["product_id"] == product_id:
            item["quantity"] = quantity
            # 商品ごとの合計金額を再計算
            item["total"] = item["quantity"] * product.price

    session.modified = True
    # 合計金額を再計算
    total_amount = sum(item["total"] for item in cart)

    return redirect(
        url_for(
            "orders.order_cart",  # 適切なビュー名に変更
            table_id=table.id,
            cart=cart,
            total_amount=total_amount,
            title="注文確認フォーム",
        )
    )


@orders_bp.route("/order/cart/<int:table_id>/remove/<int:product_id>", methods=["POST"])
def remove_item(table_id, product_id):
    cart = get_cart(table_id)

    # カート内の商品を削除
    cart = [item for item in cart if item["product_id"] != product_id]
    session[f"cart_{table_id}"] = cart  # 更新したカートをセッションに保存
    session.modified = True
    return redirect(url_for("orders.order_cart", table_id=table_id))


@orders_bp.route("/order/table/<int:table_id>/change_status/<status>", methods=["POST"])
def change_status(table_id, status):
    """
    注文のステータスを変更する。
    statusは「Order in progress」から「pending」へ切り替え
    """
    table = Table.query.get_or_404(table_id)
    bill = table.bill  # テーブルに紐づく伝票(Bill)を取得

    # ステータスが変更可能な場合のみ変更
    if bill.status != status:
        bill.status = (
            "pending" if bill.status == "Order in progress" else "Order in progress"
        )
        db.session.commit()

    return redirect(url_for("orders.order_menu", table_id=table.id))

次のルートを修正しています。

@orders_bp.route("/order/table/<int:table_id>", methods=["GET"])
def order_menu(table_id):
    table = Table.query.get_or_404(table_id)
    categories = Category.query.all()
    products = Product.query.all()
    cart = get_cart(table_id)

    # 現在のテーブルの注文履歴を取得
    orders = Order.query.filter_by(table_id=table.id).all()

    # Billが存在しない場合は新規作成
    if table.bill is None:
        bill = Bill(status="Order in progress", table_id=table.id)
        db.session.add(bill)
        table.bill = bill  # TableにBillを関連付け
        db.session.commit()
    else:
        bill = table.bill  # すでに存在するBillを使用

    # Billの状態をデバッグ
    print(f"Bill: {bill}")  # ここでbillを参照

    # 合計額を計算する
    total_amount = sum(order.total for order in orders)

    # Billが正しく作成されているかをデバッグ
    print(
        f"Created Bill ID: {table.bill.id}, Status: {table.bill.status}"
    )  # デバッグ用

    return render_template(
        "order_menu.html",
        products=products,
        cart=cart,
        table=table,
        categories=categories,
        total_amount=total_amount,
        orders=orders,  # 注文履歴を渡す
        title="メニュー一覧",
    )

次は具体的な修正・追記内容です。

    # Billが存在しない場合は新規作成
    if table.bill is None:
        bill = Bill(status="Order in progress", table_id=table.id)
        db.session.add(bill)
        table.bill = bill  # TableにBillを関連付け
        db.session.commit()
    else:
        bill = table.bill  # すでに存在するBillを使用

ここでは、db.session.commit()をtable.bill = billの後で行うように変更しています。
また、すでにBillが存在する場合は、そのBillを使用するようにelse節を追加しています。

print(f"Bill: {bill}")

Bill オブジェクトの情報を デバッグ表示。

print(
    f"Created Bill ID: {table.bill.id}, Status: {table.bill.status}"
) 

伝票の IDとステータス を出力し、正常に作成されているか確認しています。

register_base.htmlの編集(registerapp/templates内)

register_base.html を次のように編集します。

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>
        {% block title %}
        {% endblock %}
    </title>
    <link rel="stylesheet" href="{{ url_for('register.static', filename='css/style.css') }}">
</head>

<body>

    <header>
        {% block header %}
        <!-- 個別ページのヘッダーがここに入る -->
        {% endblock %}
    </header>

    <main>
        {% block content %}
        <!-- 個別ページのコンテンツがここに入る -->
        {% endblock %}
    </main>

    <footer>
        <p>© 2025 howahowa store</p>
    </footer>
</body>

</html>

register_list.htmlの編集(registerapp/templates内)

register_list.html を次のように編集します。

{% extends 'register_base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h1>{{ title }}</h1>

<form method="GET" action="{{ url_for('register.register_list') }}" class="filter-form">
    <label for="filter_date">日付と時間を選択:</label>
    <input type="datetime-local" id="filter_date" name="filter_date" value="{{ request.args.get('filter_date', '') }}">
    <button type="submit">フィルタ</button>
</form>

<div>
    <h2>Pending</h2>
    <table>
        <thead>
            <tr>
                <th>伝票番号</th>
                <th>テーブル番号</th>
                <th>作成日時</th>
                <th>合計金額</th>
                <th>ステータス</th>
                <th>詳細</th>
            </tr>
        </thead>
        <tbody>
            {% for bill in bills %}
            {% if bill.status == 'pending' %}
            <tr>
                <td>{{ bill.id }}</td>
                <td>{{ bill.table_id }}</td>
                <td>{{ bill.get_created_at_jst().strftime('%Y-%m-%d %H:%M') if bill.get_created_at_jst() else '' }}</td>
                <td>{{ bill.total_price | int }}</td>
                <td>会計予定</td>
                <td class="bill-detail"><a href="{{ url_for('register.view_bill', bill_id=bill.id) }}"
                        class="button-link">詳細</a></td>
            </tr>
            {% endif %}
            {% endfor %}
        </tbody>
    </table>
</div>

<div>
    <h2>Order in Progress</h2>
    <table>
        <thead>
            <tr>
                <th>伝票番号</th>
                <th>テーブル番号</th>
                <th>作成日時</th>
                <th>合計金額</th>
                <th>ステータス</th>
                <th>詳細</th>
            </tr>
        </thead>
        <tbody>
            {% for bill in bills %}
            {% if bill.status == 'Order in progress' %}
            <tr>
                <td>{{ bill.id }}</td>
                <td>{{ bill.table_id }}</td>
                <td>{{ bill.get_created_at_jst().strftime('%Y-%m-%d %H:%M') if bill.get_created_at_jst() else '' }}</td>
                <td>{{ bill.total_price | int }}</td>
                <td>注文中</td>
                <td class="bill-detail"><a href="{{ url_for('register.view_bill', bill_id=bill.id) }}"
                        class="button-link">詳細</a></td>
            </tr>
            {% endif %}
            {% endfor %}
        </tbody>
    </table>
</div>

<div>
    <h2>Paid</h2>
    <table>
        <thead>
            <tr>
                <th>伝票番号</th>
                <th>テーブル番号</th>
                <th>作成日時</th>
                <th>合計金額</th>
                <th>ステータス</th>
                <th>詳細</th>
            </tr>
        </thead>
        <tbody>
            {% for bill in bills %}
            {% if bill.status == 'paid' %}
            <tr>
                <td>{{ bill.id }}</td>
                <td>{{ bill.table_id }}</td>
                <td>{{ bill.get_created_at_jst().strftime('%Y-%m-%d %H:%M') if bill.get_created_at_jst() else '' }}</td>
                <td>{{ bill.total_price | int }}</td>
                <td>会計済み</td>
                <td class="bill-detail"><a href="{{ url_for('register.view_bill', bill_id=bill.id) }}"
                        class="button-link">詳細</a></td>
            </tr>
            {% endif %}
            {% endfor %}
        </tbody>
    </table>
</div>

{% endblock %}

register_bill.htmlの編集(registerapp/templates内)

register_bill.html を次のように編集します。

{% extends 'register_base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h1>伝票詳細</h1>
<p>伝票番号: {{ bill.id }}</p>
<p>テーブル番号: {{ bill.table_id }}</p>
<p>作成日時: {{ bill.get_created_at_jst().strftime('%Y-%m-%d %H:%M') if bill.get_created_at_jst() else '' }}</p>
<p style="font-size:xx-large;">合計金額: {{ total_price | int }}</p>

<h2>注文内容</h2>
<table>
    <thead>
        <tr>
            <th>商品名</th>
            <th>数量</th>
            <th>金額</th>
        </tr>
    </thead>
    <tbody>
        {% for order in orders %}
        <tr>
            <td>{{ order.product_name }}</td>
            <td>{{ order.quantity }}</td>
            <td>{{ order.total | int }}</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

<a href="{{ url_for('register.register_list') }}" class="button-link-back">戻る</a>
{% endblock %}

style.cssの編集(ordersapp/static/css内)

style.cssを次のように編集します。

body {
    font-family: Arial, sans-serif;
    margin: 20px;
    padding: 20px;
    background-color: #f8f8f8;
}

header {
    margin: 0;
    padding: 10px 0;
}

main {
    margin: 0;
    padding: 20px;
}

footer {
    background-color: #aaa;
    color: white;
    text-align: center;
    padding: 10px 0;
    margin-top: 20px;
}

h1 {
    font-size: 24px;
    font-weight: bold;
    margin-bottom: 30px;
    text-align: center;
}

h2 {
    margin-top: 30px;
    font-size: 20px;
    font-weight: bold;
    color: #333;
}

table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

th,
td {
    padding: 12px;
    text-align: left;
    border: 1px solid #ddd;
}

th {
    background-color: #4CAF50;
    color: white;
}

td {
    background-color: #fff;
}

tr:hover {
    background-color: #f1f1f1;
}

a.button-link {
    display: inline-block;
    padding: 8px 16px;
    background-color: #2196F3;
    color: white;
    text-align: center;
    text-decoration: none;
    border-radius: 5px;
    font-size: 14px;
    cursor: pointer;
    transition: background-color 0.3s ease;
    width: 100px;
    /* 幅を統一 */
    text-align: center;
}

a.button-link:hover {
    background-color: #1976D2;
}

a.button-link-back {
    display: inline-block;
    margin-top: 10px;
    padding: 8px 16px;
    background-color: #f32121;
    color: white;
    text-align: center;
    text-decoration: none;
    border-radius: 5px;
    font-size: 14px;
    cursor: pointer;
    transition: background-color 0.3s ease;
    width: 100px;
    /* 幅を統一 */
    text-align: center;
}

a.button-link-back:hover {
    background-color: #d21919;
}

tr td a.button-link {
    display: inline-block;
    margin: 0;
}

table td {
    word-wrap: break-word;
}

.filter-form {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 8px;
    background-color: #f9f9f9;
}

.filter-form label {
    font-weight: bold;
    color: #333;
}

.filter-form input {
    padding: 8px;
    font-size: 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
}

.filter-form button {
    padding: 8px 15px;
    font-size: 16px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
}

.filter-form button:hover {
    background-color: #0056b3;
}

.bill-detail {
    text-align: center;
    padding: 0;
}

order_menu.htmlの編集(ordersapp/templates内)

現状の「order_menu.html」を表示しています。

{% extends 'order_base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h2>メニュー</h2>

<!-- カテゴリータブ -->
<div class="tab-container">
    <div class="tab active" data-category="all">すべて</div>
    {% for category in categories %}
    <div class="tab" data-category="{{ category.id }}">{{ category.name }}</div>
    {% endfor %}
</div>

<div class="content-container">
    <!-- メニューリスト -->
    <div class="menu">
        {% for product in products %}
        {% if product.status != 'discontinued' %}
        <div class="menu-item" data-category="{{ product.category_id }}" {% if product.status=='sold_out' or
            product.status=='out_of_stock' or table.bill.status=="pending" %}
            style="pointer-events: none; opacity: 0.5;" {% endif %}>
            <a href="{{ url_for('orders.order_form', table_id=table.id, product_id=product.id) }}" target="_self">
                <img src="{{ url_for('products.static', filename=product.image_url) }}" alt="{{ product.name }}">
                <p>{{ product.name }}</p>
                <p>¥{{ product.price }}</p>
                {% if product.status == 'sold_out' %}
                <span class="status-label sold-out-label">完売</span>
                {% elif product.status == 'out_of_stock' %}
                <span class="status-label out-of-stock-label">在庫切れ</span>
                {% endif %}
            </a>
        </div>
        {% endif %}
        {% endfor %}
    </div>

    <!-- 注文リスト(右側に配置) -->
    <div class="order-summary">
        <!-- 合計金額 -->
        <div class="total-amount-container">
            <p><strong>合計金額: ¥{{ total_amount | int }}</strong></p>
        </div>
        <div class="cart-check-container">
            <!-- カート確認ページへのリンク -->
            <a href="{{ url_for('orders.order_cart', table_id=table.id) }}">カートを確認</a>
        </div>

        <!-- 現在の注文リスト -->
        <div class="order-list-container">
            <h3>現在の注文リスト</h3>
            <ul class="order-list">
                {% for order in orders %}
                <li>{{ order.product_name }} x{{ order.quantity }} (¥{{ order.total | int }})</li>
                {% else %}
                <li>現在、注文はありません。</li>
                {% endfor %}
            </ul>
        </div>

        <!-- ステータス変更ボタン -->
        <div class="status-change-container">
            {% if table.bill.status == "Order in progress" %}
            <form action="{{ url_for('orders.change_status', table_id=table.id, status='pending') }}" method="post">
                <button type="submit" class="status-button">会計予定に変更</button>
            </form>
            {% elif table.bill.status == "pending" %}
            <form action="{{ url_for('orders.change_status', table_id=table.id, status='Order in progress') }}"
                method="post">
                <button type="submit" class="status-button">注文可能に戻す</button>
            </form>
            {% endif %}
        </div>
    </div>
</div>

<!-- カテゴリータブの切り替えスクリプト -->
<script>
    document.addEventListener("DOMContentLoaded", function () {
        const tabs = document.querySelectorAll(".tab");
        const items = document.querySelectorAll(".menu-item");

        tabs.forEach(tab => {
            tab.addEventListener("click", function () {
                const category = this.getAttribute("data-category");

                // タブのアクティブ状態を更新
                tabs.forEach(t => t.classList.remove("active"));
                this.classList.add("active");

                // メニューアイテムの表示切り替え
                items.forEach(item => {
                    if (category === "all" || item.getAttribute("data-category") === category) {
                        item.style.display = "block";
                    } else {
                        item.style.display = "none";
                    }
                });
            });
        });
    });
</script>

{% endblock %}

古いデータベースを削除し、アプリケーションを起動します。アプリケーションを起動したら、カテゴリー、商品、テーブルのデータを入力し、いくつかのテーブルで注文を済ませます。

次の表で扱っているデータは、異なる日付の入力をdump.sqlに入力し読み込ませています。

「http://127.0.0.1:5000/register/list」にアクセスします。

いずれかの詳細ボタンをクリックします。次のように詳細情報が表示されます。

ここで、一旦、Gitをコミットしておきます。コミットメッセージは次の通りです。

Add: 伝票一覧の表示機能(20250326)
Add: 伝票の詳細情報を表示する機能(20250326)

今回は設計書などはないため、設計書番号などの提示はなし。レジ管理アプリケーションの実装を開始しています。レジ係が利用する伝票一覧と伝票の詳細情報を確認できるテンプレートとロジックを実装。

git status コマンド

git add . コマンド

git status コマンド

git commit –no-verify コマンド(noの前のハイフンはふたつあります。

ruff によるチェックが消えなかったため「git commit –no-verify」を利用しています。

上書き保存をしてCOMMIT_EDITMSGを閉じます。

今回は以上になります。

「python」おすすめ書籍 ベスト3 | 現役エンジニア&プログラミングスクール講師「python」の学習でお勧めしたい書籍をご紹介しています。お勧めする理由としては、考え方、イメージなどを適切に捉えていること、「生のpython」に焦点をあてて解説をしている書籍であることなどが理由です。勿論、この他にも良い書籍はありますが、特に質の高かったものを選んで記事にしています。ページの下部には「おすすめのITスクール情報」「おすすめ求人サイト」について情報を掲載中。...

ブックマークのすすめ

「ほわほわぶろぐ」を常に検索するのが面倒だという方はブックマークをお勧めします。ブックマークの設定は別記事にて掲載しています。

「お気に入り」の登録・削除方法【Google Chrome / Microsoft Edge】「お気に入り」の登録・削除方法【Google Chrome / Microsoft Edge】について解説している記事です。削除方法も掲載しています。...
【パソコン選び】失敗しないための重要ポイント | 現役エンジニア&プログラミングスクール講師【パソコン選び】失敗しないための重要ポイントについての記事です。パソコンのタイプと購入時に検討すべき点・家電量販店で見かけるCPUの見方・購入者が必要とするメモリ容量・HDDとSSDについて・ディスプレイの種類・バッテリーの持ち時間や保証・Officeソフト・ウィルス対策ソフトについて書いています。...
RELATED POST
11-Flask

Flask(Part.37)| 【商品管理アプリケーションの修正(2)】

2025年3月30日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site
11-Flask

Flask(Part.30)| 【注文管理アプリケーションのプログラミング(6)l(5)の解説】

2025年3月23日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site
11-Flask

Flask(Part.11)| 【ふたつのテーブルの利用(1)実装と実行】

2025年3月4日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site
11-Flask

Flask(Part.22予定)| 【複数アプリケーションの統合(2)】

2025年3月15日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site