diff --git a/backend/apps/fertilizer/migrations/0004_fertilizationplan_calc_settings.py b/backend/apps/fertilizer/migrations/0004_fertilizationplan_calc_settings.py new file mode 100644 index 0000000..ba475e4 --- /dev/null +++ b/backend/apps/fertilizer/migrations/0004_fertilizationplan_calc_settings.py @@ -0,0 +1,16 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('fertilizer', '0003_distributionplan_distributiongroup_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='fertilizationplan', + name='calc_settings', + field=models.JSONField(blank=True, default=list, verbose_name='計算設定'), + ), + ] diff --git a/backend/apps/fertilizer/models.py b/backend/apps/fertilizer/models.py index b50da4c..e758521 100644 --- a/backend/apps/fertilizer/models.py +++ b/backend/apps/fertilizer/models.py @@ -34,6 +34,7 @@ class FertilizationPlan(models.Model): 'plans.Variety', on_delete=models.PROTECT, related_name='fertilization_plans', verbose_name='品種' ) + calc_settings = models.JSONField(default=list, blank=True, verbose_name='計算設定') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) diff --git a/backend/apps/fertilizer/serializers.py b/backend/apps/fertilizer/serializers.py index 7308b07..02e4191 100644 --- a/backend/apps/fertilizer/serializers.py +++ b/backend/apps/fertilizer/serializers.py @@ -31,7 +31,7 @@ class FertilizationPlanSerializer(serializers.ModelSerializer): model = FertilizationPlan fields = [ 'id', 'name', 'year', 'variety', 'variety_name', 'crop_name', - 'entries', 'field_count', 'fertilizer_count', 'created_at', 'updated_at' + 'calc_settings', 'entries', 'field_count', 'fertilizer_count', 'created_at', 'updated_at' ] def get_variety_name(self, obj): @@ -53,7 +53,7 @@ class FertilizationPlanWriteSerializer(serializers.ModelSerializer): class Meta: model = FertilizationPlan - fields = ['id', 'name', 'year', 'variety', 'entries'] + fields = ['id', 'name', 'year', 'variety', 'calc_settings', 'entries'] def create(self, validated_data): entries_data = validated_data.pop('entries', []) diff --git a/frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx b/frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx index 1701ef9..0b2d029 100644 --- a/frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx +++ b/frontend/src/app/fertilizer/_components/FertilizerEditPage.tsx @@ -91,7 +91,13 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) { const fertIds = Array.from(new Set(plan.entries.map((e) => e.fertilizer))); const ferts = fertsRes.data.filter((f: Fertilizer) => fertIds.includes(f.id)); setPlanFertilizers(ferts); - setCalcSettings(ferts.map((f: Fertilizer) => ({ fertilizer_id: f.id, method: 'per_tan' as CalcMethod, param: '' }))); + // 保存済み計算設定を復元、なければデフォルト値 + setCalcSettings(ferts.map((f: Fertilizer) => { + const saved = plan.calc_settings?.find((s) => s.fertilizer_id === f.id); + return saved + ? { fertilizer_id: f.id, method: saved.method as CalcMethod, param: saved.param } + : { fertilizer_id: f.id, method: 'per_tan' as CalcMethod, param: '' }; + })); const fieldIds = Array.from(new Set(plan.entries.map((e) => e.field))); const fields = fieldsRes.data.filter((f: Field) => fieldIds.includes(f.id)); @@ -172,17 +178,29 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) { setAdjusted((prev) => { const next = { ...prev }; delete next[id]; return next; }); }; + const [calcNewOnly, setCalcNewOnly] = useState(true); + // ─── 自動計算 const runCalc = async (setting: CalcSetting) => { if (!setting.param) return alert('パラメータを入力してください'); if (selectedFields.length === 0) return alert('対象圃場を選択してください'); + const targetFields = calcNewOnly + ? selectedFields.filter((f) => { + const hasAdjusted = adjusted[f.id]?.[setting.fertilizer_id] !== undefined; + const hasCalc = calcMatrix[f.id]?.[setting.fertilizer_id] !== undefined; + return !hasAdjusted && !hasCalc; + }) + : selectedFields; + + if (targetFields.length === 0) return alert('未入力の圃場がありません。「全圃場」で実行してください。'); + try { const res = await api.post('/fertilizer/calculate/', { method: setting.method, param: parseFloat(setting.param), fertilizer_id: setting.fertilizer_id, - field_ids: selectedFields.map((f) => f.id), + field_ids: targetFields.map((f) => f.id), }); const results: { field_id: number; bags: number }[] = res.data; @@ -311,7 +329,7 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) { setSaving(true); try { - const payload = { name, year, variety: varietyId, entries }; + const payload = { name, year, variety: varietyId, calc_settings: calcSettings, entries }; if (isNew) { await api.post('/fertilizer/plans/', payload); } else { @@ -481,12 +499,23 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {

自動計算設定

- +
+ + +
{planFertilizers.length === 0 ? (

肥料を追加してください

diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index b722e9f..82229ba 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -84,6 +84,7 @@ export interface FertilizationPlan { variety: number; variety_name: string; crop_name: string; + calc_settings: { fertilizer_id: number; method: string; param: string }[]; entries: FertilizationEntry[]; field_count: number; fertilizer_count: number;