在庫管理機能 Phase 1 実装(apps/materials + フロントエンド)

Backend:
- apps/materials 新規作成(Material, FertilizerProfile, PesticideProfile, StockTransaction)
- 資材マスタ CRUD API(/api/materials/materials/)
- 入出庫履歴 API(/api/materials/stock-transactions/)
- 在庫集計 API(/api/materials/stock-summary/)
- 既存 Fertilizer に material OneToOneField 追加(0005マイグレーション、データ移行込み)

Frontend:
- /materials: 在庫一覧画面(タブフィルタ、履歴展開、入出庫モーダル)
- /materials/masters: 資材マスタ管理(肥料/農薬/その他タブ、インライン編集)
- Navbar に「在庫管理」メニュー追加
- Material/StockTransaction/StockSummary 型定義追加

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Akira
2026-03-14 15:42:47 +09:00
parent 67d4197b7f
commit 497bc87c24
20 changed files with 2344 additions and 1 deletions

View File

@@ -0,0 +1,87 @@
import decimal
import django.core.validators
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name='Material',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, verbose_name='資材名')),
('material_type', models.CharField(choices=[('fertilizer', '肥料'), ('pesticide', '農薬'), ('seedling', '種苗'), ('other', 'その他')], max_length=20, verbose_name='資材種別')),
('maker', models.CharField(blank=True, default='', max_length=100, verbose_name='メーカー')),
('stock_unit', models.CharField(choices=[('bag', ''), ('bottle', ''), ('kg', 'kg'), ('liter', 'L'), ('piece', '')], default='bag', max_length=20, verbose_name='在庫単位')),
('is_active', models.BooleanField(default=True, verbose_name='使用中')),
('notes', models.TextField(blank=True, default='', verbose_name='備考')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': '資材',
'verbose_name_plural': '資材',
'ordering': ['material_type', 'name'],
},
),
migrations.CreateModel(
name='FertilizerProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('capacity_kg', models.DecimalField(blank=True, decimal_places=3, max_digits=8, null=True, verbose_name='1袋重量(kg)')),
('nitrogen_pct', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='窒素(%)')),
('phosphorus_pct', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='リン酸(%)')),
('potassium_pct', models.DecimalField(blank=True, decimal_places=2, max_digits=5, null=True, verbose_name='カリ(%)')),
('material', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='fertilizer_profile', to='materials.material')),
],
options={
'verbose_name': '肥料詳細',
'verbose_name_plural': '肥料詳細',
},
),
migrations.CreateModel(
name='PesticideProfile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('registration_no', models.CharField(blank=True, default='', max_length=100, verbose_name='農薬登録番号')),
('formulation', models.CharField(blank=True, default='', max_length=100, verbose_name='剤型')),
('usage_unit', models.CharField(blank=True, default='', max_length=50, verbose_name='使用単位')),
('dilution_ratio', models.CharField(blank=True, default='', max_length=100, verbose_name='希釈倍率')),
('active_ingredient', models.CharField(blank=True, default='', max_length=200, verbose_name='有効成分')),
('category', models.CharField(blank=True, default='', max_length=100, verbose_name='分類')),
('material', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='pesticide_profile', to='materials.material')),
],
options={
'verbose_name': '農薬詳細',
'verbose_name_plural': '農薬詳細',
},
),
migrations.CreateModel(
name='StockTransaction',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('transaction_type', models.CharField(choices=[('purchase', '入庫'), ('use', '使用'), ('adjustment_plus', '棚卸増'), ('adjustment_minus', '棚卸減'), ('discard', '廃棄')], max_length=30, verbose_name='取引種別')),
('quantity', models.DecimalField(decimal_places=3, max_digits=10, validators=[django.core.validators.MinValueValidator(decimal.Decimal('0.001'))], verbose_name='数量')),
('occurred_on', models.DateField(verbose_name='発生日')),
('note', models.TextField(blank=True, default='', verbose_name='備考')),
('created_at', models.DateTimeField(auto_now_add=True)),
('material', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='stock_transactions', to='materials.material', verbose_name='資材')),
],
options={
'verbose_name': '入出庫履歴',
'verbose_name_plural': '入出庫履歴',
'ordering': ['-occurred_on', '-created_at', '-id'],
},
),
migrations.AddConstraint(
model_name='material',
constraint=models.UniqueConstraint(fields=('material_type', 'name'), name='uniq_material_type_name'),
),
]

View File

@@ -0,0 +1 @@