施肥計画の計算設定を保存・復元し、未入力圃場のみ計算オプションを追加
- FertilizationPlanにcalc_settings JSONFieldを追加(migration 0004) - 編集画面を開くと前回の計算方式・パラメータが復元される - 「未入力圃場のみ」チェックで既存値を保持したまま新規圃場だけ計算可能 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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='計算設定'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -34,6 +34,7 @@ class FertilizationPlan(models.Model):
|
|||||||
'plans.Variety', on_delete=models.PROTECT,
|
'plans.Variety', on_delete=models.PROTECT,
|
||||||
related_name='fertilization_plans', verbose_name='品種'
|
related_name='fertilization_plans', verbose_name='品種'
|
||||||
)
|
)
|
||||||
|
calc_settings = models.JSONField(default=list, blank=True, verbose_name='計算設定')
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class FertilizationPlanSerializer(serializers.ModelSerializer):
|
|||||||
model = FertilizationPlan
|
model = FertilizationPlan
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'name', 'year', 'variety', 'variety_name', 'crop_name',
|
'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):
|
def get_variety_name(self, obj):
|
||||||
@@ -53,7 +53,7 @@ class FertilizationPlanWriteSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FertilizationPlan
|
model = FertilizationPlan
|
||||||
fields = ['id', 'name', 'year', 'variety', 'entries']
|
fields = ['id', 'name', 'year', 'variety', 'calc_settings', 'entries']
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
entries_data = validated_data.pop('entries', [])
|
entries_data = validated_data.pop('entries', [])
|
||||||
|
|||||||
@@ -91,7 +91,13 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
|||||||
const fertIds = Array.from(new Set(plan.entries.map((e) => e.fertilizer)));
|
const fertIds = Array.from(new Set(plan.entries.map((e) => e.fertilizer)));
|
||||||
const ferts = fertsRes.data.filter((f: Fertilizer) => fertIds.includes(f.id));
|
const ferts = fertsRes.data.filter((f: Fertilizer) => fertIds.includes(f.id));
|
||||||
setPlanFertilizers(ferts);
|
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 fieldIds = Array.from(new Set(plan.entries.map((e) => e.field)));
|
||||||
const fields = fieldsRes.data.filter((f: Field) => fieldIds.includes(f.id));
|
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; });
|
setAdjusted((prev) => { const next = { ...prev }; delete next[id]; return next; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [calcNewOnly, setCalcNewOnly] = useState(true);
|
||||||
|
|
||||||
// ─── 自動計算
|
// ─── 自動計算
|
||||||
const runCalc = async (setting: CalcSetting) => {
|
const runCalc = async (setting: CalcSetting) => {
|
||||||
if (!setting.param) return alert('パラメータを入力してください');
|
if (!setting.param) return alert('パラメータを入力してください');
|
||||||
if (selectedFields.length === 0) 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 {
|
try {
|
||||||
const res = await api.post('/fertilizer/calculate/', {
|
const res = await api.post('/fertilizer/calculate/', {
|
||||||
method: setting.method,
|
method: setting.method,
|
||||||
param: parseFloat(setting.param),
|
param: parseFloat(setting.param),
|
||||||
fertilizer_id: setting.fertilizer_id,
|
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;
|
const results: { field_id: number; bags: number }[] = res.data;
|
||||||
|
|
||||||
@@ -311,7 +329,7 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
|||||||
|
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
try {
|
try {
|
||||||
const payload = { name, year, variety: varietyId, entries };
|
const payload = { name, year, variety: varietyId, calc_settings: calcSettings, entries };
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
await api.post('/fertilizer/plans/', payload);
|
await api.post('/fertilizer/plans/', payload);
|
||||||
} else {
|
} else {
|
||||||
@@ -481,6 +499,16 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
|||||||
<div className="bg-white rounded-lg shadow p-4 mb-4">
|
<div className="bg-white rounded-lg shadow p-4 mb-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h2 className="text-sm font-semibold text-gray-700">自動計算設定</h2>
|
<h2 className="text-sm font-semibold text-gray-700">自動計算設定</h2>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<label className="flex items-center gap-1.5 text-xs text-gray-600 cursor-pointer select-none">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={calcNewOnly}
|
||||||
|
onChange={(e) => setCalcNewOnly(e.target.checked)}
|
||||||
|
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||||
|
/>
|
||||||
|
未入力圃場のみ
|
||||||
|
</label>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowFertPicker(true)}
|
onClick={() => setShowFertPicker(true)}
|
||||||
className="flex items-center gap-1 text-xs text-green-600 hover:text-green-800 border border-green-300 rounded px-2 py-1"
|
className="flex items-center gap-1 text-xs text-green-600 hover:text-green-800 border border-green-300 rounded px-2 py-1"
|
||||||
@@ -488,6 +516,7 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
|||||||
<Plus className="h-3 w-3" />肥料を追加
|
<Plus className="h-3 w-3" />肥料を追加
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{planFertilizers.length === 0 ? (
|
{planFertilizers.length === 0 ? (
|
||||||
<p className="text-sm text-gray-400">肥料を追加してください</p>
|
<p className="text-sm text-gray-400">肥料を追加してください</p>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export interface FertilizationPlan {
|
|||||||
variety: number;
|
variety: number;
|
||||||
variety_name: string;
|
variety_name: string;
|
||||||
crop_name: string;
|
crop_name: string;
|
||||||
|
calc_settings: { fertilizer_id: number; method: string; param: string }[];
|
||||||
entries: FertilizationEntry[];
|
entries: FertilizationEntry[];
|
||||||
field_count: number;
|
field_count: number;
|
||||||
fertilizer_count: number;
|
fertilizer_count: number;
|
||||||
|
|||||||
Reference in New Issue
Block a user