diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0aed9fb --- /dev/null +++ b/.gitignore @@ -0,0 +1,121 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# verification +erp_system/verification/ diff --git a/README_ERP.md b/README_ERP.md new file mode 100644 index 0000000..38523c3 --- /dev/null +++ b/README_ERP.md @@ -0,0 +1,64 @@ +# 統合業務ERPシステム + +このシステムは、Djangoを用いて構築された、実運用を想定したERP(Enterprise Resource Planning)システムです。 +人事、販売、在庫の各管理機能を備え、すぐに業務での利用を開始できます。 + +## 主な機能 + +1. **人事管理 (HR)** + - 従業員情報の登録・管理 + - 部署・役職のマスタ管理 + - 従業員の検索・フィルタリング + +2. **販売管理 (Sales)** + - 顧客情報の管理 + - 商品マスタ管理 + - 受注管理(見積、受注、請求ステータス管理) + - 受注明細のインライン編集 + +3. **在庫管理 (Inventory)** + - 商品ごとの在庫数管理 + - 入出庫履歴(入荷、出荷、返品、廃棄)の記録 + - 在庫数の自動更新機能 + +## セットアップと起動方法 + +### 前提条件 +- Python 3.x がインストールされていること + +### インストール手順 + +1. 依存パッケージのインストール + ```bash + pip install django + ``` + +2. データベースのセットアップ + ```bash + python manage.py migrate + ``` + +3. 初期データの投入(管理者ユーザーとテストデータが作成されます) + ```bash + python populate_data.py + ``` + ※ 管理者ユーザー名: `admin` / パスワード: `adminpass` + +4. サーバーの起動 + ```bash + python manage.py runserver + ``` + +5. アクセス + - ブラウザで `http://127.0.0.1:8000/` にアクセスするとダッシュボードが表示されます。 + - 管理画面へは `http://127.0.0.1:8000/admin/` からアクセスできます。 + +## 運用上の注意 + +- **在庫の更新**: 在庫数は「入出庫履歴 (StockMovement)」を作成することで自動的に増減します。手動で在庫数を変更することも可能ですが、履歴を残すために履歴の登録を推奨します。 +- **受注の入力**: 受注画面で「受注明細」を追加することで、複数の商品を一度に注文登録できます。 + +## 開発者情報 + +- このシステムは拡張性を考慮して設計されています。 +- データベースはデフォルトでSQLiteを使用していますが、`settings.py` を変更することでPostgreSQLなどのRDBMSに容易に移行可能です。 diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/__init__.cpython-312.pyc b/core/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..05dc940 Binary files /dev/null and b/core/__pycache__/__init__.cpython-312.pyc differ diff --git a/core/__pycache__/admin.cpython-312.pyc b/core/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..66a2035 Binary files /dev/null and b/core/__pycache__/admin.cpython-312.pyc differ diff --git a/core/__pycache__/apps.cpython-312.pyc b/core/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..7616018 Binary files /dev/null and b/core/__pycache__/apps.cpython-312.pyc differ diff --git a/core/__pycache__/models.cpython-312.pyc b/core/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..1d116b0 Binary files /dev/null and b/core/__pycache__/models.cpython-312.pyc differ diff --git a/core/__pycache__/views.cpython-312.pyc b/core/__pycache__/views.cpython-312.pyc new file mode 100644 index 0000000..0db59f1 Binary files /dev/null and b/core/__pycache__/views.cpython-312.pyc differ diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 0000000..f046564 --- /dev/null +++ b/core/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin +from .models import CompanyInfo + +@admin.register(CompanyInfo) +class CompanyInfoAdmin(admin.ModelAdmin): + list_display = ('name', 'phone', 'email', 'updated_at') diff --git a/core/apps.py b/core/apps.py new file mode 100644 index 0000000..5ef1d60 --- /dev/null +++ b/core/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CoreConfig(AppConfig): + name = "core" diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..dc3696c --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,65 @@ +# Generated by Django 6.0.2 on 2026-02-16 09:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="CompanyInfo", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="会社名")), + ( + "address", + models.CharField( + blank=True, max_length=255, null=True, verbose_name="住所" + ), + ), + ( + "phone", + models.CharField( + blank=True, max_length=20, null=True, verbose_name="電話番号" + ), + ), + ( + "email", + models.EmailField( + blank=True, + max_length=254, + null=True, + verbose_name="代表メールアドレス", + ), + ), + ( + "website", + models.URLField(blank=True, null=True, verbose_name="ウェブサイト"), + ), + ( + "created_at", + models.DateTimeField(auto_now_add=True, verbose_name="作成日時"), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="更新日時"), + ), + ], + options={ + "verbose_name": "会社情報", + "verbose_name_plural": "会社情報", + }, + ), + ] diff --git a/core/migrations/__init__.py b/core/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/migrations/__pycache__/0001_initial.cpython-312.pyc b/core/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..ed16660 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/core/migrations/__pycache__/__init__.cpython-312.pyc b/core/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..b16bbb7 Binary files /dev/null and b/core/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/core/models.py b/core/models.py new file mode 100644 index 0000000..92c9f81 --- /dev/null +++ b/core/models.py @@ -0,0 +1,17 @@ +from django.db import models + +class CompanyInfo(models.Model): + name = models.CharField(max_length=255, verbose_name="会社名") + address = models.CharField(max_length=255, null=True, blank=True, verbose_name="住所") + phone = models.CharField(max_length=20, null=True, blank=True, verbose_name="電話番号") + email = models.EmailField(null=True, blank=True, verbose_name="代表メールアドレス") + website = models.URLField(null=True, blank=True, verbose_name="ウェブサイト") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="作成日時") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時") + + class Meta: + verbose_name = "会社情報" + verbose_name_plural = "会社情報" + + def __str__(self): + return self.name diff --git a/core/templates/core/index.html b/core/templates/core/index.html new file mode 100644 index 0000000..ba22ea2 --- /dev/null +++ b/core/templates/core/index.html @@ -0,0 +1,94 @@ + + + + + + ERP System + + + + + + +
+
+
+

