2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

通信環境が悪い学園祭でも動く「オフライン単独レジ(POS)」を1枚のHTMLで作った

2
Posted at

はじめに

学園祭の模擬店で使うレジ(POS)システムを作りました。

作るにあたって最初に決めたのは、「クラウドと通信しない」 という方針です。

経験上、人がたくさん集まる場所は通信環境が悪く、クラウド連携型のサービスはうまく動きません。会計のたびに通信待ちが発生したり、最悪その場で使えなくなったりします。学園祭当日にレジが止まるのは致命的です。

そこで本プログラムは、外部と一切通信せず、その端末だけで完結して動く ことを目的に、あえてシンプルな構成にしました。

  • 🔌 インストール不要index.html をブラウザで開くだけ
  • 📴 オフライン動作 … ネットがなくても会計できる
  • 📱 スマホ・タブレット対応 … 画面幅で自動レイアウト切替
  • 🗄 サーバー・アカウント不要 … データは端末内に保存

リポジトリはこちらです。

デモ用スクリーンショット

作ったもの

学園祭の模擬店運営に必要な機能を、下部タブ5つに整理しました。

タブ 内容
🧾 レジ 商品をタッチしてカゴに追加、合計・お預かり・おつりを自動計算
⚙️ 設定 釣銭準備金(初期お釣り)の登録と、商品の登録/編集/削除
📊 売上 総売上・会計件数・客単価、商品別ランキング、時間帯別グラフ
🕑 履歴 過去の会計を一覧・取り消し(取消時は在庫を自動復元)
🔒 締め 交代時に「釣銭準備金+売上」と実際の現金を照合し、過不足を記録

技術構成

いわゆる「モダンな構成」は使っていません。依存ライブラリゼロ・ビルドなし・素の HTML / CSS / JavaScript です。

  • フロント:1枚の index.html(HTML + CSS + JS を内包)
  • データ保存:ブラウザの localStorage
  • フレームワーク:なし
  • ビルドツール:なし

理由はシンプルで、当日トラブっても中身を全部読める・直せる ことを重視したからです。ファイル1枚なら、USBメモリやLINEで配っても動きますし、ビルド環境がなくても改修できます。

設計のポイント

1. データは端末内に持ち、通信しない

状態はすべて localStorage に JSON で保存します。読み込み時に既定値とマージすることで、機能追加でキーが増えても古いデータが壊れないようにしています。

const KEY = "gakusai_pos_v1";
const DEFAULT = { products:[], sales:[], closings:[], float:0, seq:{p:1,s:1,c:1} };

function load(){
  try{
    const raw = localStorage.getItem(KEY);
    if(!raw) return structuredClone(DEFAULT);
    const d = JSON.parse(raw);
    // 既定値にマージ → 後から追加したキー(例: float)も安全に埋まる
    return Object.assign(structuredClone(DEFAULT), d);
  }catch(e){ return structuredClone(DEFAULT); }
}

function save(){
  try{ localStorage.setItem(KEY, JSON.stringify(DB)); }
  catch(e){ toast("保存に失敗しました"); }
}

2. 在庫は「取り消し」で元に戻す

会計を取り消したら在庫も自動で戻します。学園祭のレジは操作ミスが起きやすいので、「取消したら売った分の在庫が復活する」という当たり前の挙動を保証しました。

function voidSale(id){
  const s = DB.sales.find(x=>x.id===id);
  if(!s || s.voided) return;
  s.voided = true;
  // 売った分の在庫を戻す
  s.items.forEach(i=>{
    const p = findProduct(i.productId);
    if(p && p.stock!=null){ p.sold = Math.max(0, p.sold - i.qty); }
  });
  save();
}

3. 「締め」でレジの現金を照合する

一番こだわったのが締め(レジ点検)機能です。交代時などに、開店時にレジへ入れた釣銭準備金(初期お釣り)+その間の売上 と、実際に数えた現金が合うかを照合します。

// 想定レジ現金 = 釣銭準備金 + 前回の締め以降の売上
const drawer = float + expected;
// 過不足 = 数えた現金 − 想定レジ現金
const diff = counted - drawer;

過不足がひと目で分かるので、「誰かの回のときに合わなかった」を後から追えます。売上だけでなく釣銭準備金を計算に含めているのがポイントです。

4. スマホもタブレットも1つのHTMLで

CSSのメディアクエリだけでレイアウトを出し分けています。

  • スマホ:縦1列(商品グリッド → カート → 会計ボタン)
  • タブレット・横長画面:2カラム(左に商品、右にカートと会計を固定表示)
/* タブレット以上は2カラム。カートは画面に追従(sticky) */
@media (min-width:760px){
  #view-register{
    display:grid;
    grid-template-columns:1fr minmax(300px,340px);
    gap:18px;
    align-items:start;
  }
  .reg-side{ position:sticky; top:60px; }
}

あえて割り切った点

オフライン単独動作を優先した結果、割り切った部分もあります。

  • 複数端末の売上を自動合算する機能はない … レジを分けるなら端末ごとに集計し、締めで合算する運用にします
  • データは端末依存 … 同じ端末・同じブラウザで使い続ける必要があります(サイトデータ削除で消える)

これらは「通信に依存しない」ことの裏返しであり、学園祭という用途ではメリットの方が大きいと判断しました。

まとめ

  • 人が集まる場所=通信が不安定、という前提でオフライン単独動作を選んだ
  • 1枚のHTML+localStorageという枯れた構成で、当日でも直せる安心感を優先した
  • レジ/在庫/売上集計/締め(現金照合)まで、模擬店運営に必要な機能をカバーした

「学園祭のレジ、どうしよう?」と悩んでいる方の参考になれば嬉しいです。


タグ候補: JavaScript HTML CSS 個人開発 localStorage

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?