種子資材ベースへの切り替えを反映しました。
frontend/src/app/rice-transplant/_components/RiceTransplantEditPage.tsx では、田植え計画の主選択を 品種 から 種子資材 に変更し、画面上は種子資材名だけで進めるようにしました。保存時だけ内部的に、その種子資材に紐づく Variety を解決して既存 API に送っています。候補圃場取得もその内部参照で動く形です。frontend/src/app/rice-transplant/page.tsx の一覧表示も 作物 / 品種 ではなく 種子資材 列に変更しました。 仕様書 document/16_マスタードキュメント_田植え計画編.md も 年度 × 種子資材 を軸にした説明へ更新済みです。確認できたのは Python 側の py_compile までで、フロントのビルド確認はまだしていません。Issue #2 にも今回の変更内容をコメント済みです。
This commit is contained in:
@@ -18,7 +18,7 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
|
||||
const [name, setName] = useState('');
|
||||
const [year, setYear] = useState(currentYear);
|
||||
const [varietyId, setVarietyId] = useState<number | ''>('');
|
||||
const [seedMaterialId, setSeedMaterialId] = useState<number | ''>('');
|
||||
const [seedlingBoxesPerTan, setSeedlingBoxesPerTan] = useState('');
|
||||
const [defaultSeedGramsPerBox, setDefaultSeedGramsPerBox] = useState('200');
|
||||
const [notes, setNotes] = useState('');
|
||||
@@ -39,8 +39,9 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
|
||||
const years = Array.from({ length: 5 }, (_, i) => currentYear + 1 - i);
|
||||
|
||||
const getVariety = (id: number) =>
|
||||
crops.flatMap((crop) => crop.varieties).find((variety) => variety.id === id);
|
||||
const allVarieties = crops.flatMap((crop) => crop.varieties);
|
||||
const getVarietyBySeedMaterial = (id: number) =>
|
||||
allVarieties.find((variety) => variety.seed_material === id) ?? null;
|
||||
|
||||
const calculateDefaultBoxes = (field: Field, perTan: string) => {
|
||||
const areaTan = parseFloat(field.area_tan || '0');
|
||||
@@ -64,9 +65,12 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
if (!isNew && planId) {
|
||||
const planRes = await api.get(`/plans/rice-transplant-plans/${planId}/`);
|
||||
const plan: RiceTransplantPlan = planRes.data;
|
||||
const fetchedVarieties = cropsRes.data.flatMap((crop: Crop) => crop.varieties);
|
||||
const linkedVariety =
|
||||
fetchedVarieties.find((variety) => variety.id === plan.variety) ?? null;
|
||||
setName(plan.name);
|
||||
setYear(plan.year);
|
||||
setVarietyId(plan.variety);
|
||||
setSeedMaterialId(linkedVariety?.seed_material ?? '');
|
||||
setSeedlingBoxesPerTan(plan.seedling_boxes_per_tan);
|
||||
setDefaultSeedGramsPerBox(plan.default_seed_grams_per_box);
|
||||
setNotes(plan.notes);
|
||||
@@ -97,10 +101,11 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCandidates = async () => {
|
||||
if (!varietyId || !year || (!isNew && loading)) return;
|
||||
const selectedVariety = seedMaterialId ? getVarietyBySeedMaterial(seedMaterialId) : null;
|
||||
if (!selectedVariety || !year || (!isNew && loading)) return;
|
||||
try {
|
||||
const res = await api.get(
|
||||
`/plans/rice-transplant-plans/candidate_fields/?year=${year}&variety_id=${varietyId}`
|
||||
`/plans/rice-transplant-plans/candidate_fields/?year=${year}&variety_id=${selectedVariety.id}`
|
||||
);
|
||||
const nextCandidates: Field[] = res.data;
|
||||
setCandidateFields(nextCandidates);
|
||||
@@ -113,16 +118,16 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
}
|
||||
};
|
||||
fetchCandidates();
|
||||
}, [varietyId, year, isNew, loading]);
|
||||
}, [seedMaterialId, year, isNew, loading]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!varietyId) return;
|
||||
const variety = getVariety(varietyId);
|
||||
if (!seedMaterialId) return;
|
||||
const variety = getVarietyBySeedMaterial(seedMaterialId);
|
||||
if (!variety) return;
|
||||
if (isNew || seedlingBoxesPerTan === '') {
|
||||
setSeedlingBoxesPerTan(variety.default_seedling_boxes_per_tan);
|
||||
}
|
||||
}, [varietyId, crops, isNew, seedlingBoxesPerTan]);
|
||||
}, [seedMaterialId, crops, isNew, seedlingBoxesPerTan]);
|
||||
|
||||
useEffect(() => {
|
||||
const nextCalc: BoxMap = {};
|
||||
@@ -206,15 +211,15 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
return Number.isNaN(value) ? 0 : value;
|
||||
};
|
||||
|
||||
const selectedVariety = varietyId ? getVariety(varietyId) : null;
|
||||
const seedStock = selectedVariety?.seed_material
|
||||
? seedStocks.find((item) => item.material_id === selectedVariety.seed_material) ?? null
|
||||
const selectedSeedStock = seedMaterialId
|
||||
? seedStocks.find((item) => item.material_id === seedMaterialId) ?? null
|
||||
: null;
|
||||
const selectedVariety = seedMaterialId ? getVarietyBySeedMaterial(seedMaterialId) : null;
|
||||
|
||||
const totalBoxes = selectedFields.reduce((sum, field) => sum + effectiveBoxes(field.id), 0);
|
||||
const seedGrams = parseFloat(defaultSeedGramsPerBox || '0');
|
||||
const totalSeedKg = seedGrams > 0 ? (totalBoxes * seedGrams) / 1000 : 0;
|
||||
const seedInventoryKg = parseFloat(seedStock?.current_stock ?? '0');
|
||||
const seedInventoryKg = parseFloat(selectedSeedStock?.current_stock ?? '0');
|
||||
const remainingSeedKg = seedInventoryKg - totalSeedKg;
|
||||
|
||||
const handleSave = async () => {
|
||||
@@ -223,8 +228,12 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
setError('計画名を入力してください。');
|
||||
return;
|
||||
}
|
||||
if (!varietyId) {
|
||||
setError('品種を選択してください。');
|
||||
if (!seedMaterialId) {
|
||||
setError('種子資材を選択してください。');
|
||||
return;
|
||||
}
|
||||
if (!selectedVariety) {
|
||||
setError('選択した種子資材に対応する品種が未設定です。資材マスタで紐付けてください。');
|
||||
return;
|
||||
}
|
||||
if (selectedFields.length === 0) {
|
||||
@@ -240,7 +249,7 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
const payload = {
|
||||
name,
|
||||
year,
|
||||
variety: varietyId,
|
||||
variety: selectedVariety.id,
|
||||
seedling_boxes_per_tan: seedlingBoxesPerTan || '0',
|
||||
default_seed_grams_per_box: defaultSeedGramsPerBox || '0',
|
||||
notes,
|
||||
@@ -349,21 +358,19 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-600">品種</label>
|
||||
<label className="mb-1 block text-xs font-medium text-gray-600">種子資材</label>
|
||||
<select
|
||||
value={varietyId}
|
||||
onChange={(e) => setVarietyId(e.target.value ? parseInt(e.target.value, 10) : '')}
|
||||
value={seedMaterialId}
|
||||
onChange={(e) =>
|
||||
setSeedMaterialId(e.target.value ? parseInt(e.target.value, 10) : '')
|
||||
}
|
||||
className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-green-500"
|
||||
>
|
||||
<option value="">選択してください</option>
|
||||
{crops.map((crop) => (
|
||||
<optgroup key={crop.id} label={crop.name}>
|
||||
{crop.varieties.map((variety) => (
|
||||
<option key={variety.id} value={variety.id}>
|
||||
{variety.name}
|
||||
</option>
|
||||
))}
|
||||
</optgroup>
|
||||
{seedStocks.map((stock) => (
|
||||
<option key={stock.material_id} value={stock.material_id}>
|
||||
{stock.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
@@ -445,7 +452,7 @@ export default function RiceTransplantEditPage({ planId }: { planId?: number })
|
||||
<span>{totalSeedKg.toFixed(3)}kg</span>
|
||||
</div>
|
||||
<div className="flex justify-between text-gray-600">
|
||||
<span>{seedStock?.name || selectedVariety?.seed_material_name || '種子在庫未設定'}</span>
|
||||
<span>{selectedSeedStock?.name || '種子在庫未設定'}</span>
|
||||
<span>{seedInventoryKg.toFixed(3)}kg</span>
|
||||
</div>
|
||||
<div
|
||||
|
||||
@@ -110,7 +110,7 @@ export default function RiceTransplantPage() {
|
||||
<thead className="border-b bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">計画名</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">作物 / 品種</th>
|
||||
<th className="px-4 py-3 text-left font-medium text-gray-700">種子資材</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-gray-700">圃場</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-gray-700">苗箱合計</th>
|
||||
<th className="px-4 py-3 text-right font-medium text-gray-700">種もみ計画</th>
|
||||
@@ -122,7 +122,9 @@ export default function RiceTransplantPage() {
|
||||
{plans.map((plan) => (
|
||||
<tr key={plan.id} className="hover:bg-gray-50">
|
||||
<td className="px-4 py-3 font-medium text-gray-900">{plan.name}</td>
|
||||
<td className="px-4 py-3 text-gray-600">{plan.crop_name} / {plan.variety_name}</td>
|
||||
<td className="px-4 py-3 text-gray-600">
|
||||
{plan.seed_material_name || '-'}
|
||||
</td>
|
||||
<td className="px-4 py-3 text-right text-gray-600">{plan.field_count}筆</td>
|
||||
<td className="px-4 py-3 text-right tabular-nums text-gray-600">{plan.total_seedling_boxes}</td>
|
||||
<td className="px-4 py-3 text-right tabular-nums text-gray-600">{plan.total_seed_kg}kg</td>
|
||||
|
||||
Reference in New Issue
Block a user