業務システムへようこそ

+

本システムは、人事、販売、在庫管理を一元的に行うためのプラットフォームです。

+ 管理画面へログイン +
+
+ +
+
+
+

現在の受注件数

+

{{ order_count }} 件

+ 受注一覧を見る +
+
+
+
+

在庫アラート

+

在庫が10個未満の商品があります。

+
    + {% for stock in low_stock_items %} +
  • {{ stock.product.name }}: {{ stock.quantity }}個
  • + {% empty %} +
  • 現在、在庫不足の商品はありません。
  • + {% endfor %} +
+ 在庫状況を確認 +
+
+
+ +
+
+
+ +
+
+

人事管理 (HR)

+

従業員情報の管理、部署・役職の設定を行います。

+ 従業員一覧 » +
+
+
+
+ +
+
+

販売管理 (Sales)

+

顧客情報の管理、見積・受注・請求業務を行います。

+ 受注一覧 » +
+
+
+
+ +
+
+

在庫管理 (Inventory)

+

商品在庫の管理、入出庫履歴の確認を行います。

+ 在庫一覧 » +
+
+
+ + +
+ + diff --git a/core/tests.py b/core/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/core/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/core/views.py b/core/views.py new file mode 100644 index 0000000..4a02752 --- /dev/null +++ b/core/views.py @@ -0,0 +1,14 @@ +from django.shortcuts import render +from sales.models import Order +from inventory.models import Stock + +def index(request): + # 簡易ダッシュボード用のデータ取得 + order_count = Order.objects.count() + low_stock_items = Stock.objects.filter(quantity__lt=10) + + context = { + 'order_count': order_count, + 'low_stock_items': low_stock_items, + } + return render(request, 'core/index.html', context) diff --git a/db.sqlite3 b/db.sqlite3 new file mode 100644 index 0000000..5bf0919 Binary files /dev/null and b/db.sqlite3 differ diff --git a/erp_system/__init__.py b/erp_system/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/erp_system/__pycache__/__init__.cpython-312.pyc b/erp_system/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..370b585 Binary files /dev/null and b/erp_system/__pycache__/__init__.cpython-312.pyc differ diff --git a/erp_system/__pycache__/settings.cpython-312.pyc b/erp_system/__pycache__/settings.cpython-312.pyc new file mode 100644 index 0000000..7be1db0 Binary files /dev/null and b/erp_system/__pycache__/settings.cpython-312.pyc differ diff --git a/erp_system/__pycache__/tests.cpython-312.pyc b/erp_system/__pycache__/tests.cpython-312.pyc new file mode 100644 index 0000000..d807f27 Binary files /dev/null and b/erp_system/__pycache__/tests.cpython-312.pyc differ diff --git a/erp_system/__pycache__/urls.cpython-312.pyc b/erp_system/__pycache__/urls.cpython-312.pyc new file mode 100644 index 0000000..a583792 Binary files /dev/null and b/erp_system/__pycache__/urls.cpython-312.pyc differ diff --git a/erp_system/__pycache__/wsgi.cpython-312.pyc b/erp_system/__pycache__/wsgi.cpython-312.pyc new file mode 100644 index 0000000..22f500c Binary files /dev/null and b/erp_system/__pycache__/wsgi.cpython-312.pyc differ diff --git a/erp_system/asgi.py b/erp_system/asgi.py new file mode 100644 index 0000000..70445e0 --- /dev/null +++ b/erp_system/asgi.py @@ -0,0 +1,16 @@ +""" +ASGI config for erp_system project. + +It exposes the ASGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/asgi/ +""" + +import os + +from django.core.asgi import get_asgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "erp_system.settings") + +application = get_asgi_application() diff --git a/erp_system/settings.py b/erp_system/settings.py new file mode 100644 index 0000000..96d956e --- /dev/null +++ b/erp_system/settings.py @@ -0,0 +1,121 @@ +""" +Django settings for erp_system project. + +Generated by 'django-admin startproject' using Django 6.0.2. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/6.0/ref/settings/ +""" + +from pathlib import Path + +# Build paths inside the project like this: BASE_DIR / 'subdir'. +BASE_DIR = Path(__file__).resolve().parent.parent + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = "django-insecure-(fra)yw3&6crq9wf^(b75$*4zkxw*d17o!z6+9m_v9#8z_$q9%" + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = [ + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "core", + "hr", + "sales", + "inventory", +] + +MIDDLEWARE = [ + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "erp_system.urls" + +TEMPLATES = [ + { + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", + ], + }, + }, +] + +WSGI_APPLICATION = "erp_system.wsgi.application" + + +# Database +# https://docs.djangoproject.com/en/6.0/ref/settings/#databases + +DATABASES = { + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", + } +} + + +# Password validation +# https://docs.djangoproject.com/en/6.0/ref/settings/#auth-password-validators + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/6.0/topics/i18n/ + +LANGUAGE_CODE = "ja" + +TIME_ZONE = "Asia/Tokyo" + +USE_I18N = True + +USE_TZ = True + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/6.0/howto/static-files/ + +STATIC_URL = "static/" diff --git a/erp_system/tests.py b/erp_system/tests.py new file mode 100644 index 0000000..1dadebd --- /dev/null +++ b/erp_system/tests.py @@ -0,0 +1,40 @@ +from django.test import TestCase +from sales.models import Customer, Product, Order, OrderItem +from inventory.models import Stock, StockMovement +from hr.models import Employee, Department, Position +from django.utils import timezone + +class ERPTestCase(TestCase): + def setUp(self): + # 部署・役職・従業員 + self.dept = Department.objects.create(name="Sales", code="SALES") + self.pos = Position.objects.create(title="Manager", rank=1) + self.emp = Employee.objects.create( + first_name="Taro", last_name="Yamada", email="taro@example.com", + department=self.dept, position=self.pos, hire_date=timezone.now().date() + ) + # 顧客・商品 + self.customer = Customer.objects.create(name="Client A", code="C001") + self.product = Product.objects.create(name="Item A", code="P001", price=1000) + + def test_stock_movement(self): + # 在庫移動のテスト + StockMovement.objects.create(product=self.product, movement_type='in', quantity=10) + stock = Stock.objects.get(product=self.product) + self.assertEqual(stock.quantity, 10) + + StockMovement.objects.create(product=self.product, movement_type='out', quantity=3) + stock.refresh_from_db() + self.assertEqual(stock.quantity, 7) + + def test_order_total(self): + # 受注金額の計算テスト + order = Order.objects.create(customer=self.customer, employee=self.emp) + item1 = OrderItem.objects.create(order=order, product=self.product, quantity=2) # 2000 + + order.refresh_from_db() + self.assertEqual(order.total_amount, 2000) + + item2 = OrderItem.objects.create(order=order, product=self.product, quantity=1) # +1000 + order.refresh_from_db() + self.assertEqual(order.total_amount, 3000) diff --git a/erp_system/urls.py b/erp_system/urls.py new file mode 100644 index 0000000..e559b27 --- /dev/null +++ b/erp_system/urls.py @@ -0,0 +1,25 @@ +""" +URL configuration for erp_system project. + +The `urlpatterns` list routes URLs to views. For more information please see: + https://docs.djangoproject.com/en/6.0/topics/http/urls/ +Examples: +Function views + 1. Add an import: from my_app import views + 2. Add a URL to urlpatterns: path('', views.home, name='home') +Class-based views + 1. Add an import: from other_app.views import Home + 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') +Including another URLconf + 1. Import the include() function: from django.urls import include, path + 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) +""" + +from django.contrib import admin +from django.urls import path +from core import views + +urlpatterns = [ + path("", views.index, name="index"), + path("admin/", admin.site.urls), +] diff --git a/erp_system/verification/dashboard.png b/erp_system/verification/dashboard.png new file mode 100644 index 0000000..d9f64e1 Binary files /dev/null and b/erp_system/verification/dashboard.png differ diff --git a/erp_system/verification/verify_dashboard.py b/erp_system/verification/verify_dashboard.py new file mode 100644 index 0000000..8d94109 --- /dev/null +++ b/erp_system/verification/verify_dashboard.py @@ -0,0 +1,25 @@ +from playwright.sync_api import sync_playwright + +def verify_dashboard(): + with sync_playwright() as p: + browser = p.chromium.launch(headless=True) + page = browser.new_page() + + print("Navigating to dashboard...") + page.goto("http://127.0.0.1:8000/") + + print("Verifying title...") + assert "ERP System" in page.title() + + print("Verifying content...") + assert page.get_by_role("heading", name="業務システムへようこそ").is_visible() + assert page.get_by_text("現在の受注件数").is_visible() + + print("Taking screenshot...") + page.screenshot(path="erp_system/verification/dashboard.png") + + browser.close() + print("Verification complete!") + +if __name__ == "__main__": + verify_dashboard() diff --git a/erp_system/wsgi.py b/erp_system/wsgi.py new file mode 100644 index 0000000..dc90040 --- /dev/null +++ b/erp_system/wsgi.py @@ -0,0 +1,16 @@ +""" +WSGI config for erp_system project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/6.0/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "erp_system.settings") + +application = get_wsgi_application() diff --git a/hello.py b/hello.py index a3eba49..8525600 100644 --- a/hello.py +++ b/hello.py @@ -2,20 +2,22 @@ import random import os + def rainbow_text(text): - colors = ['\033[91m', '\033[92m', '\033[93m', '\033[94m', '\033[95m', '\033[96m'] - reset = '\033[0m' - + colors = ["\033[91m", "\033[92m", "\033[93m", "\033[94m", "\033[95m", "\033[96m"] + reset = "\033[0m" + for char in text: color = random.choice(colors) - print(f"{color}{char}{reset}", end='', flush=True) + print(f"{color}{char}{reset}", end="", flush=True) time.sleep(0.1) print() + def matrix_effect(): chars = "01アイウエオカキクケコサシスセスミチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン" width = 50 - + for _ in range(10): line = "" for _ in range(width): @@ -23,32 +25,36 @@ def matrix_effect(): print(f"\033[92m{line}\033[0m") time.sleep(0.05) + def typewriter_effect(text, delay=0.05): for char in text: - print(char, end='', flush=True) + print(char, end="", flush=True) time.sleep(delay) print() + # 文字ごとに色が変化するタイプライター効果 def colored_typewriter_effect(text, delay=0.05): - colors = ['\033[91m', '\033[92m', '\033[93m', '\033[94m', '\033[95m', '\033[96m'] - reset = '\033[0m' + colors = ["\033[91m", "\033[92m", "\033[93m", "\033[94m", "\033[95m", "\033[96m"] + reset = "\033[0m" for char in text: color = random.choice(colors) - print(f"{color}{char}{reset}", end='', flush=True) + print(f"{color}{char}{reset}", end="", flush=True) time.sleep(delay) print() + # シンプルなスピナーアニメーション def spinner_effect(duration=2): - spinner = ['|', '/', '-', '\\'] + spinner = ["|", "/", "-", "\\"] end_time = time.time() + duration idx = 0 while time.time() < end_time: - print(spinner[idx % len(spinner)], end='\r', flush=True) + print(spinner[idx % len(spinner)], end="\r", flush=True) time.sleep(0.1) idx += 1 - print(' ', end='\r') + print(" ", end="\r") + def explosion_effect(): frames = [ @@ -56,15 +62,16 @@ def explosion_effect(): " ✨💥✨ ", " ⭐✨💥✨⭐ ", " 🌟⭐✨💥✨⭐🌟 ", - " 🎆🌟⭐✨💥✨⭐🌟🎆 " + " 🎆🌟⭐✨💥✨⭐🌟🎆 ", ] - + for frame in frames: - os.system('clear' if os.name == 'posix' else 'cls') + os.system("clear" if os.name == "posix" else "cls") print("\n" * 10) print(f"{' ' * 2}{frame}") time.sleep(0.3) + print("🎬 Claude エフェクトショー開始!") time.sleep(1) diff --git a/hello_0718.py b/hello_0718.py index f9ff292..a12f146 100644 --- a/hello_0718.py +++ b/hello_0718.py @@ -1,5 +1,6 @@ def hello_0718(): return "hello 0718" + if __name__ == "__main__": - print(hello_0718()) \ No newline at end of file + print(hello_0718()) diff --git a/hr/__init__.py b/hr/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hr/__pycache__/__init__.cpython-312.pyc b/hr/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..87636b9 Binary files /dev/null and b/hr/__pycache__/__init__.cpython-312.pyc differ diff --git a/hr/__pycache__/admin.cpython-312.pyc b/hr/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..7541326 Binary files /dev/null and b/hr/__pycache__/admin.cpython-312.pyc differ diff --git a/hr/__pycache__/apps.cpython-312.pyc b/hr/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..09616da Binary files /dev/null and b/hr/__pycache__/apps.cpython-312.pyc differ diff --git a/hr/__pycache__/models.cpython-312.pyc b/hr/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..e2aa1f0 Binary files /dev/null and b/hr/__pycache__/models.cpython-312.pyc differ diff --git a/hr/admin.py b/hr/admin.py new file mode 100644 index 0000000..a9b6c77 --- /dev/null +++ b/hr/admin.py @@ -0,0 +1,19 @@ +from django.contrib import admin +from .models import Department, Position, Employee + +@admin.register(Department) +class DepartmentAdmin(admin.ModelAdmin): + list_display = ('name', 'code', 'parent') + search_fields = ('name', 'code') + +@admin.register(Position) +class PositionAdmin(admin.ModelAdmin): + list_display = ('title', 'rank') + ordering = ('rank',) + +@admin.register(Employee) +class EmployeeAdmin(admin.ModelAdmin): + list_display = ('__str__', 'department', 'position', 'email', 'phone', 'hire_date', 'is_active') + list_filter = ('department', 'position', 'is_active', 'hire_date') + search_fields = ('first_name', 'last_name', 'email', 'phone') + ordering = ('department', 'position', 'last_name', 'first_name') diff --git a/hr/apps.py b/hr/apps.py new file mode 100644 index 0000000..20721ae --- /dev/null +++ b/hr/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class HrConfig(AppConfig): + name = "hr" diff --git a/hr/migrations/0001_initial.py b/hr/migrations/0001_initial.py new file mode 100644 index 0000000..50cda8e --- /dev/null +++ b/hr/migrations/0001_initial.py @@ -0,0 +1,125 @@ +# Generated by Django 6.0.2 on 2026-02-16 09:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] + + operations = [ + migrations.CreateModel( + name="Position", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("title", models.CharField(max_length=100, verbose_name="役職名")), + ("rank", models.IntegerField(default=0, verbose_name="ランク")), + ], + options={ + "verbose_name": "役職", + "verbose_name_plural": "役職", + "ordering": ["rank"], + }, + ), + migrations.CreateModel( + name="Department", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=100, verbose_name="部署名")), + ( + "code", + models.CharField( + max_length=20, unique=True, verbose_name="部署コード" + ), + ), + ( + "parent", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="children", + to="hr.department", + verbose_name="親部署", + ), + ), + ], + options={ + "verbose_name": "部署", + "verbose_name_plural": "部署", + }, + ), + migrations.CreateModel( + name="Employee", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("first_name", models.CharField(max_length=50, verbose_name="名")), + ("last_name", models.CharField(max_length=50, verbose_name="姓")), + ( + "email", + models.EmailField( + max_length=254, unique=True, verbose_name="メールアドレス" + ), + ), + ( + "phone", + models.CharField( + blank=True, max_length=20, null=True, verbose_name="電話番号" + ), + ), + ("hire_date", models.DateField(verbose_name="入社日")), + ("is_active", models.BooleanField(default=True, verbose_name="在籍中")), + ( + "department", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="hr.department", + verbose_name="部署", + ), + ), + ( + "position", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="hr.position", + verbose_name="役職", + ), + ), + ], + options={ + "verbose_name": "従業員", + "verbose_name_plural": "従業員", + }, + ), + ] diff --git a/hr/migrations/__init__.py b/hr/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/hr/migrations/__pycache__/0001_initial.cpython-312.pyc b/hr/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..a18504e Binary files /dev/null and b/hr/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/hr/migrations/__pycache__/__init__.cpython-312.pyc b/hr/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..37c7439 Binary files /dev/null and b/hr/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/hr/models.py b/hr/models.py new file mode 100644 index 0000000..4da446e --- /dev/null +++ b/hr/models.py @@ -0,0 +1,42 @@ +from django.db import models + +class Department(models.Model): + name = models.CharField(max_length=100, verbose_name="部署名") + code = models.CharField(max_length=20, unique=True, verbose_name="部署コード") + parent = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True, related_name='children', verbose_name="親部署") + + class Meta: + verbose_name = "部署" + verbose_name_plural = "部署" + + def __str__(self): + return self.name + +class Position(models.Model): + title = models.CharField(max_length=100, verbose_name="役職名") + rank = models.IntegerField(default=0, verbose_name="ランク") + + class Meta: + verbose_name = "役職" + verbose_name_plural = "役職" + ordering = ['rank'] + + def __str__(self): + return self.title + +class Employee(models.Model): + first_name = models.CharField(max_length=50, verbose_name="名") + last_name = models.CharField(max_length=50, verbose_name="姓") + email = models.EmailField(unique=True, verbose_name="メールアドレス") + phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="電話番号") + department = models.ForeignKey(Department, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="部署") + position = models.ForeignKey(Position, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="役職") + hire_date = models.DateField(verbose_name="入社日") + is_active = models.BooleanField(default=True, verbose_name="在籍中") + + class Meta: + verbose_name = "従業員" + verbose_name_plural = "従業員" + + def __str__(self): + return f"{self.last_name} {self.first_name}" diff --git a/hr/tests.py b/hr/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/hr/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/hr/views.py b/hr/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/hr/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/inventory/__init__.py b/inventory/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/inventory/__pycache__/__init__.cpython-312.pyc b/inventory/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..f282fd0 Binary files /dev/null and b/inventory/__pycache__/__init__.cpython-312.pyc differ diff --git a/inventory/__pycache__/admin.cpython-312.pyc b/inventory/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..0e13024 Binary files /dev/null and b/inventory/__pycache__/admin.cpython-312.pyc differ diff --git a/inventory/__pycache__/apps.cpython-312.pyc b/inventory/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..bc86643 Binary files /dev/null and b/inventory/__pycache__/apps.cpython-312.pyc differ diff --git a/inventory/__pycache__/models.cpython-312.pyc b/inventory/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..21af2f9 Binary files /dev/null and b/inventory/__pycache__/models.cpython-312.pyc differ diff --git a/inventory/admin.py b/inventory/admin.py new file mode 100644 index 0000000..21d4357 --- /dev/null +++ b/inventory/admin.py @@ -0,0 +1,15 @@ +from django.contrib import admin +from .models import Stock, StockMovement + +@admin.register(Stock) +class StockAdmin(admin.ModelAdmin): + list_display = ('product', 'quantity', 'location', 'updated_at') + search_fields = ('product__name', 'product__code', 'location') + list_filter = ('updated_at', 'location') + +@admin.register(StockMovement) +class StockMovementAdmin(admin.ModelAdmin): + list_display = ('product', 'movement_type', 'quantity', 'date', 'note') + search_fields = ('product__name', 'product__code') + list_filter = ('movement_type', 'date') + date_hierarchy = 'date' diff --git a/inventory/apps.py b/inventory/apps.py new file mode 100644 index 0000000..a9ebba5 --- /dev/null +++ b/inventory/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class InventoryConfig(AppConfig): + name = "inventory" diff --git a/inventory/migrations/0001_initial.py b/inventory/migrations/0001_initial.py new file mode 100644 index 0000000..72c60db --- /dev/null +++ b/inventory/migrations/0001_initial.py @@ -0,0 +1,100 @@ +# Generated by Django 6.0.2 on 2026-02-16 09:35 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("sales", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Stock", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("quantity", models.IntegerField(default=0, verbose_name="在庫数")), + ( + "location", + models.CharField( + blank=True, max_length=100, null=True, verbose_name="保管場所" + ), + ), + ( + "updated_at", + models.DateTimeField(auto_now=True, verbose_name="更新日時"), + ), + ( + "product", + models.OneToOneField( + on_delete=django.db.models.deletion.CASCADE, + related_name="stock", + to="sales.product", + verbose_name="商品", + ), + ), + ], + options={ + "verbose_name": "在庫", + "verbose_name_plural": "在庫", + }, + ), + migrations.CreateModel( + name="StockMovement", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "movement_type", + models.CharField( + choices=[ + ("in", "入荷 (+)"), + ("out", "出荷 (-)"), + ("return", "返品受入 (+)"), + ("waste", "廃棄 (-)"), + ("adjust_in", "調整増 (+)"), + ("adjust_out", "調整減 (-)"), + ], + max_length=20, + verbose_name="移動タイプ", + ), + ), + ("quantity", models.PositiveIntegerField(verbose_name="数量")), + ("date", models.DateTimeField(auto_now_add=True, verbose_name="日時")), + ("note", models.TextField(blank=True, null=True, verbose_name="備考")), + ( + "product", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="movements", + to="sales.product", + verbose_name="商品", + ), + ), + ], + options={ + "verbose_name": "入出庫履歴", + "verbose_name_plural": "入出庫履歴", + "ordering": ["-date"], + }, + ), + ] diff --git a/inventory/migrations/__init__.py b/inventory/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/inventory/migrations/__pycache__/0001_initial.cpython-312.pyc b/inventory/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..bb29bff Binary files /dev/null and b/inventory/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/inventory/migrations/__pycache__/__init__.cpython-312.pyc b/inventory/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..a38c6f4 Binary files /dev/null and b/inventory/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/inventory/models.py b/inventory/models.py new file mode 100644 index 0000000..f1fdd13 --- /dev/null +++ b/inventory/models.py @@ -0,0 +1,53 @@ +from django.db import models +from sales.models import Product + +class Stock(models.Model): + product = models.OneToOneField(Product, on_delete=models.CASCADE, related_name='stock', verbose_name="商品") + quantity = models.IntegerField(default=0, verbose_name="在庫数") + location = models.CharField(max_length=100, blank=True, null=True, verbose_name="保管場所") + updated_at = models.DateTimeField(auto_now=True, verbose_name="更新日時") + + class Meta: + verbose_name = "在庫" + verbose_name_plural = "在庫" + + def __str__(self): + return f"{self.product.name}: {self.quantity}" + +class StockMovement(models.Model): + MOVEMENT_TYPE_CHOICES = ( + ('in', '入荷 (+)'), + ('out', '出荷 (-)'), + ('return', '返品受入 (+)'), + ('waste', '廃棄 (-)'), + ('adjust_in', '調整増 (+)'), + ('adjust_out', '調整減 (-)'), + ) + + product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='movements', verbose_name="商品") + movement_type = models.CharField(max_length=20, choices=MOVEMENT_TYPE_CHOICES, verbose_name="移動タイプ") + quantity = models.PositiveIntegerField(verbose_name="数量") + date = models.DateTimeField(auto_now_add=True, verbose_name="日時") + note = models.TextField(blank=True, null=True, verbose_name="備考") + + class Meta: + verbose_name = "入出庫履歴" + verbose_name_plural = "入出庫履歴" + ordering = ['-date'] + + def __str__(self): + return f"{self.get_movement_type_display()} - {self.product.name} ({self.quantity})" + + def save(self, *args, **kwargs): + # 新規作成時のみ在庫を更新 + if self.pk is None: + stock, created = Stock.objects.get_or_create(product=self.product) + + if self.movement_type in ['in', 'return', 'adjust_in']: + stock.quantity += self.quantity + elif self.movement_type in ['out', 'waste', 'adjust_out']: + stock.quantity -= self.quantity + + stock.save() + + super().save(*args, **kwargs) diff --git a/inventory/tests.py b/inventory/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/inventory/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/inventory/views.py b/inventory/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/inventory/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..c7b2799 --- /dev/null +++ b/manage.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" + +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "erp_system.settings") + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == "__main__": + main() diff --git a/populate_data.py b/populate_data.py new file mode 100644 index 0000000..d39d6f9 --- /dev/null +++ b/populate_data.py @@ -0,0 +1,94 @@ +import os +import django +from django.utils import timezone +from decimal import Decimal + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'erp_system.settings') +django.setup() + +from core.models import CompanyInfo +from hr.models import Department, Position, Employee +from sales.models import Customer, Product, Order, OrderItem +from inventory.models import StockMovement + +def populate(): + print("Creating Company Info...") + CompanyInfo.objects.get_or_create( + name="株式会社ERPデモ", + address="東京都千代田区1-1-1", + phone="03-1234-5678", + email="info@erp-demo.co.jp", + website="https://www.erp-demo.co.jp" + ) + + print("Creating Departments...") + sales_dept, _ = Department.objects.get_or_create(name="営業部", code="SALES") + dev_dept, _ = Department.objects.get_or_create(name="開発部", code="DEV") + admin_dept, _ = Department.objects.get_or_create(name="総務部", code="ADMIN") + + print("Creating Positions...") + manager_pos, _ = Position.objects.get_or_create(title="部長", rank=10) + leader_pos, _ = Position.objects.get_or_create(title="課長", rank=20) + staff_pos, _ = Position.objects.get_or_create(title="社員", rank=30) + + print("Creating Employees...") + emp1, _ = Employee.objects.get_or_create( + email="yamada@erp-demo.co.jp", + defaults={ + "first_name": "太郎", "last_name": "山田", + "phone": "090-1111-2222", + "department": sales_dept, "position": manager_pos, + "hire_date": timezone.now().date() + } + ) + emp2, _ = Employee.objects.get_or_create( + email="suzuki@erp-demo.co.jp", + defaults={ + "first_name": "次郎", "last_name": "鈴木", + "phone": "090-3333-4444", + "department": dev_dept, "position": staff_pos, + "hire_date": timezone.now().date() + } + ) + + print("Creating Customers...") + cust1, _ = Customer.objects.get_or_create( + code="C001", + defaults={ + "name": "株式会社A", "email": "contact@a-corp.co.jp", + "phone": "03-5555-6666", "address": "東京都港区" + } + ) + cust2, _ = Customer.objects.get_or_create( + code="C002", + defaults={ + "name": "合同会社B", "email": "info@b-llc.co.jp", + "phone": "06-7777-8888", "address": "大阪府大阪市" + } + ) + + print("Creating Products...") + prod1, _ = Product.objects.get_or_create( + code="P001", + defaults={"name": "高性能PC", "price": Decimal("150000"), "description": "ハイスペックなデスクトップPC"} + ) + prod2, _ = Product.objects.get_or_create( + code="P002", + defaults={"name": "オフィスチェア", "price": Decimal("30000"), "description": "長時間座っても疲れない椅子"} + ) + + print("Creating Stock Movements (Initial Inventory)...") + # P001: 10個入荷 + StockMovement.objects.create(product=prod1, movement_type='in', quantity=10, note="初期在庫") + # P002: 20個入荷 + StockMovement.objects.create(product=prod2, movement_type='in', quantity=20, note="初期在庫") + + print("Creating Orders...") + order1 = Order.objects.create(customer=cust1, employee=emp1, status='ordered') + OrderItem.objects.create(order=order1, product=prod1, quantity=2) + OrderItem.objects.create(order=order1, product=prod2, quantity=5) + + print("Data population completed successfully!") + +if __name__ == '__main__': + populate() diff --git a/sales/__init__.py b/sales/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sales/__pycache__/__init__.cpython-312.pyc b/sales/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..a92cf11 Binary files /dev/null and b/sales/__pycache__/__init__.cpython-312.pyc differ diff --git a/sales/__pycache__/admin.cpython-312.pyc b/sales/__pycache__/admin.cpython-312.pyc new file mode 100644 index 0000000..d4db9a4 Binary files /dev/null and b/sales/__pycache__/admin.cpython-312.pyc differ diff --git a/sales/__pycache__/apps.cpython-312.pyc b/sales/__pycache__/apps.cpython-312.pyc new file mode 100644 index 0000000..3f46885 Binary files /dev/null and b/sales/__pycache__/apps.cpython-312.pyc differ diff --git a/sales/__pycache__/models.cpython-312.pyc b/sales/__pycache__/models.cpython-312.pyc new file mode 100644 index 0000000..e573e51 Binary files /dev/null and b/sales/__pycache__/models.cpython-312.pyc differ diff --git a/sales/admin.py b/sales/admin.py new file mode 100644 index 0000000..f31d226 --- /dev/null +++ b/sales/admin.py @@ -0,0 +1,30 @@ +from django.contrib import admin +from .models import Customer, Product, Order, OrderItem + +class OrderItemInline(admin.TabularInline): + model = OrderItem + extra = 1 + +@admin.register(Customer) +class CustomerAdmin(admin.ModelAdmin): + list_display = ('name', 'code', 'phone', 'email') + search_fields = ('name', 'code') + +@admin.register(Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ('name', 'code', 'price') + search_fields = ('name', 'code') + +@admin.register(Order) +class OrderAdmin(admin.ModelAdmin): + list_display = ('customer', 'order_date', 'employee', 'total_amount', 'status') + list_filter = ('status', 'order_date', 'employee') + search_fields = ('customer__name', 'order_date') + inlines = [OrderItemInline] + + def save_model(self, request, obj, form, change): + super().save_model(request, obj, form, change) + # 合計金額の再計算(インラインアイテム変更時などに備えて) + # ただし、OrderItemの保存は親の保存後に行われるため、ここでの計算は少しタイミングが難しい場合がある + # 今回はOrderItemのsaveメソッドで親を更新するようにしているので、ここでは特に何もしなくてよい + pass diff --git a/sales/apps.py b/sales/apps.py new file mode 100644 index 0000000..b715163 --- /dev/null +++ b/sales/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class SalesConfig(AppConfig): + name = "sales" diff --git a/sales/migrations/0001_initial.py b/sales/migrations/0001_initial.py new file mode 100644 index 0000000..07852a9 --- /dev/null +++ b/sales/migrations/0001_initial.py @@ -0,0 +1,206 @@ +# Generated by Django 6.0.2 on 2026-02-16 09:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("hr", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="Customer", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="顧客名")), + ( + "code", + models.CharField( + max_length=50, unique=True, verbose_name="顧客コード" + ), + ), + ( + "email", + models.EmailField( + blank=True, + max_length=254, + null=True, + verbose_name="メールアドレス", + ), + ), + ( + "phone", + models.CharField( + blank=True, max_length=20, null=True, verbose_name="電話番号" + ), + ), + ( + "address", + models.TextField(blank=True, null=True, verbose_name="住所"), + ), + ], + options={ + "verbose_name": "顧客", + "verbose_name_plural": "顧客", + }, + ), + migrations.CreateModel( + name="Product", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255, verbose_name="商品名")), + ( + "code", + models.CharField( + max_length=50, unique=True, verbose_name="商品コード" + ), + ), + ( + "price", + models.DecimalField( + decimal_places=2, max_digits=10, verbose_name="単価" + ), + ), + ( + "description", + models.TextField(blank=True, null=True, verbose_name="説明"), + ), + ], + options={ + "verbose_name": "商品", + "verbose_name_plural": "商品", + }, + ), + migrations.CreateModel( + name="Order", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "order_date", + models.DateField(auto_now_add=True, verbose_name="受注日"), + ), + ( + "total_amount", + models.DecimalField( + decimal_places=2, + default=0, + max_digits=12, + verbose_name="合計金額", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("estimate", "見積"), + ("ordered", "受注"), + ("shipped", "出荷済"), + ("billed", "請求済"), + ("paid", "入金済"), + ("cancelled", "キャンセル"), + ], + default="estimate", + max_length=20, + verbose_name="ステータス", + ), + ), + ( + "customer", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="sales.customer", + verbose_name="顧客", + ), + ), + ( + "employee", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="hr.employee", + verbose_name="担当者", + ), + ), + ], + options={ + "verbose_name": "受注", + "verbose_name_plural": "受注", + }, + ), + migrations.CreateModel( + name="OrderItem", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "quantity", + models.PositiveIntegerField(default=1, verbose_name="数量"), + ), + ( + "unit_price", + models.DecimalField( + decimal_places=2, max_digits=10, verbose_name="単価" + ), + ), + ( + "order", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="items", + to="sales.order", + verbose_name="受注", + ), + ), + ( + "product", + models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="sales.product", + verbose_name="商品", + ), + ), + ], + options={ + "verbose_name": "受注明細", + "verbose_name_plural": "受注明細", + }, + ), + ] diff --git a/sales/migrations/__init__.py b/sales/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/sales/migrations/__pycache__/0001_initial.cpython-312.pyc b/sales/migrations/__pycache__/0001_initial.cpython-312.pyc new file mode 100644 index 0000000..44d3856 Binary files /dev/null and b/sales/migrations/__pycache__/0001_initial.cpython-312.pyc differ diff --git a/sales/migrations/__pycache__/__init__.cpython-312.pyc b/sales/migrations/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 0000000..1c23e04 Binary files /dev/null and b/sales/migrations/__pycache__/__init__.cpython-312.pyc differ diff --git a/sales/models.py b/sales/models.py new file mode 100644 index 0000000..9cca7d3 --- /dev/null +++ b/sales/models.py @@ -0,0 +1,83 @@ +from django.db import models +from hr.models import Employee + +class Customer(models.Model): + name = models.CharField(max_length=255, verbose_name="顧客名") + code = models.CharField(max_length=50, unique=True, verbose_name="顧客コード") + email = models.EmailField(blank=True, null=True, verbose_name="メールアドレス") + phone = models.CharField(max_length=20, blank=True, null=True, verbose_name="電話番号") + address = models.TextField(blank=True, null=True, verbose_name="住所") + + class Meta: + verbose_name = "顧客" + verbose_name_plural = "顧客" + + def __str__(self): + return self.name + +class Product(models.Model): + name = models.CharField(max_length=255, verbose_name="商品名") + code = models.CharField(max_length=50, unique=True, verbose_name="商品コード") + price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="単価") + description = models.TextField(blank=True, null=True, verbose_name="説明") + + class Meta: + verbose_name = "商品" + verbose_name_plural = "商品" + + def __str__(self): + return self.name + +class Order(models.Model): + STATUS_CHOICES = ( + ('estimate', '見積'), + ('ordered', '受注'), + ('shipped', '出荷済'), + ('billed', '請求済'), + ('paid', '入金済'), + ('cancelled', 'キャンセル'), + ) + + customer = models.ForeignKey(Customer, on_delete=models.CASCADE, verbose_name="顧客") + employee = models.ForeignKey(Employee, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="担当者") + order_date = models.DateField(auto_now_add=True, verbose_name="受注日") + total_amount = models.DecimalField(max_digits=12, decimal_places=2, default=0, verbose_name="合計金額") + status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='estimate', verbose_name="ステータス") + + class Meta: + verbose_name = "受注" + verbose_name_plural = "受注" + + def __str__(self): + return f"{self.customer} - {self.order_date}" + + def calculate_total(self): + total = sum(item.subtotal for item in self.items.all()) + self.total_amount = total + self.save() + +class OrderItem(models.Model): + order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE, verbose_name="受注") + product = models.ForeignKey(Product, on_delete=models.SET_NULL, null=True, verbose_name="商品") + quantity = models.PositiveIntegerField(default=1, verbose_name="数量") + unit_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="単価") + + @property + def subtotal(self): + return self.quantity * self.unit_price + + class Meta: + verbose_name = "受注明細" + verbose_name_plural = "受注明細" + + def save(self, *args, **kwargs): + if not self.unit_price and self.product: + self.unit_price = self.product.price + super().save(*args, **kwargs) + # 親の合計金額を更新 + self.order.calculate_total() + + def delete(self, *args, **kwargs): + order = self.order + super().delete(*args, **kwargs) + order.calculate_total() diff --git a/sales/tests.py b/sales/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/sales/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/sales/views.py b/sales/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/sales/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/server.log b/server.log new file mode 100644 index 0000000..2443752 --- /dev/null +++ b/server.log @@ -0,0 +1,172 @@ +Watching for file changes with StatReloader +[16/Feb/2026 09:29:57] "HEAD / HTTP/1.1" 200 0 +/app/erp_system/settings.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 09:29:49 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/core/models.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:30:36 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/core/admin.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:30:44 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/erp_system/settings.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:30:49 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/hr/models.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:31:58 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/hr/admin.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:32:07 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/erp_system/settings.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:32:14 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/sales/models.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:33:07 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/sales/admin.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:33:20 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/erp_system/settings.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:33:29 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/inventory/models.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:34:29 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/inventory/models.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:34:42 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/inventory/admin.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:34:53 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +/app/erp_system/urls.py changed, reloading. +Performing system checks... + +System check identified no issues (0 silenced). +February 16, 2026 - 18:34:59 +Django version 6.0.2, using settings 'erp_system.settings' +Starting development server at http://127.0.0.1:8000/ +Quit the server with CONTROL-C. + +WARNING: This is a development server. Do not use it in a production setting. Use a production WSGI or ASGI server instead. +For more information on production servers see: https://docs.djangoproject.com/en/6.0/howto/deployment/ +Watching for file changes with StatReloader +[16/Feb/2026 18:38:05] "GET / HTTP/1.1" 200 4218 +[16/Feb/2026 18:40:09] "GET / HTTP/1.1" 200 4218 diff --git a/test_hello_0718.py b/test_hello_0718.py index bf1f44a..71fe400 100644 --- a/test_hello_0718.py +++ b/test_hello_0718.py @@ -1,16 +1,18 @@ import unittest from hello_0718 import hello_0718 + class TestHello0718(unittest.TestCase): def test_hello_0718(self): result = hello_0718() self.assertEqual(result, "hello 0718") - + def test_hello_0718_output(self): # Test that the function returns the expected string expected = "hello 0718" actual = hello_0718() self.assertEqual(actual, expected) + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main()