施肥計画の計算設定を保存・復元し、未入力圃場のみ計算オプションを追加
- 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,
|
||||
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)
|
||||
|
||||
|
||||
@@ -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', [])
|
||||
|
||||
@@ -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,6 +499,16 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
||||
<div className="bg-white rounded-lg shadow p-4 mb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<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
|
||||
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"
|
||||
@@ -488,6 +516,7 @@ export default function FertilizerEditPage({ planId }: { planId?: number }) {
|
||||
<Plus className="h-3 w-3" />肥料を追加
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{planFertilizers.length === 0 ? (
|
||||
<p className="text-sm text-gray-400">肥料を追加してください</p>
|
||||
) : (
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user