Compare commits
13 Commits
35eae7b3ec
...
e6b307b0dc
| Author | SHA1 | Date | |
|---|---|---|---|
| e6b307b0dc | |||
| b8e0301175 | |||
| e2da441a6d | |||
| dca6cc5dd4 | |||
| 5c99ff7475 | |||
| 0283c250e4 | |||
| 5bd52df240 | |||
| 5ad403bf86 | |||
| ce3844957f | |||
| 6c0f655d0a | |||
| 29b820b846 | |||
| 34311889e8 | |||
| ba60ed541c |
@@ -12,7 +12,7 @@ server:
|
||||
|
||||
# 日志配置
|
||||
log:
|
||||
level: "debug" # 日志级别: "debug", "info", "warn", "error", "dpanic", "panic", "fatal"
|
||||
level: "info" # 日志级别: "debug", "info", "warn", "error", "dpanic", "panic", "fatal"
|
||||
format: "console" # 日志格式: "console" 或 "json"
|
||||
enable_file: true # 是否启用文件日志
|
||||
file_path: "./app_logs/app.log" # 日志文件路径
|
||||
|
||||
@@ -1741,327 +1741,408 @@
|
||||
"raw_materials": {
|
||||
"DL-蛋氨酸98": {
|
||||
"descriptions": "饲料级合成蛋氨酸,几乎100%可利用,是猪限制性氨基酸补充的首选来源,可显著提高生长速度和饲料转化率。",
|
||||
"unit_price": 21.50
|
||||
"unit_price": 21.50,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"L-色氨酸98": {
|
||||
"descriptions": "饲料级合成色氨酸,猪的第四限制性氨基酸,缺乏时严重影响采食量和生长,补充可提升猪只食欲和免疫力。",
|
||||
"unit_price": 68.00
|
||||
"unit_price": 68.00,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"L-苏氨酸98": {
|
||||
"descriptions": "饲料级合成苏氨酸,猪的第三限制性氨基酸,主要影响蛋白沉积和免疫器官发育,仔猪阶段尤为重要。",
|
||||
"unit_price": 10.80
|
||||
"unit_price": 10.80,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"L-赖氨酸HCl 98": {
|
||||
"descriptions": "饲料级赖氨酸盐酸盐,猪的第一限制性氨基酸,低蛋白日粮配方核心,降低氮排放的同时维持生长性能。",
|
||||
"unit_price": 11.20
|
||||
"unit_price": 11.20,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"乳清粉": {
|
||||
"descriptions": "仔猪最优质的乳源蛋白和乳糖来源,提高采食量、促进肠道发育、缓解断奶应激,是教槽料和保育料黄金原料。",
|
||||
"unit_price": 6.50
|
||||
"unit_price": 6.50,
|
||||
"max_ratio": 40.00
|
||||
},
|
||||
"兔肉粉": {
|
||||
"descriptions": "高蛋白高消化率动物蛋白源,氨基酸平衡好,适口性佳,适合高档仔猪料和母猪料使用。",
|
||||
"unit_price": 11.50
|
||||
"unit_price": 11.50,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"全株玉米青贮": {
|
||||
"descriptions": "粗饲料来源,提供有效纤维,调节成年母猪肠道健康,降低便秘,价格低廉。",
|
||||
"unit_price": 0.45
|
||||
"unit_price": 0.45,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"双低菜籽粕": {
|
||||
"descriptions": "双低菜粕,硫甙和异硫氰酸酯含量低,可部分替代豆粕使用,但仍需注意赖氨酸利用率和甲状腺影响。",
|
||||
"unit_price": 2.40
|
||||
"unit_price": 2.40,
|
||||
"max_ratio": 35.00
|
||||
},
|
||||
"向日葵籽": {
|
||||
"descriptions": "高油分能量原料,富含亚油酸,但纤维高,猪的利用率一般,多用于母猪料。",
|
||||
"unit_price": 5.80
|
||||
"unit_price": 5.80,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"啤酒糟干": {
|
||||
"descriptions": "高蛋白高纤维副产品,适口性好,可用于生长肥育猪和母猪料,注意霉菌毒素风险。",
|
||||
"unit_price": 1.90
|
||||
"unit_price": 1.90,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"啤酒花渣": {
|
||||
"descriptions": "啤酒副产物,湿态使用时适口性好,可降低母猪便秘,但干物质低、易发霉。",
|
||||
"unit_price": 0.60
|
||||
"unit_price": 0.60,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"国产鱼粉60": {
|
||||
"descriptions": "中等品质鱼粉,蛋白高但新鲜度一般,挥发性盐基氮和组胺需关注,仔猪料谨慎使用。",
|
||||
"unit_price": 9.50
|
||||
"unit_price": 9.50,
|
||||
"max_ratio": 15.00
|
||||
},
|
||||
"土豆蛋白": {
|
||||
"descriptions": "高消化率植物浓缩蛋白,氨基酸平衡好,是优质替代血浆和鱼粉的原料之一。",
|
||||
"unit_price": 8.50
|
||||
"unit_price": 8.50,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"大豆油": {
|
||||
"descriptions": "高能量油脂,猪利用率极高,用于提高日粮能量浓度,改善皮毛光亮度。",
|
||||
"unit_price": 8.20
|
||||
"unit_price": 8.20,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"大豆粕44": {
|
||||
"descriptions": "普通豆粕,蛋白43.8%左右,抗营养因子较高,需关注脲酶和KOH溶解度。",
|
||||
"unit_price": 3.05
|
||||
"unit_price": 3.05,
|
||||
"max_ratio": 65.00
|
||||
},
|
||||
"大豆粕46": {
|
||||
"descriptions": "优质豆粕,蛋白更高,抗营养因子更低,是猪料最常用蛋白原料。",
|
||||
"unit_price": 3.25
|
||||
"unit_price": 3.25,
|
||||
"max_ratio": 65.00
|
||||
},
|
||||
"大豆粕48": {
|
||||
"descriptions": "高蛋白豆粕,抗营养因子最低,低蛋白日粮配方的理想蛋白源。",
|
||||
"unit_price": 3.60
|
||||
"unit_price": 3.60,
|
||||
"max_ratio": 65.00
|
||||
},
|
||||
"大麦": {
|
||||
"descriptions": "能量稍低于玉米,纤维较高,可部分替代玉米,注意DON毒素风险。",
|
||||
"unit_price": 2.10
|
||||
"unit_price": 2.10,
|
||||
"max_ratio": 60.00
|
||||
},
|
||||
"小苏打": {
|
||||
"descriptions": "缓冲剂和钠源,缓解热应激、改善母猪泌乳期酸中毒。",
|
||||
"unit_price": 1.60
|
||||
"unit_price": 1.60,
|
||||
"max_ratio": 2.00
|
||||
},
|
||||
"小麦": {
|
||||
"descriptions": "能量与玉米接近,但黏性大,易导致肠道问题,仔猪料慎用。",
|
||||
"unit_price": 2.55
|
||||
"unit_price": 2.55,
|
||||
"max_ratio": 60.00
|
||||
},
|
||||
"小麦次粉": {
|
||||
"descriptions": "小麦加工副产品,蛋白和磷较高,但DON和ZEN风险高,限量使用。",
|
||||
"unit_price": 2.20
|
||||
"unit_price": 2.20,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"小麦麸": {
|
||||
"descriptions": "高纤维原料,用于母猪料促进肠道蠕动,降低便秘。",
|
||||
"unit_price": 1.75
|
||||
"unit_price": 1.75,
|
||||
"max_ratio": 40.00
|
||||
},
|
||||
"木薯干": {
|
||||
"descriptions": "高能量淀粉源,几乎不含蛋白,价格低廉,但需搭配优质蛋白。",
|
||||
"unit_price": 2.05
|
||||
"unit_price": 2.05,
|
||||
"max_ratio": 50.00
|
||||
},
|
||||
"杂交构树叶粉": {
|
||||
"descriptions": "新型蛋白饲料资源,蛋白中等,富含黄酮,但单宁和草酸高,需限量并配合脱毒处理。",
|
||||
"unit_price": 2.20
|
||||
"unit_price": 2.20,
|
||||
"max_ratio": 5.00
|
||||
},
|
||||
"构树叶粉(老叶高纤维)": {
|
||||
"descriptions": "老叶构树粉,纤维更高,适合母猪粗饲料使用。",
|
||||
"unit_price": 1.50
|
||||
"unit_price": 1.50,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"柠檬酸渣": {
|
||||
"descriptions": "湿态副产品,适口性好,可用于母猪料降低成本。",
|
||||
"unit_price": 0.50
|
||||
"unit_price": 0.50,
|
||||
"max_ratio": 5.00
|
||||
},
|
||||
"棉籽粕": {
|
||||
"descriptions": "蛋白较高,但游离棉酚严重影响公猪生育力和生长,需严格限量或脱毒。",
|
||||
"unit_price": 2.80
|
||||
"unit_price": 2.80,
|
||||
"max_ratio": 3.00
|
||||
},
|
||||
"棕榈油": {
|
||||
"descriptions": "饱和脂肪酸高,能量高,但熔点高,冬季易凝固,仔猪利用率稍差。",
|
||||
"unit_price": 8.50
|
||||
"unit_price": 8.50,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"棕榈粕": {
|
||||
"descriptions": "高纤维高脂肪副产品,能量一般,多用于母猪料。",
|
||||
"unit_price": 1.60
|
||||
"unit_price": 1.60,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"椰子粕": {
|
||||
"descriptions": "蛋白和能量中等,适口性好,可部分替代豆粕。",
|
||||
"unit_price": 2.30
|
||||
"unit_price": 2.30,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"燕麦": {
|
||||
"descriptions": "能量和脂肪较高,适口性佳,但价格贵,一般少用。",
|
||||
"unit_price": 3.20
|
||||
"unit_price": 3.20,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"燕麦草": {
|
||||
"descriptions": "粗饲料,母猪用以增加饱腹感和肠道健康。",
|
||||
"unit_price": 2.60
|
||||
"unit_price": 2.60,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"猪肺粉": {
|
||||
"descriptions": "优质动物蛋白,消化率高,适口性极佳,适合高档仔猪料。",
|
||||
"unit_price": 9.00
|
||||
"unit_price": 9.00,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"玉米": {
|
||||
"descriptions": "猪最主要的能量原料,淀粉消化率高,毒素风险需关注。",
|
||||
"unit_price": 2.30
|
||||
"unit_price": 2.30,
|
||||
"max_ratio": 100.00
|
||||
},
|
||||
"玉米DDGS": {
|
||||
"descriptions": "高蛋白高脂肪玉米副产品,磷利用率高,适合生长肥育猪和母猪。",
|
||||
"unit_price": 2.15
|
||||
"unit_price": 2.15,
|
||||
"max_ratio": 40.00
|
||||
},
|
||||
"玉米油": {
|
||||
"descriptions": "优质植物油,富含不饱和脂肪酸,能量最高油脂之一。",
|
||||
"unit_price": 9.50
|
||||
"unit_price": 9.50,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"玉米胚芽粕": {
|
||||
"descriptions": "蛋白和脂肪较高,磷利用率好,可部分替代豆粕和油。",
|
||||
"unit_price": 2.05
|
||||
"unit_price": 2.05,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"玉米蛋白粉60": {
|
||||
"descriptions": "高蛋白高蛋氨酸,色素来源,用于改善猪皮红毛亮。",
|
||||
"unit_price": 4.80
|
||||
"unit_price": 4.80,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"玉米青贮": {
|
||||
"descriptions": "粗饲料,母猪用以调节肠道,降低饲料成本。",
|
||||
"unit_price": 0.40
|
||||
"unit_price": 0.40,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"瓜子粕": {
|
||||
"descriptions": "葵花籽粕的别称,蛋白较高,纤维也高。",
|
||||
"unit_price": 2.10
|
||||
"unit_price": 2.10,
|
||||
"max_ratio": 35.00
|
||||
},
|
||||
"甜菜粕": {
|
||||
"descriptions": "高可溶性纤维,母猪极佳的防便秘原料。",
|
||||
"unit_price": 1.95
|
||||
"unit_price": 1.95,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"石粉": {
|
||||
"descriptions": "最常用的钙源,价格低廉,注意粒度影响吸收率。",
|
||||
"unit_price": 0.18
|
||||
"unit_price": 0.18,
|
||||
"max_ratio": 8.00
|
||||
},
|
||||
"碎米": {
|
||||
"descriptions": "能量接近玉米,蛋白稍低,适口性好。",
|
||||
"unit_price": 2.80
|
||||
"unit_price": 2.80,
|
||||
"max_ratio": 60.00
|
||||
},
|
||||
"磷酸氢钙": {
|
||||
"descriptions": "猪最常用磷钙来源,有效磷高。",
|
||||
"unit_price": 3.20
|
||||
"unit_price": 3.20,
|
||||
"max_ratio": 8.00
|
||||
},
|
||||
"稻草粉": {
|
||||
"descriptions": "最廉价粗纤维来源,母猪限量使用防便秘。",
|
||||
"unit_price": 0.60
|
||||
"unit_price": 0.60,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"稻谷": {
|
||||
"descriptions": "带壳稻子,能量低于玉米,纤维高。",
|
||||
"unit_price": 1.90
|
||||
"unit_price": 1.90,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"稻谷糠": {
|
||||
"descriptions": "米糠的一种,高脂肪高磷,需注意酸败。",
|
||||
"unit_price": 1.60
|
||||
"unit_price": 1.60,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"米糠": {
|
||||
"descriptions": "高能量高磷副产品,注意黄曲霉毒素和酸败。",
|
||||
"unit_price": 1.85
|
||||
"unit_price": 1.85,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"米糠粕": {
|
||||
"descriptions": "脱脂米糠,蛋白较高,能量降低。",
|
||||
"unit_price": 1.95
|
||||
"unit_price": 1.95,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"红薯干": {
|
||||
"descriptions": "高淀粉低蛋白能量原料,类似木薯。",
|
||||
"unit_price": 2.20
|
||||
"unit_price": 2.20,
|
||||
"max_ratio": 50.00
|
||||
},
|
||||
"肉粉": {
|
||||
"descriptions": "普通肉粉,蛋白和灰分波动大,质量不稳定。",
|
||||
"unit_price": 4.50
|
||||
"unit_price": 4.50,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"肉骨粉50": {
|
||||
"descriptions": "含骨较高,钙磷比例好,但蛋白较低。",
|
||||
"unit_price": 4.20
|
||||
"unit_price": 4.20,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"脱脂奶粉": {
|
||||
"descriptions": "优质乳蛋白源,仔猪料黄金原料。",
|
||||
"unit_price": 22.00
|
||||
"unit_price": 22.00,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"膨化全脂大豆": {
|
||||
"descriptions": "经过高温膨化的全脂大豆,抗营养因子破坏彻底,仔猪可用。",
|
||||
"unit_price": 4.10
|
||||
"unit_price": 4.10,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"芝麻粕": {
|
||||
"descriptions": "蛋白高,蛋氨酸丰富,但草酸高,需限量。",
|
||||
"unit_price": 2.90
|
||||
"unit_price": 2.90,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"花生秧粉": {
|
||||
"descriptions": "粗饲料,母猪用。",
|
||||
"unit_price": 0.85
|
||||
"unit_price": 0.85,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"花生粕": {
|
||||
"descriptions": "蛋白高,但黄曲霉毒素风险极高,猪场慎用。",
|
||||
"unit_price": 3.70
|
||||
"unit_price": 3.70,
|
||||
"max_ratio": 5.00
|
||||
},
|
||||
"苜蓿草块": {
|
||||
"descriptions": "优质粗饲料,富含维生素和未知生长因子,母猪和仔猪都适用。",
|
||||
"unit_price": 2.40
|
||||
"unit_price": 2.40,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"苜蓿草粉": {
|
||||
"descriptions": "蛋白较高,但皂苷和香豆素可能影响采食。",
|
||||
"unit_price": 2.50
|
||||
"unit_price": 2.50,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"苹果渣": {
|
||||
"descriptions": "湿态副产品,适口性好,母猪喜欢。",
|
||||
"unit_price": 0.55
|
||||
"unit_price": 0.55,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"菜籽粕": {
|
||||
"descriptions": "普通菜粕,硫甙高,对甲状腺影响大,猪限量使用。",
|
||||
"unit_price": 2.30
|
||||
"unit_price": 2.30,
|
||||
"max_ratio": 15.00
|
||||
},
|
||||
"葡萄糖": {
|
||||
"descriptions": "快速能量源,教槽料常用,缓解应激。",
|
||||
"unit_price": 3.80
|
||||
"unit_price": 3.80,
|
||||
"max_ratio": 15.00
|
||||
},
|
||||
"葵花籽粕": {
|
||||
"descriptions": "高纤维蛋白源,赖氨酸低,需补充赖氨酸。",
|
||||
"unit_price": 2.10
|
||||
"unit_price": 2.10,
|
||||
"max_ratio": 35.00
|
||||
},
|
||||
"蔗糖": {
|
||||
"descriptions": "高能量碳水,教槽料诱食用。",
|
||||
"unit_price": 6.50
|
||||
"unit_price": 6.50,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"虾粉": {
|
||||
"descriptions": "优质动物蛋白,含虾青素,改善体色。",
|
||||
"unit_price": 6.00
|
||||
"unit_price": 6.00,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"蚕蛹粉": {
|
||||
"descriptions": "高蛋白高脂肪,氨基酸平衡好,但脂肪易氧化。",
|
||||
"unit_price": 8.00
|
||||
"unit_price": 8.00,
|
||||
"max_ratio": 15.00
|
||||
},
|
||||
"蚕豆": {
|
||||
"descriptions": "蛋白较高,淀粉消化率好,但含抗营养因子。",
|
||||
"unit_price": 3.40
|
||||
"unit_price": 3.40,
|
||||
"max_ratio": 50.00
|
||||
},
|
||||
"蟹粉": {
|
||||
"descriptions": "高蛋白高灰分动物蛋白,钙磷丰富。",
|
||||
"unit_price": 4.50
|
||||
"unit_price": 4.50,
|
||||
"max_ratio": 10.00
|
||||
},
|
||||
"血浆蛋白粉": {
|
||||
"descriptions": "仔猪断奶料黄金功能性蛋白,IgG高,促进肠道发育和免疫。",
|
||||
"unit_price": 45.00
|
||||
"unit_price": 45.00,
|
||||
"max_ratio": 5.00
|
||||
},
|
||||
"血粉": {
|
||||
"descriptions": "赖氨酸极高,但适口性差,需喷涂使用。",
|
||||
"unit_price": 6.50
|
||||
"unit_price": 6.50,
|
||||
"max_ratio": 5.00
|
||||
},
|
||||
"豆磷脂": {
|
||||
"descriptions": "高能量乳化剂,促进脂肪消化,改善皮毛。",
|
||||
"unit_price": 6.80
|
||||
"unit_price": 6.80,
|
||||
"max_ratio": 1.00
|
||||
},
|
||||
"豌豆": {
|
||||
"descriptions": "蛋白中等,淀粉消化好,可部分替代玉米和豆粕。",
|
||||
"unit_price": 3.50
|
||||
"unit_price": 3.50,
|
||||
"max_ratio": 50.00
|
||||
},
|
||||
"豌豆蛋白": {
|
||||
"descriptions": "豌豆浓缩蛋白,蛋白高,抗营养因子低。",
|
||||
"unit_price": 9.50
|
||||
"unit_price": 9.50,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"进口鱼粉65": {
|
||||
"descriptions": "高品质鱼粉,新鲜度好,仔猪和母猪料优质蛋白源。",
|
||||
"unit_price": 12.80
|
||||
"unit_price": 12.80,
|
||||
"max_ratio": 30.00
|
||||
},
|
||||
"食盐": {
|
||||
"descriptions": "提供钠和氯,调节电解质平衡。",
|
||||
"unit_price": 0.50
|
||||
"unit_price": 0.50,
|
||||
"max_ratio": 1.00
|
||||
},
|
||||
"饲料酵母粉": {
|
||||
"descriptions": "富含核苷酸和小肽,促进肠道健康和免疫。",
|
||||
"unit_price": 6.50
|
||||
"unit_price": 6.50,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"高粱": {
|
||||
"descriptions": "能量接近玉米,但单宁高影响消化率,需选低单宁品种。",
|
||||
"unit_price": 2.20
|
||||
"unit_price": 2.20,
|
||||
"max_ratio": 50.00
|
||||
},
|
||||
"鱼油": {
|
||||
"descriptions": "富含DHA和EPA,促进脑发育和抗炎,母猪和仔猪推荐。",
|
||||
"unit_price": 18.00
|
||||
"unit_price": 18.00,
|
||||
"max_ratio": 3.00
|
||||
},
|
||||
"鸡肉粉": {
|
||||
"descriptions": "优质陆基动物蛋白,消化率高,适口性好。",
|
||||
"unit_price": 7.50
|
||||
"unit_price": 7.50,
|
||||
"max_ratio": 25.00
|
||||
},
|
||||
"鸭肉粉": {
|
||||
"descriptions": "与鸡肉粉类似,脂肪稍高。",
|
||||
"unit_price": 7.20
|
||||
"unit_price": 7.20,
|
||||
"max_ratio": 20.00
|
||||
},
|
||||
"鹅肉粉": {
|
||||
"descriptions": "蛋白和脂肪中等,质量稳定。",
|
||||
"unit_price": 7.00
|
||||
"unit_price": 7.00,
|
||||
"max_ratio": 20.00
|
||||
}
|
||||
},
|
||||
"nutrients": {
|
||||
|
||||
@@ -4,56 +4,56 @@
|
||||
"杜长大 (DLY)": {
|
||||
"保育期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.012,
|
||||
"max_requirement": 0.015
|
||||
"min_requirement": 1.2,
|
||||
"max_requirement": 1.5
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0072,
|
||||
"max_requirement": 0.0105
|
||||
"min_requirement": 0.72,
|
||||
"max_requirement": 1.05
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0078,
|
||||
"max_requirement": 0.0108
|
||||
"min_requirement": 0.78,
|
||||
"max_requirement": 1.08
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0022,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.22,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.22
|
||||
"min_requirement": 18.0,
|
||||
"max_requirement": 22.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.009,
|
||||
"max_requirement": 0.012
|
||||
"min_requirement": 0.9,
|
||||
"max_requirement": 1.2
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3226.5,
|
||||
"max_requirement": 3585.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -67,56 +67,56 @@
|
||||
},
|
||||
"育肥前期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0094,
|
||||
"max_requirement": 0.0110
|
||||
"min_requirement": 0.94,
|
||||
"max_requirement": 1.10
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0055,
|
||||
"max_requirement": 0.0073
|
||||
"min_requirement": 0.55,
|
||||
"max_requirement": 0.73
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0058,
|
||||
"max_requirement": 0.0077
|
||||
"min_requirement": 0.58,
|
||||
"max_requirement": 0.77
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0016,
|
||||
"max_requirement": 0.0022
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.22
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.18
|
||||
"min_requirement": 16.0,
|
||||
"max_requirement": 18.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.007,
|
||||
"max_requirement": 0.009
|
||||
"min_requirement": 0.7,
|
||||
"max_requirement": 0.9
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0040
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.40
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3107.0,
|
||||
"max_requirement": 3346.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -130,56 +130,56 @@
|
||||
},
|
||||
"育肥后期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0081,
|
||||
"max_requirement": 0.0090
|
||||
"min_requirement": 0.81,
|
||||
"max_requirement": 0.90
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.0058
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.58
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0048,
|
||||
"max_requirement": 0.0061
|
||||
"min_requirement": 0.48,
|
||||
"max_requirement": 0.61
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0013,
|
||||
"max_requirement": 0.0018
|
||||
"min_requirement": 0.13,
|
||||
"max_requirement": 0.18
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.14,
|
||||
"max_requirement": 0.16
|
||||
"min_requirement": 14.0,
|
||||
"max_requirement": 16.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.006
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.6
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0018,
|
||||
"max_requirement": 0.0035
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.35
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2987.5,
|
||||
"max_requirement": 3226.5
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -193,56 +193,56 @@
|
||||
},
|
||||
"二次育肥期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0053,
|
||||
"max_requirement": 0.0065
|
||||
"min_requirement": 0.53,
|
||||
"max_requirement": 0.65
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0030,
|
||||
"max_requirement": 0.0041
|
||||
"min_requirement": 0.30,
|
||||
"max_requirement": 0.41
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0031,
|
||||
"max_requirement": 0.0043
|
||||
"min_requirement": 0.31,
|
||||
"max_requirement": 0.43
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0010,
|
||||
"max_requirement": 0.0013
|
||||
"min_requirement": 0.10,
|
||||
"max_requirement": 0.13
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.12,
|
||||
"max_requirement": 0.14
|
||||
"min_requirement": 12.0,
|
||||
"max_requirement": 14.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.004,
|
||||
"max_requirement": 0.0055
|
||||
"min_requirement": 0.4,
|
||||
"max_requirement": 0.55
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2868.0,
|
||||
"max_requirement": 3107.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -258,56 +258,56 @@
|
||||
"杜大长 (DYL)": {
|
||||
"保育期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.012,
|
||||
"max_requirement": 0.015
|
||||
"min_requirement": 1.2,
|
||||
"max_requirement": 1.5
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0072,
|
||||
"max_requirement": 0.0105
|
||||
"min_requirement": 0.72,
|
||||
"max_requirement": 1.05
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0078,
|
||||
"max_requirement": 0.0108
|
||||
"min_requirement": 0.78,
|
||||
"max_requirement": 1.08
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0022,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.22,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.22
|
||||
"min_requirement": 18.0,
|
||||
"max_requirement": 22.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.009,
|
||||
"max_requirement": 0.012
|
||||
"min_requirement": 0.9,
|
||||
"max_requirement": 1.2
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3226.5,
|
||||
"max_requirement": 3585.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -321,56 +321,56 @@
|
||||
},
|
||||
"育肥前期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0094,
|
||||
"max_requirement": 0.0110
|
||||
"min_requirement": 0.94,
|
||||
"max_requirement": 1.10
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0055,
|
||||
"max_requirement": 0.0073
|
||||
"min_requirement": 0.55,
|
||||
"max_requirement": 0.73
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0058,
|
||||
"max_requirement": 0.0077
|
||||
"min_requirement": 0.58,
|
||||
"max_requirement": 0.77
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0016,
|
||||
"max_requirement": 0.0022
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.22
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.18
|
||||
"min_requirement": 16.0,
|
||||
"max_requirement": 18.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.007,
|
||||
"max_requirement": 0.009
|
||||
"min_requirement": 0.7,
|
||||
"max_requirement": 0.9
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0040
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.40
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3107.0,
|
||||
"max_requirement": 3346.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -384,56 +384,56 @@
|
||||
},
|
||||
"育肥后期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0081,
|
||||
"max_requirement": 0.0090
|
||||
"min_requirement": 0.81,
|
||||
"max_requirement": 0.90
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.0058
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.58
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0048,
|
||||
"max_requirement": 0.0061
|
||||
"min_requirement": 0.48,
|
||||
"max_requirement": 0.61
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0013,
|
||||
"max_requirement": 0.0018
|
||||
"min_requirement": 0.13,
|
||||
"max_requirement": 0.18
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.14,
|
||||
"max_requirement": 0.16
|
||||
"min_requirement": 14.0,
|
||||
"max_requirement": 16.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.006
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.6
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0018,
|
||||
"max_requirement": 0.0035
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.35
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2987.5,
|
||||
"max_requirement": 3226.5
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -447,56 +447,56 @@
|
||||
},
|
||||
"二次育肥期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0053,
|
||||
"max_requirement": 0.0065
|
||||
"min_requirement": 0.53,
|
||||
"max_requirement": 0.65
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0030,
|
||||
"max_requirement": 0.0041
|
||||
"min_requirement": 0.30,
|
||||
"max_requirement": 0.41
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0031,
|
||||
"max_requirement": 0.0043
|
||||
"min_requirement": 0.31,
|
||||
"max_requirement": 0.43
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0010,
|
||||
"max_requirement": 0.0013
|
||||
"min_requirement": 0.10,
|
||||
"max_requirement": 0.13
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.12,
|
||||
"max_requirement": 0.14
|
||||
"min_requirement": 12.0,
|
||||
"max_requirement": 14.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.004,
|
||||
"max_requirement": 0.0055
|
||||
"min_requirement": 0.4,
|
||||
"max_requirement": 0.55
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2868.0,
|
||||
"max_requirement": 3107.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -512,56 +512,56 @@
|
||||
"皮长大 (PLY)": {
|
||||
"保育期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.012,
|
||||
"max_requirement": 0.015
|
||||
"min_requirement": 1.2,
|
||||
"max_requirement": 1.5
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0072,
|
||||
"max_requirement": 0.0105
|
||||
"min_requirement": 0.72,
|
||||
"max_requirement": 1.05
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0078,
|
||||
"max_requirement": 0.0108
|
||||
"min_requirement": 0.78,
|
||||
"max_requirement": 1.08
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0022,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.22,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.22
|
||||
"min_requirement": 18.0,
|
||||
"max_requirement": 22.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.009,
|
||||
"max_requirement": 0.012
|
||||
"min_requirement": 0.9,
|
||||
"max_requirement": 1.2
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3226.5,
|
||||
"max_requirement": 3585.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -575,56 +575,56 @@
|
||||
},
|
||||
"育肥前期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0094,
|
||||
"max_requirement": 0.0110
|
||||
"min_requirement": 0.94,
|
||||
"max_requirement": 1.10
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0055,
|
||||
"max_requirement": 0.0073
|
||||
"min_requirement": 0.55,
|
||||
"max_requirement": 0.73
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0058,
|
||||
"max_requirement": 0.0077
|
||||
"min_requirement": 0.58,
|
||||
"max_requirement": 0.77
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0016,
|
||||
"max_requirement": 0.0022
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.22
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.16,
|
||||
"max_requirement": 0.18
|
||||
"min_requirement": 16.0,
|
||||
"max_requirement": 18.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.007,
|
||||
"max_requirement": 0.009
|
||||
"min_requirement": 0.7,
|
||||
"max_requirement": 0.9
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.002,
|
||||
"max_requirement": 0.0040
|
||||
"min_requirement": 0.2,
|
||||
"max_requirement": 0.40
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 3107.0,
|
||||
"max_requirement": 3346.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -638,56 +638,56 @@
|
||||
},
|
||||
"育肥后期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0081,
|
||||
"max_requirement": 0.0090
|
||||
"min_requirement": 0.81,
|
||||
"max_requirement": 0.90
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.0058
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.58
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0048,
|
||||
"max_requirement": 0.0061
|
||||
"min_requirement": 0.48,
|
||||
"max_requirement": 0.61
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0013,
|
||||
"max_requirement": 0.0018
|
||||
"min_requirement": 0.13,
|
||||
"max_requirement": 0.18
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.14,
|
||||
"max_requirement": 0.16
|
||||
"min_requirement": 14.0,
|
||||
"max_requirement": 16.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.006,
|
||||
"max_requirement": 0.008
|
||||
"min_requirement": 0.6,
|
||||
"max_requirement": 0.8
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.0045,
|
||||
"max_requirement": 0.006
|
||||
"min_requirement": 0.45,
|
||||
"max_requirement": 0.6
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0018,
|
||||
"max_requirement": 0.0035
|
||||
"min_requirement": 0.18,
|
||||
"max_requirement": 0.35
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2987.5,
|
||||
"max_requirement": 3226.5
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
@@ -701,56 +701,56 @@
|
||||
},
|
||||
"二次育肥期": {
|
||||
"可消化赖氨酸 (SID %)": {
|
||||
"min_requirement": 0.0053,
|
||||
"max_requirement": 0.0065
|
||||
"min_requirement": 0.53,
|
||||
"max_requirement": 0.65
|
||||
},
|
||||
"蛋+胱氨酸 (%)": {
|
||||
"min_requirement": 0.0030,
|
||||
"max_requirement": 0.0041
|
||||
"min_requirement": 0.30,
|
||||
"max_requirement": 0.41
|
||||
},
|
||||
"可消化苏氨酸 (SID %)": {
|
||||
"min_requirement": 0.0031,
|
||||
"max_requirement": 0.0043
|
||||
"min_requirement": 0.31,
|
||||
"max_requirement": 0.43
|
||||
},
|
||||
"可消化色氨酸 (SID %)": {
|
||||
"min_requirement": 0.0010,
|
||||
"max_requirement": 0.0013
|
||||
"min_requirement": 0.10,
|
||||
"max_requirement": 0.13
|
||||
},
|
||||
"粗蛋白 (%)": {
|
||||
"min_requirement": 0.12,
|
||||
"max_requirement": 0.14
|
||||
"min_requirement": 12.0,
|
||||
"max_requirement": 14.0
|
||||
},
|
||||
"粗脂肪 (%)": {
|
||||
"min_requirement": 0.03,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 3.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"粗纤维 (%)": {
|
||||
"min_requirement": 0.02,
|
||||
"max_requirement": 0.06
|
||||
"min_requirement": 2.0,
|
||||
"max_requirement": 6.0
|
||||
},
|
||||
"钙 (%)": {
|
||||
"min_requirement": 0.005,
|
||||
"max_requirement": 0.007
|
||||
"min_requirement": 0.5,
|
||||
"max_requirement": 0.7
|
||||
},
|
||||
"总磷 (%)": {
|
||||
"min_requirement": 0.004,
|
||||
"max_requirement": 0.0055
|
||||
"min_requirement": 0.4,
|
||||
"max_requirement": 0.55
|
||||
},
|
||||
"有效磷 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0030
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.30
|
||||
},
|
||||
"代谢能 (kcal/kg)": {
|
||||
"min_requirement": 2868.0,
|
||||
"max_requirement": 3107.0
|
||||
},
|
||||
"钠 (%)": {
|
||||
"min_requirement": 0.0015,
|
||||
"max_requirement": 0.0025
|
||||
"min_requirement": 0.15,
|
||||
"max_requirement": 0.25
|
||||
},
|
||||
"氯 (%)": {
|
||||
"min_requirement": 0.0025,
|
||||
"max_requirement": 0.0045
|
||||
"min_requirement": 0.25,
|
||||
"max_requirement": 0.45
|
||||
},
|
||||
"黄曲霉毒素B1 (μg/kg)": {
|
||||
"max_requirement": 10
|
||||
|
||||
@@ -63,3 +63,5 @@ http://git.huangwc.com/pig/pig-farm-controller/issues/66
|
||||
13. 重构配方领域
|
||||
14. 配方增删改查服务层和控制器
|
||||
15. 实现库存管理相关逻辑
|
||||
16. 实现配方生成器
|
||||
17. 实现使用系统中所有可用的原料一键生成配方
|
||||
79
docs/docs.go
79
docs/docs.go
@@ -3145,6 +3145,52 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"饲料管理-配方"
|
||||
],
|
||||
"summary": "使用系统中所有可用的原料一键生成配方",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "猪类型ID",
|
||||
"name": "pig_type_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "业务码为201代表创建成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/dto.GenerateRecipeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3669,7 +3715,6 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -3679,12 +3724,12 @@ const docTemplate = `{
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -3694,7 +3739,8 @@ const docTemplate = `{
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -7439,6 +7485,23 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.GenerateRecipeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "新生成的配方描述",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "新生成的配方ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "新生成的配方名称",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.HistoricalAlarmDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10523,7 +10586,6 @@ const docTemplate = `{
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -10533,10 +10595,10 @@ const docTemplate = `{
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -10546,7 +10608,8 @@ const docTemplate = `{
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -3137,6 +3137,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"饲料管理-配方"
|
||||
],
|
||||
"summary": "使用系统中所有可用的原料一键生成配方",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "猪类型ID",
|
||||
"name": "pig_type_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "业务码为201代表创建成功",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/dto.GenerateRecipeResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/feed/recipes/{id}": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -3661,7 +3707,6 @@
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -3671,12 +3716,12 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -3686,7 +3731,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -7431,6 +7477,23 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.GenerateRecipeResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"description": "新生成的配方描述",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "新生成的配方ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"description": "新生成的配方名称",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dto.HistoricalAlarmDTO": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -10515,7 +10578,6 @@
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -10525,10 +10587,10 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -10538,7 +10600,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -565,6 +565,18 @@ definitions:
|
||||
thresholds:
|
||||
type: number
|
||||
type: object
|
||||
dto.GenerateRecipeResponse:
|
||||
properties:
|
||||
description:
|
||||
description: 新生成的配方描述
|
||||
type: string
|
||||
id:
|
||||
description: 新生成的配方ID
|
||||
type: integer
|
||||
name:
|
||||
description: 新生成的配方名称
|
||||
type: string
|
||||
type: object
|
||||
dto.HistoricalAlarmDTO:
|
||||
properties:
|
||||
alarm_code:
|
||||
@@ -2719,7 +2731,6 @@ definitions:
|
||||
- PlanTypeFilterSystem
|
||||
zapcore.Level:
|
||||
enum:
|
||||
- 7
|
||||
- -1
|
||||
- 0
|
||||
- 1
|
||||
@@ -2730,10 +2741,10 @@ definitions:
|
||||
- -1
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
format: int32
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- _numLevels
|
||||
- DebugLevel
|
||||
- InfoLevel
|
||||
- WarnLevel
|
||||
@@ -2744,6 +2755,7 @@ definitions:
|
||||
- _minLevel
|
||||
- _maxLevel
|
||||
- InvalidLevel
|
||||
- _numLevels
|
||||
info:
|
||||
contact:
|
||||
email: divano@example.com
|
||||
@@ -4722,6 +4734,32 @@ paths:
|
||||
summary: 更新配方
|
||||
tags:
|
||||
- 饲料管理-配方
|
||||
/api/v1/feed/recipes/generate-from-all-materials/{pig_type_id}:
|
||||
post:
|
||||
description: 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。
|
||||
parameters:
|
||||
- description: 猪类型ID
|
||||
in: path
|
||||
name: pig_type_id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: 业务码为201代表创建成功
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/dto.GenerateRecipeResponse'
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 使用系统中所有可用的原料一键生成配方
|
||||
tags:
|
||||
- 饲料管理-配方
|
||||
/api/v1/inventory/stock/adjust:
|
||||
post:
|
||||
consumes:
|
||||
@@ -4947,7 +4985,6 @@ paths:
|
||||
name: end_time
|
||||
type: string
|
||||
- enum:
|
||||
- 7
|
||||
- -1
|
||||
- 0
|
||||
- 1
|
||||
@@ -4958,12 +4995,12 @@ paths:
|
||||
- -1
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
format: int32
|
||||
in: query
|
||||
name: level
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- _numLevels
|
||||
- DebugLevel
|
||||
- InfoLevel
|
||||
- WarnLevel
|
||||
@@ -4974,6 +5011,7 @@ paths:
|
||||
- _minLevel
|
||||
- _maxLevel
|
||||
- InvalidLevel
|
||||
- _numLevels
|
||||
- enum:
|
||||
- 邮件
|
||||
- 企业微信
|
||||
|
||||
1
go.mod
1
go.mod
@@ -19,6 +19,7 @@ require (
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.43.0
|
||||
gonum.org/v1/gonum v0.16.0
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
|
||||
2
go.sum
2
go.sum
@@ -171,6 +171,8 @@ golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -259,6 +259,7 @@ func (a *API) setupRoutes() {
|
||||
feedGroup.DELETE("/recipes/:id", a.recipeController.DeleteRecipe)
|
||||
feedGroup.GET("/recipes/:id", a.recipeController.GetRecipe)
|
||||
feedGroup.GET("/recipes", a.recipeController.ListRecipes)
|
||||
feedGroup.POST("/recipes/generate-from-all-materials/:pig_type_id", a.recipeController.GenerateFromAllMaterials)
|
||||
}
|
||||
logger.Debug("饲料管理相关接口注册成功 (需要认证和审计)")
|
||||
|
||||
|
||||
@@ -194,3 +194,34 @@ func (c *RecipeController) ListRecipes(ctx echo.Context) error {
|
||||
logger.Infof("%s: 获取配方列表成功, 数量: %d", actionType, len(resp.List))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取配方列表成功", resp, actionType, "获取配方列表成功", resp)
|
||||
}
|
||||
|
||||
// GenerateFromAllMaterials godoc
|
||||
// @Summary 使用系统中所有可用的原料一键生成配方
|
||||
// @Description 根据指定的猪类型ID,使用系统中所有可用的原料,自动计算并创建一个成本最优的配方。
|
||||
// @Tags 饲料管理-配方
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param pig_type_id path int true "猪类型ID"
|
||||
// @Success 201 {object} controller.Response{data=dto.GenerateRecipeResponse} "业务码为201代表创建成功"
|
||||
// @Router /api/v1/feed/recipes/generate-from-all-materials/{pig_type_id} [post]
|
||||
func (c *RecipeController) GenerateFromAllMaterials(ctx echo.Context) error {
|
||||
reqCtx, logger := logs.Trace(ctx.Request().Context(), c.ctx, "GenerateFromAllMaterials")
|
||||
const actionType = "使用系统中所有可用的原料一键生成配方"
|
||||
|
||||
idStr := ctx.Param("pig_type_id")
|
||||
id, err := strconv.ParseUint(idStr, 10, 32)
|
||||
if err != nil {
|
||||
logger.Errorf("%s: 猪类型ID格式错误: %v, ID: %s", actionType, err, idStr)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的猪类型ID格式", actionType, "猪类型ID格式错误", idStr)
|
||||
}
|
||||
|
||||
recipe, err := c.recipeService.GenerateRecipeWithAllRawMaterials(reqCtx, uint32(id))
|
||||
if err != nil {
|
||||
logger.Errorf("%s: 服务层生成配方失败: %v, PigTypeID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "生成配方失败: "+err.Error(), actionType, "服务层生成配方失败", id)
|
||||
}
|
||||
|
||||
resp := dto.ToGenerateRecipeResponse(recipe)
|
||||
logger.Infof("%s: 配方生成成功, 新配方ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "配方生成成功", resp, actionType, "配方生成成功", resp)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,7 @@ func ConvertRawMaterialToDTO(rm *models.RawMaterial) *RawMaterialResponse {
|
||||
Name: rm.Name,
|
||||
Description: rm.Description,
|
||||
ReferencePrice: rm.ReferencePrice,
|
||||
MaxAdditionRatio: rm.MaxAdditionRatio,
|
||||
RawMaterialNutrients: rawMaterialNutrientDTOs,
|
||||
}
|
||||
}
|
||||
@@ -280,3 +281,15 @@ func ConvertUpdateRecipeRequestToModel(req *UpdateRecipeRequest) *models.Recipe
|
||||
RecipeIngredients: ingredients,
|
||||
}
|
||||
}
|
||||
|
||||
// ToGenerateRecipeResponse 将 models.Recipe 转换为 GenerateRecipeResponse DTO
|
||||
func ToGenerateRecipeResponse(recipe *models.Recipe) *GenerateRecipeResponse {
|
||||
if recipe == nil {
|
||||
return nil
|
||||
}
|
||||
return &GenerateRecipeResponse{
|
||||
ID: recipe.ID,
|
||||
Name: recipe.Name,
|
||||
Description: recipe.Description,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,16 +52,18 @@ type ListNutrientResponse struct {
|
||||
|
||||
// CreateRawMaterialRequest 创建原料的请求体
|
||||
type CreateRawMaterialRequest struct {
|
||||
Name string `json:"name" validate:"required,max=100"` // 原料名称
|
||||
Description string `json:"description" validate:"max=255"` // 描述
|
||||
ReferencePrice float32 `json:"reference_price"` // 参考价格(kg/元)
|
||||
Name string `json:"name" validate:"required,max=100"` // 原料名称
|
||||
Description string `json:"description" validate:"max=255"` // 描述
|
||||
ReferencePrice float32 `json:"reference_price"` // 参考价格(kg/元)
|
||||
MaxAdditionRatio float32 `json:"max_addition_ratio"` // 最大添加比例
|
||||
}
|
||||
|
||||
// UpdateRawMaterialRequest 更新原料的请求体
|
||||
type UpdateRawMaterialRequest struct {
|
||||
Name string `json:"name" validate:"required,max=100"` // 原料名称
|
||||
Description string `json:"description" validate:"max=255"` // 描述
|
||||
ReferencePrice float32 `json:"reference_price"` // 参考价格(kg/元)
|
||||
Name string `json:"name" validate:"required,max=100"` // 原料名称
|
||||
Description string `json:"description" validate:"max=255"` // 描述
|
||||
ReferencePrice float32 `json:"reference_price"` // 参考价格(kg/元)
|
||||
MaxAdditionRatio *float32 `json:"max_addition_ratio"` // 最大添加比例
|
||||
}
|
||||
|
||||
// RawMaterialNutrientDTO 原料营养素响应体
|
||||
@@ -78,6 +80,7 @@ type RawMaterialResponse struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
ReferencePrice float32 `json:"reference_price"` // 参考价格(kg/元)
|
||||
MaxAdditionRatio float32 `json:"max_addition_ratio"` // 最大添加比例
|
||||
RawMaterialNutrients []RawMaterialNutrientDTO `json:"raw_material_nutrients"` // 关联的营养素信息
|
||||
}
|
||||
|
||||
@@ -325,3 +328,10 @@ type ListRecipeResponse struct {
|
||||
List []RecipeResponse `json:"list"`
|
||||
Pagination PaginationDTO `json:"pagination"`
|
||||
}
|
||||
|
||||
// GenerateRecipeResponse 是一键生成配方的响应体
|
||||
type GenerateRecipeResponse struct {
|
||||
ID uint32 `json:"id"` // 新生成的配方ID
|
||||
Name string `json:"name"` // 新生成的配方名称
|
||||
Description string `json:"description"` // 新生成的配方描述
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func NewRawMaterialService(ctx context.Context, recipeSvc recipe.Service) RawMat
|
||||
func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, req *dto.CreateRawMaterialRequest) (*dto.RawMaterialResponse, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial")
|
||||
|
||||
rawMaterial, err := s.recipeSvc.CreateRawMaterial(serviceCtx, req.Name, req.Description, req.ReferencePrice)
|
||||
rawMaterial, err := s.recipeSvc.CreateRawMaterial(serviceCtx, req.Name, req.Description, req.ReferencePrice, req.MaxAdditionRatio)
|
||||
if err != nil {
|
||||
if errors.Is(err, recipe.ErrRawMaterialNameConflict) {
|
||||
return nil, ErrRawMaterialNameConflict
|
||||
@@ -61,7 +61,7 @@ func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, req *dto
|
||||
func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, req *dto.UpdateRawMaterialRequest) (*dto.RawMaterialResponse, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial")
|
||||
|
||||
rawMaterial, err := s.recipeSvc.UpdateRawMaterial(serviceCtx, id, req.Name, req.Description, req.ReferencePrice)
|
||||
rawMaterial, err := s.recipeSvc.UpdateRawMaterial(serviceCtx, id, req.Name, req.Description, req.ReferencePrice, req.MaxAdditionRatio)
|
||||
if err != nil {
|
||||
if errors.Is(err, recipe.ErrRawMaterialNotFound) {
|
||||
return nil, ErrRawMaterialNotFound
|
||||
|
||||
@@ -25,22 +25,31 @@ type RecipeService interface {
|
||||
DeleteRecipe(ctx context.Context, id uint32) error
|
||||
GetRecipeByID(ctx context.Context, id uint32) (*dto.RecipeResponse, error)
|
||||
ListRecipes(ctx context.Context, req *dto.ListRecipeRequest) (*dto.ListRecipeResponse, error)
|
||||
// GenerateRecipeWithAllRawMaterials 添加新方法
|
||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
}
|
||||
|
||||
// recipeServiceImpl 是 RecipeService 接口的实现
|
||||
type recipeServiceImpl struct {
|
||||
ctx context.Context
|
||||
recipeSvc recipe.RecipeCoreService
|
||||
recipeSvc recipe.Service
|
||||
}
|
||||
|
||||
// NewRecipeService 创建一个新的 RecipeService 实例
|
||||
func NewRecipeService(ctx context.Context, recipeSvc recipe.RecipeCoreService) RecipeService {
|
||||
func NewRecipeService(ctx context.Context, recipeSvc recipe.Service) RecipeService {
|
||||
return &recipeServiceImpl{
|
||||
ctx: ctx,
|
||||
recipeSvc: recipeSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateRecipeWithAllRawMaterials 实现新方法
|
||||
func (s *recipeServiceImpl) GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "GenerateRecipeWithAllRawMaterials")
|
||||
// 直接调用领域服务的方法
|
||||
return s.recipeSvc.GenerateRecipeWithAllRawMaterials(serviceCtx, pigTypeID)
|
||||
}
|
||||
|
||||
// CreateRecipe 创建配方
|
||||
func (s *recipeServiceImpl) CreateRecipe(ctx context.Context, req *dto.CreateRecipeRequest) (*dto.RecipeResponse, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRecipe")
|
||||
|
||||
@@ -228,6 +228,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
||||
pigTypeService := recipe.NewPigTypeService(logs.AddCompName(baseCtx, "PigTypeService"), infra.repos.unitOfWork, infra.repos.pigTypeRepo)
|
||||
rawMaterialService := recipe.NewRawMaterialService(logs.AddCompName(baseCtx, "RawMaterialService"), infra.repos.unitOfWork, infra.repos.rawMaterialRepo, inventoryService)
|
||||
recipeCoreService := recipe.NewRecipeCoreService(logs.AddCompName(baseCtx, "RecipeCoreService"), infra.repos.unitOfWork, infra.repos.recipeRepo)
|
||||
recipeGenerateManager := recipe.NewRecipeGenerateManager(logs.AddCompName(baseCtx, "RecipeGenerateManager"))
|
||||
recipeService := recipe.NewRecipeService(
|
||||
logs.AddCompName(baseCtx, "RecipeService"),
|
||||
nutrientService,
|
||||
@@ -236,6 +237,7 @@ func initDomainServices(ctx context.Context, cfg *config.Config, infra *Infrastr
|
||||
pigAgeStageService,
|
||||
pigTypeService,
|
||||
recipeCoreService,
|
||||
recipeGenerateManager,
|
||||
)
|
||||
|
||||
return &DomainServices{
|
||||
|
||||
@@ -21,15 +21,16 @@ type StockQuerier interface {
|
||||
|
||||
// 定义领域特定的错误
|
||||
var (
|
||||
ErrRawMaterialNameConflict = fmt.Errorf("原料名称已存在")
|
||||
ErrRawMaterialNotFound = fmt.Errorf("原料不存在")
|
||||
ErrStockNotEmpty = fmt.Errorf("原料尚有库存,无法删除")
|
||||
ErrRawMaterialNameConflict = fmt.Errorf("原料名称已存在")
|
||||
ErrRawMaterialNotFound = fmt.Errorf("原料不存在")
|
||||
ErrStockNotEmpty = fmt.Errorf("原料尚有库存,无法删除")
|
||||
ErrRawMaterialInUseByRecipe = fmt.Errorf("原料已被配方使用,无法删除")
|
||||
)
|
||||
|
||||
// RawMaterialService 定义了原料领域的核心业务服务接口
|
||||
type RawMaterialService interface {
|
||||
CreateRawMaterial(ctx context.Context, name, description string, referencePrice float32) (*models.RawMaterial, error)
|
||||
UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32) (*models.RawMaterial, error)
|
||||
CreateRawMaterial(ctx context.Context, name, description string, referencePrice, maxAdditionRatio float32) (*models.RawMaterial, error)
|
||||
UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32, maxAdditionRatio *float32) (*models.RawMaterial, error)
|
||||
DeleteRawMaterial(ctx context.Context, id uint32) error
|
||||
GetRawMaterial(ctx context.Context, id uint32) (*models.RawMaterial, error)
|
||||
ListRawMaterials(ctx context.Context, opts repository.RawMaterialListOptions, page, pageSize int) ([]models.RawMaterial, int64, error)
|
||||
@@ -55,7 +56,7 @@ func NewRawMaterialService(ctx context.Context, uow repository.UnitOfWork, rawMa
|
||||
}
|
||||
|
||||
// CreateRawMaterial 实现了创建原料的核心业务逻辑
|
||||
func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, description string, referencePrice float32) (*models.RawMaterial, error) {
|
||||
func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, description string, referencePrice, maxAdditionRatio float32) (*models.RawMaterial, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "CreateRawMaterial")
|
||||
|
||||
// 检查名称是否已存在
|
||||
@@ -68,9 +69,10 @@ func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, de
|
||||
}
|
||||
|
||||
rawMaterial := &models.RawMaterial{
|
||||
Name: name,
|
||||
Description: description,
|
||||
ReferencePrice: referencePrice,
|
||||
Name: name,
|
||||
Description: description,
|
||||
ReferencePrice: referencePrice,
|
||||
MaxAdditionRatio: maxAdditionRatio,
|
||||
}
|
||||
|
||||
if err := s.rawMaterialRepo.CreateRawMaterial(serviceCtx, rawMaterial); err != nil {
|
||||
@@ -81,7 +83,7 @@ func (s *rawMaterialServiceImpl) CreateRawMaterial(ctx context.Context, name, de
|
||||
}
|
||||
|
||||
// UpdateRawMaterial 实现了更新原料的核心业务逻辑
|
||||
func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32) (*models.RawMaterial, error) {
|
||||
func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint32, name, description string, referencePrice float32, maxAdditionRatio *float32) (*models.RawMaterial, error) {
|
||||
serviceCtx := logs.AddFuncName(ctx, s.ctx, "UpdateRawMaterial")
|
||||
|
||||
// 检查要更新的实体是否存在
|
||||
@@ -107,6 +109,9 @@ func (s *rawMaterialServiceImpl) UpdateRawMaterial(ctx context.Context, id uint3
|
||||
rawMaterial.Name = name
|
||||
rawMaterial.Description = description
|
||||
rawMaterial.ReferencePrice = referencePrice
|
||||
if maxAdditionRatio != nil {
|
||||
rawMaterial.MaxAdditionRatio = *maxAdditionRatio
|
||||
}
|
||||
|
||||
if err := s.rawMaterialRepo.UpdateRawMaterial(serviceCtx, rawMaterial); err != nil {
|
||||
return nil, fmt.Errorf("更新原料失败: %w", err)
|
||||
@@ -138,6 +143,15 @@ func (s *rawMaterialServiceImpl) DeleteRawMaterial(ctx context.Context, id uint3
|
||||
return ErrStockNotEmpty
|
||||
}
|
||||
|
||||
// 检查原料是否被配方使用
|
||||
isUsed, err := s.rawMaterialRepo.IsRawMaterialUsedInRecipes(serviceCtx, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查原料是否被配方使用失败: %w", err)
|
||||
}
|
||||
if isUsed {
|
||||
return ErrRawMaterialInUseByRecipe
|
||||
}
|
||||
|
||||
if err := s.rawMaterialRepo.DeleteRawMaterial(serviceCtx, id); err != nil {
|
||||
return fmt.Errorf("删除原料失败: %w", err)
|
||||
}
|
||||
|
||||
338
internal/domain/recipe/recipe_generate_manager.go
Normal file
338
internal/domain/recipe/recipe_generate_manager.go
Normal file
@@ -0,0 +1,338 @@
|
||||
package recipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"gonum.org/v1/gonum/mat"
|
||||
"gonum.org/v1/gonum/optimize/convex/lp"
|
||||
)
|
||||
|
||||
// RecipeGenerateManager 定义了配方生成器的能力。
|
||||
// 它可以有多种实现,例如基于成本优化、基于生长性能优化等。
|
||||
type RecipeGenerateManager interface {
|
||||
// GenerateRecipe 根据猪的营养需求和可用原料,生成一个配方。
|
||||
GenerateRecipe(ctx context.Context, pigType models.PigType, materials []models.RawMaterial) (*models.Recipe, error)
|
||||
}
|
||||
|
||||
// recipeGenerateManagerImpl 是 RecipeGenerateManager 的默认实现。
|
||||
// 它实现了基于成本最优的配方生成逻辑。
|
||||
type recipeGenerateManagerImpl struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// NewRecipeGenerateManager 创建一个默认的配方生成器实例。
|
||||
func NewRecipeGenerateManager(ctx context.Context) RecipeGenerateManager {
|
||||
return &recipeGenerateManagerImpl{
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// internalFillerRawMaterialName 是内部虚拟填充料的名称。
|
||||
// 该填充料用于线性规划计算,确保总比例为100%,但不会出现在最终配方中。
|
||||
internalFillerRawMaterialName = "内部填充料_InternalFiller"
|
||||
// internalFillerNutrientID 是内部虚拟填充营养素的ID。
|
||||
// 使用 math.MaxUint32 作为一个极大的、不可能与实际ID冲突的值,用于关联填充料。
|
||||
internalFillerNutrientID = math.MaxUint32
|
||||
)
|
||||
|
||||
// GenerateRecipe 根据猪的营养需求和可用原料,使用线性规划计算出成本最低的饲料配方。
|
||||
func (r *recipeGenerateManagerImpl) GenerateRecipe(ctx context.Context, pigType models.PigType, materials []models.RawMaterial) (*models.Recipe, error) {
|
||||
// 1. 基础校验
|
||||
if len(materials) == 0 {
|
||||
return nil, errors.New("无法生成配方:未提供任何原料")
|
||||
}
|
||||
if len(pigType.PigNutrientRequirements) == 0 {
|
||||
return nil, errors.New("无法生成配方:猪类型未设置营养需求")
|
||||
}
|
||||
|
||||
// 收集猪类型所有有需求的营养素ID (包括min_requirement或max_requirement不为0的)。
|
||||
// 用于后续过滤掉完全不相关的原料。
|
||||
requiredNutrientIDs := make(map[uint32]bool)
|
||||
for _, req := range pigType.PigNutrientRequirements {
|
||||
requiredNutrientIDs[req.NutrientID] = true
|
||||
}
|
||||
|
||||
// 过滤掉那些不包含猪类型任何所需营养素的原料。
|
||||
var filteredMaterials []models.RawMaterial
|
||||
for _, mat := range materials {
|
||||
hasRelevantNutrient := false
|
||||
for _, matNut := range mat.RawMaterialNutrients {
|
||||
// 检查原料是否包含猪类型所需的任何营养素
|
||||
if requiredNutrientIDs[matNut.NutrientID] {
|
||||
hasRelevantNutrient = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// 如果原料包含至少一个猪类型需求的营养素,则保留
|
||||
if hasRelevantNutrient {
|
||||
filteredMaterials = append(filteredMaterials, mat)
|
||||
}
|
||||
}
|
||||
materials = filteredMaterials // 使用过滤后的原料列表
|
||||
|
||||
if len(materials) == 0 {
|
||||
return nil, errors.New("无法生成配方:所有提供的原料都不包含猪类型所需的任何营养素,请检查原料配置或猪类型营养需求")
|
||||
}
|
||||
|
||||
// 创建一个虚拟的、价格为0、不含任何实际营养素的填充料。
|
||||
// 其唯一目的是在LP求解中作为“凑数”的选项,确保总比例为100%,且不影响实际配方成本。
|
||||
fillerRawMaterial := models.RawMaterial{
|
||||
Model: models.Model{
|
||||
ID: math.MaxUint32 - 1, // 使用一个极大的、不可能与实际原料ID冲突的值
|
||||
},
|
||||
Name: internalFillerRawMaterialName,
|
||||
Description: "内部虚拟填充料,用于线性规划凑足100%比例,不含实际营养,价格为0。",
|
||||
ReferencePrice: 0.0, // 价格为0,确保LP优先选择它来凑数
|
||||
RawMaterialNutrients: []models.RawMaterialNutrient{
|
||||
{
|
||||
NutrientID: internalFillerNutrientID, // 关联一个虚拟营养素,确保其在LP中被识别,但其含量为0
|
||||
Value: 0.0,
|
||||
},
|
||||
},
|
||||
}
|
||||
materials = append(materials, fillerRawMaterial) // 将填充料添加到原料列表中
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. 准备数据结构
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// materialNutrients 映射: 为了快速查找原料的营养含量 [RawMaterialID][NutrientID] => Value
|
||||
materialNutrients := make(map[uint32]map[uint32]float64)
|
||||
// materialIndex 映射: 原料ID到矩阵列索引的映射 (前 N 列对应 N 种原料)
|
||||
materialIndex := make(map[uint32]int)
|
||||
// materialIDs 列表: 记录原料ID以便结果回溯
|
||||
materialIDs := make([]uint32, len(materials))
|
||||
|
||||
for i, m := range materials {
|
||||
materialIndex[m.ID] = i
|
||||
materialIDs[i] = m.ID
|
||||
materialNutrients[m.ID] = make(map[uint32]float64)
|
||||
for _, n := range m.RawMaterialNutrients {
|
||||
// 注意:这里假设 float32 转 float64 精度足够
|
||||
materialNutrients[m.ID][n.NutrientID] = float64(n.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// nutrientConstraints 存储营养素的下限和上限约束信息。
|
||||
type nutrientConstraintInfo struct {
|
||||
isMax bool // true=上限约束(<=), false=下限约束(>=)
|
||||
nutrientID uint32
|
||||
limit float64
|
||||
}
|
||||
var nutrientConstraints []nutrientConstraintInfo
|
||||
|
||||
// 添加营养约束
|
||||
for _, req := range pigType.PigNutrientRequirements {
|
||||
// 排除内部虚拟填充营养素的约束,因为它不应有实际需求
|
||||
if req.NutrientID == internalFillerNutrientID {
|
||||
continue
|
||||
}
|
||||
|
||||
// 添加下限约束 (Value >= Min)
|
||||
// 逻辑: Sum(Mat * x) >= Min -> Sum(Mat * x) - slack = Min
|
||||
nutrientConstraints = append(nutrientConstraints, nutrientConstraintInfo{
|
||||
isMax: false,
|
||||
nutrientID: req.NutrientID,
|
||||
limit: float64(req.MinRequirement),
|
||||
})
|
||||
|
||||
// 添加上限约束 (Value <= Max)
|
||||
// 逻辑: Sum(Mat * x) <= Max -> Sum(Mat * x) + slack = Max
|
||||
if req.MaxRequirement > 0 {
|
||||
// 简单的校验,如果 Min > Max 则是逻辑矛盾,直接报错
|
||||
if req.MinRequirement > req.MaxRequirement {
|
||||
return nil, fmt.Errorf("营养素 %d 的需求配置无效: 最小需求 (%f) 大于最大需求 (%f)", req.NutrientID, req.MinRequirement, req.MaxRequirement)
|
||||
}
|
||||
nutrientConstraints = append(nutrientConstraints, nutrientConstraintInfo{
|
||||
isMax: true,
|
||||
nutrientID: req.NutrientID,
|
||||
limit: float64(req.MaxRequirement),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// maxAdditionConstraints 存储每个原料的最大添加比例约束 (x_i <= limit)。
|
||||
type maxAdditionConstraintInfo struct {
|
||||
materialColIndex int // 原料在 A 矩阵中的列索引
|
||||
limit float64
|
||||
}
|
||||
var maxAdditionConstraints []maxAdditionConstraintInfo
|
||||
|
||||
// 遍历所有原料,包括填充料,添加 MaxAdditionRatio 约束
|
||||
for _, mat := range materials {
|
||||
// 填充料不应受 MaxAdditionRatio 限制
|
||||
if mat.ID == fillerRawMaterial.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
// 只有当 MaxAdditionRatio > 0 时才添加约束。
|
||||
// 如果 MaxAdditionRatio 为 0 或负数,则表示该原料没有最大添加比例限制。
|
||||
if mat.MaxAdditionRatio > 0 {
|
||||
materialColIndex, ok := materialIndex[mat.ID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("内部错误:未找到原料 %d (%s) 的列索引", mat.ID, mat.Name)
|
||||
}
|
||||
maxAdditionConstraints = append(maxAdditionConstraints, maxAdditionConstraintInfo{
|
||||
materialColIndex: materialColIndex,
|
||||
limit: float64(mat.MaxAdditionRatio) / 100.0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. 构建线性规划矩阵 (Ax = b) 和 目标函数 (c)
|
||||
// ---------------------------------------------------------
|
||||
|
||||
numMaterials := len(materials) // 此时已包含填充料
|
||||
numNutrientConstraints := len(nutrientConstraints)
|
||||
numMaxAdditionConstraints := len(maxAdditionConstraints)
|
||||
|
||||
// 松弛变量数量 = 营养约束数量 + 最大添加比例约束数量
|
||||
numSlack := numNutrientConstraints + numMaxAdditionConstraints
|
||||
numCols := numMaterials + numSlack
|
||||
|
||||
// 行数 = 1 (总量约束) + 营养约束数量 + 最大添加比例约束数量
|
||||
numRows := 1 + numNutrientConstraints + numMaxAdditionConstraints
|
||||
|
||||
// A: 约束系数矩阵
|
||||
A := mat.NewDense(numRows, numCols, nil)
|
||||
// b: 约束值向量
|
||||
b := make([]float64, numRows)
|
||||
// c: 成本向量 (目标函数系数)
|
||||
c := make([]float64, numCols)
|
||||
|
||||
// 填充 c (成本)
|
||||
for i, m := range materials {
|
||||
c[i] = float64(m.ReferencePrice)
|
||||
}
|
||||
// 松弛变量的成本为 0,Go 默认初始化为 0,无需操作
|
||||
|
||||
// 填充 Row 0: 总量约束 (Sum(x) = 1)
|
||||
// 系数: 所有原料对应列为 1,松弛变量列为 0
|
||||
for j := 0; j < numMaterials; j++ {
|
||||
A.Set(0, j, 1.0)
|
||||
}
|
||||
b[0] = 1.0
|
||||
|
||||
// currentConstraintRowIndex 记录当前正在填充的约束行索引,从1开始(0行被总量约束占用)。
|
||||
currentConstraintRowIndex := 1
|
||||
|
||||
// 填充营养约束行
|
||||
for i, cons := range nutrientConstraints {
|
||||
rowIndex := currentConstraintRowIndex + i
|
||||
// 营养约束的松弛变量列紧跟在原料列之后
|
||||
slackColIndex := numMaterials + i
|
||||
|
||||
b[rowIndex] = cons.limit
|
||||
|
||||
// 设置原料系数
|
||||
for j, m := range materials {
|
||||
// 获取该原料这种营养素的含量,如果没有则为0
|
||||
val := materialNutrients[m.ID][cons.nutrientID]
|
||||
A.Set(rowIndex, j, val)
|
||||
}
|
||||
|
||||
// 设置松弛变量系数
|
||||
// 如果是下限 (>=): Sum - s = Limit => s系数为 -1
|
||||
// 如果是上限 (<=): Sum + s = Limit => s系数为 +1
|
||||
if cons.isMax {
|
||||
A.Set(rowIndex, slackColIndex, 1.0)
|
||||
} else {
|
||||
A.Set(rowIndex, slackColIndex, -1.0)
|
||||
}
|
||||
}
|
||||
currentConstraintRowIndex += numNutrientConstraints // 推进当前约束行索引
|
||||
|
||||
// 填充 MaxAdditionRatio 约束行
|
||||
for i, cons := range maxAdditionConstraints {
|
||||
rowIndex := currentConstraintRowIndex + i
|
||||
// MaxAdditionRatio 约束的松弛变量列在营养约束的松弛变量之后
|
||||
slackColIndex := numMaterials + numNutrientConstraints + i
|
||||
|
||||
// 约束形式: x_j + s_k = Limit_j (其中 x_j 是原料 j 的比例,s_k 是松弛变量)
|
||||
A.Set(rowIndex, cons.materialColIndex, 1.0) // 原料本身的系数
|
||||
A.Set(rowIndex, slackColIndex, 1.0) // 松弛变量的系数
|
||||
b[rowIndex] = cons.limit
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. 执行单纯形法求解
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// lp.Simplex 求解: minimize c^T * x subject to A * x = b, x >= 0
|
||||
optVal, x, err := lp.Simplex(c, A, b, 1e-8, nil)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, lp.ErrInfeasible) {
|
||||
return nil, errors.New("无法生成配方:根据提供的原料,无法满足所有营养需求或最大添加比例限制 (无可行解),请检查原料配置、营养需求或最大添加比例")
|
||||
}
|
||||
if errors.Is(err, lp.ErrUnbounded) {
|
||||
return nil, errors.New("计算错误:解无界 (可能数据配置有误,例如某个营养素没有上限约束且成本为负)")
|
||||
}
|
||||
return nil, fmt.Errorf("配方计算失败: %w", err)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 5. 结果解析与构建
|
||||
// ---------------------------------------------------------
|
||||
|
||||
// 统计实际原料数量(排除填充料)
|
||||
actualMaterialCount := 0
|
||||
for _, m := range materials {
|
||||
if m.ID != fillerRawMaterial.ID {
|
||||
actualMaterialCount++
|
||||
}
|
||||
}
|
||||
|
||||
recipe := &models.Recipe{
|
||||
Name: fmt.Sprintf("%s-%s - 自动计算配方", pigType.Breed.Name, pigType.AgeStage.Name),
|
||||
Description: fmt.Sprintf("基于 %d 种原料计算的最优成本配方。计算时预估成本: %.2f元/kg", actualMaterialCount, optVal),
|
||||
RecipeIngredients: []models.RecipeIngredient{},
|
||||
}
|
||||
|
||||
// 遍历原料部分的解 (前 numMaterials 个变量)
|
||||
totalPercentage := 0.0
|
||||
for i := 0; i < numMaterials; i++ {
|
||||
// 排除内部虚拟填充料,不将其加入最终配方
|
||||
if materialIDs[i] == fillerRawMaterial.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
proportion := x[i]
|
||||
|
||||
// 忽略极小值 (浮点数误差)。
|
||||
// 调整过滤阈值到万分之一 (0.01%),即小于0.0001的比例将被忽略。
|
||||
if proportion < 1e-4 {
|
||||
continue
|
||||
}
|
||||
|
||||
// 记录总和用于最后的校验
|
||||
totalPercentage += proportion
|
||||
|
||||
recipe.RecipeIngredients = append(recipe.RecipeIngredients, models.RecipeIngredient{
|
||||
RawMaterialID: materialIDs[i],
|
||||
// 比例: float64 -> float32
|
||||
Percentage: float32(proportion * 100.0),
|
||||
})
|
||||
}
|
||||
|
||||
// 二次校验: 确保实际原料总量不超过 100% (允许小于100%因为填充料被移除)。
|
||||
// 允许略微超过100%的浮点误差,但不能显著超过。
|
||||
if totalPercentage > 1.0+1e-3 {
|
||||
return nil, fmt.Errorf("计算结果异常:实际原料总量超过 100%% (计算值: %.2f),请检查算法或数据配置", totalPercentage)
|
||||
}
|
||||
// 如果 totalPercentage 小于 1.0,说明填充料被使用,这是符合预期的。
|
||||
// 此时需要在描述中说明需要添加的廉价填充料的百分比。
|
||||
if totalPercentage < 1.0-1e-4 { // 允许微小的浮点误差
|
||||
fillerPercentage := (1.0 - totalPercentage) * 100.0
|
||||
recipe.Description = fmt.Sprintf("%s。注意:配方中实际原料占比 %.2f%%,需额外补充 %.2f%% 廉价填充料", recipe.Description, totalPercentage*100.0, fillerPercentage)
|
||||
}
|
||||
|
||||
return recipe, nil
|
||||
}
|
||||
@@ -2,6 +2,10 @@ package recipe
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
)
|
||||
|
||||
// Service 定义了配方与原料领域的核心业务服务接口
|
||||
@@ -13,6 +17,8 @@ type Service interface {
|
||||
PigAgeStageService
|
||||
PigTypeService
|
||||
RecipeCoreService
|
||||
RecipeGenerateManager
|
||||
GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error)
|
||||
}
|
||||
|
||||
// recipeServiceImpl 是 Service 的实现,通过组合各个子服务来实现
|
||||
@@ -24,6 +30,7 @@ type recipeServiceImpl struct {
|
||||
PigAgeStageService
|
||||
PigTypeService
|
||||
RecipeCoreService
|
||||
RecipeGenerateManager
|
||||
}
|
||||
|
||||
// NewRecipeService 创建一个新的 Service 实例
|
||||
@@ -35,14 +42,50 @@ func NewRecipeService(
|
||||
pigAgeStageService PigAgeStageService,
|
||||
pigTypeService PigTypeService,
|
||||
recipeCoreService RecipeCoreService,
|
||||
recipeGenerateManager RecipeGenerateManager,
|
||||
) Service {
|
||||
return &recipeServiceImpl{
|
||||
ctx: ctx,
|
||||
NutrientService: nutrientService,
|
||||
RawMaterialService: rawMaterialService,
|
||||
PigBreedService: pigBreedService,
|
||||
PigAgeStageService: pigAgeStageService,
|
||||
PigTypeService: pigTypeService,
|
||||
RecipeCoreService: recipeCoreService,
|
||||
ctx: ctx,
|
||||
NutrientService: nutrientService,
|
||||
RawMaterialService: rawMaterialService,
|
||||
PigBreedService: pigBreedService,
|
||||
PigAgeStageService: pigAgeStageService,
|
||||
PigTypeService: pigTypeService,
|
||||
RecipeCoreService: recipeCoreService,
|
||||
RecipeGenerateManager: recipeGenerateManager,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateRecipeWithAllRawMaterials 使用所有已知原料为特定猪类型生成一个新配方。
|
||||
// pigTypeID: 目标猪类型的ID。
|
||||
// 返回: 生成的配方对象指针和可能的错误。
|
||||
func (r *recipeServiceImpl) GenerateRecipeWithAllRawMaterials(ctx context.Context, pigTypeID uint32) (*models.Recipe, error) {
|
||||
// 1. 获取猪只类型信息,确保包含了营养需求
|
||||
pigType, err := r.GetPigTypeByID(ctx, pigTypeID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取猪类型信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 获取所有原料
|
||||
// 我们通过传递一个非常大的 pageSize 来获取所有原料,这在大多数情况下是可行的。
|
||||
// 对于超大规模系统,可能需要考虑分页迭代,但目前这是一个简单有效的策略。
|
||||
materials, _, err := r.ListRawMaterials(ctx, repository.RawMaterialListOptions{}, 1, 9999)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取所有原料列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 调用生成器生成配方
|
||||
recipe, err := r.GenerateRecipe(ctx, *pigType, materials)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成配方失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 保存新生成的配方到数据库
|
||||
// CreateRecipe 会处理配方及其成分的保存
|
||||
if recipe, err = r.CreateRecipe(ctx, recipe); err != nil {
|
||||
return nil, fmt.Errorf("保存生成的配方失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 返回创建的配方 (现在它应该已经有了ID)
|
||||
return recipe, nil
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/database/seeder"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"gorm.io/gorm"
|
||||
@@ -95,9 +92,9 @@ func SeedFromPreset(ctx context.Context, db *gorm.DB, presetDir string) error {
|
||||
var seederFunc SeederFunc
|
||||
switch dataTypeStr {
|
||||
case "nutrient":
|
||||
seederFunc = seedNutrients
|
||||
seederFunc = seeder.SeedNutrients
|
||||
case "pig_nutrient_requirements":
|
||||
seederFunc = seedPigNutrientRequirements
|
||||
seederFunc = seeder.SeedPigNutrientRequirements
|
||||
default:
|
||||
logger.Warnf("警告: 存在未知的 type: '%s',已跳过", dataTypeStr)
|
||||
continue
|
||||
@@ -127,522 +124,3 @@ func SeedFromPreset(ctx context.Context, db *gorm.DB, presetDir string) error {
|
||||
return nil // 提交事务
|
||||
})
|
||||
}
|
||||
|
||||
// rawMaterialInfo 用于临时存储解析后的原料描述和价格信息。
|
||||
type rawMaterialInfo struct {
|
||||
Description string
|
||||
UnitPrice float32
|
||||
}
|
||||
|
||||
// seedNutrients 先严格校验JSON源文件,然后以“有则跳过”的模式播种数据。
|
||||
func seedNutrients(ctx context.Context, tx *gorm.DB, jsonData []byte) error {
|
||||
logger := logs.GetLogger(ctx)
|
||||
|
||||
// 检查 Nutrient 表是否为空,如果非空则跳过播种
|
||||
isEmpty, err := isTableEmpty(tx, &models.Nutrient{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 Nutrient 表是否为空失败: %w", err)
|
||||
}
|
||||
if !isEmpty {
|
||||
logger.Info("已存在原料数据, 跳过数据播种")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 严格校验JSON文件,检查内部重复键
|
||||
if err := validateAndParseNutrientJSON(jsonData); err != nil {
|
||||
return fmt.Errorf("JSON源文件校验失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 解析简介信息
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
rawMaterialInfos := make(map[string]rawMaterialInfo)
|
||||
nutrientDescriptions := make(map[string]string)
|
||||
|
||||
if descriptionsNode.Exists() {
|
||||
// 解析 raw_materials 描述和价格
|
||||
descriptionsNode.Get("raw_materials").ForEach(func(key, value gjson.Result) bool {
|
||||
rawMaterialInfos[key.String()] = rawMaterialInfo{
|
||||
Description: value.Get("descriptions").String(),
|
||||
UnitPrice: float32(value.Get("unit_price").Float()),
|
||||
}
|
||||
return true
|
||||
})
|
||||
descriptionsNode.Get("nutrients").ForEach(func(key, value gjson.Result) bool {
|
||||
nutrientDescriptions[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 将通过校验的、干净的数据写入数据库
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
dataNode.ForEach(func(rawMaterialKey, rawMaterialValue gjson.Result) bool {
|
||||
rawMaterialName := rawMaterialKey.String()
|
||||
var rawMaterial models.RawMaterial
|
||||
|
||||
// 获取原料的描述和价格信息
|
||||
info := rawMaterialInfos[rawMaterialName]
|
||||
|
||||
// 将 Description 和 ReferencePrice 放入 Create 对象中
|
||||
err = tx.Where(models.RawMaterial{Name: rawMaterialName}).
|
||||
FirstOrCreate(&rawMaterial, models.RawMaterial{
|
||||
Name: rawMaterialName,
|
||||
Description: info.Description,
|
||||
ReferencePrice: info.UnitPrice,
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
|
||||
rawMaterialValue.ForEach(func(nutrientKey, nutrientValue gjson.Result) bool {
|
||||
nutrientName := nutrientKey.String()
|
||||
value := float32(nutrientValue.Float())
|
||||
|
||||
var nutrient models.Nutrient
|
||||
// 将 Description 放入 Create 对象中
|
||||
err = tx.Where(models.Nutrient{Name: nutrientName}).
|
||||
FirstOrCreate(&nutrient, models.Nutrient{
|
||||
Name: nutrientName,
|
||||
Description: nutrientDescriptions[nutrientName],
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
|
||||
linkData := models.RawMaterialNutrient{
|
||||
RawMaterialID: rawMaterial.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
}
|
||||
// 使用 FirstOrCreate 确保关联的唯一性
|
||||
err = tx.Where(linkData).FirstOrCreate(&linkData, models.RawMaterialNutrient{
|
||||
RawMaterialID: linkData.RawMaterialID,
|
||||
NutrientID: linkData.NutrientID,
|
||||
Value: value,
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
|
||||
return err // 返回捕获到的错误
|
||||
}
|
||||
|
||||
// seedPigNutrientRequirements 先严格校验JSON源文件,然后以“有则跳过”的模式播种数据。
|
||||
func seedPigNutrientRequirements(ctx context.Context, tx *gorm.DB, jsonData []byte) error {
|
||||
logger := logs.GetLogger(ctx)
|
||||
|
||||
// 检查 PigBreed 表是否为空,如果非空则跳过播种
|
||||
isEmpty, err := isTableEmpty(tx, &models.PigBreed{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 PigBreed 表是否为空失败: %w", err)
|
||||
}
|
||||
if !isEmpty {
|
||||
logger.Info("已存在猪种数据, 跳过数据播种")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 严格校验JSON文件,检查内部重复键
|
||||
if err := validateAndParsePigNutrientRequirementJSON(jsonData); err != nil {
|
||||
return fmt.Errorf("JSON源文件校验失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 解析简介信息
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
pigBreedDescriptions := make(map[string]models.PigBreed)
|
||||
pigAgeStageDescriptions := make(map[string]models.PigAgeStage)
|
||||
pigTypeDescriptions := make(map[string]map[string]models.PigType)
|
||||
|
||||
if descriptionsNode.Exists() {
|
||||
// 解析 pig_breeds 描述
|
||||
descriptionsNode.Get("pig_breeds").ForEach(func(key, value gjson.Result) bool {
|
||||
var pb models.PigBreed
|
||||
pb.Name = key.String()
|
||||
pb.Description = value.Get("description").String()
|
||||
pb.ParentInfo = value.Get("parent_info").String()
|
||||
pb.AppearanceFeatures = value.Get("appearance_features").String()
|
||||
pb.BreedAdvantages = value.Get("breed_advantages").String()
|
||||
pb.BreedDisadvantages = value.Get("breed_disadvantages").String()
|
||||
pigBreedDescriptions[key.String()] = pb
|
||||
return true
|
||||
})
|
||||
|
||||
// 解析 pig_age_stages 描述
|
||||
descriptionsNode.Get("pig_age_stages").ForEach(func(key, value gjson.Result) bool {
|
||||
var pas models.PigAgeStage
|
||||
pas.Name = key.String()
|
||||
pas.Description = value.String()
|
||||
pigAgeStageDescriptions[key.String()] = pas
|
||||
return true
|
||||
})
|
||||
|
||||
// 解析 pig_breed_age_stages (PigType) 描述
|
||||
descriptionsNode.Get("pig_breed_age_stages").ForEach(func(breedKey, breedValue gjson.Result) bool {
|
||||
if _, ok := pigTypeDescriptions[breedKey.String()]; !ok {
|
||||
pigTypeDescriptions[breedKey.String()] = make(map[string]models.PigType)
|
||||
}
|
||||
breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool {
|
||||
var pt models.PigType
|
||||
pt.Description = ageStageValue.Get("description").String()
|
||||
pt.DailyFeedIntake = float32(ageStageValue.Get("daily_feed_intake").Float())
|
||||
pt.DailyGainWeight = float32(ageStageValue.Get("daily_gain_weight").Float())
|
||||
pt.MinDays = uint32(ageStageValue.Get("min_days").Uint())
|
||||
pt.MaxDays = uint32(ageStageValue.Get("max_days").Uint())
|
||||
pt.MinWeight = float32(ageStageValue.Get("min_weight").Float())
|
||||
pt.MaxWeight = float32(ageStageValue.Get("max_weight").Float())
|
||||
pigTypeDescriptions[breedKey.String()][ageStageKey.String()] = pt
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 将通过校验的、干净的数据写入数据库
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
dataNode.ForEach(func(breedKey, breedValue gjson.Result) bool {
|
||||
breedName := breedKey.String()
|
||||
var pigBreed models.PigBreed
|
||||
// 查找或创建 PigBreed
|
||||
pbDesc := pigBreedDescriptions[breedName]
|
||||
err = tx.Where(models.PigBreed{Name: breedName}).
|
||||
FirstOrCreate(&pigBreed, models.PigBreed{
|
||||
Name: breedName,
|
||||
Description: pbDesc.Description,
|
||||
ParentInfo: pbDesc.ParentInfo,
|
||||
AppearanceFeatures: pbDesc.AppearanceFeatures,
|
||||
BreedAdvantages: pbDesc.BreedAdvantages,
|
||||
BreedDisadvantages: pbDesc.BreedDisadvantages,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool {
|
||||
ageStageName := ageStageKey.String()
|
||||
var pigAgeStage models.PigAgeStage
|
||||
// 查找或创建 PigAgeStage
|
||||
pasDesc := pigAgeStageDescriptions[ageStageName]
|
||||
err = tx.Where(models.PigAgeStage{Name: ageStageName}).
|
||||
FirstOrCreate(&pigAgeStage, models.PigAgeStage{
|
||||
Name: ageStageName,
|
||||
Description: pasDesc.Description,
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var pigType models.PigType
|
||||
// 查找或创建 PigType
|
||||
ptDesc := pigTypeDescriptions[breedName][ageStageName]
|
||||
err = tx.Where(models.PigType{BreedID: pigBreed.ID, AgeStageID: pigAgeStage.ID}).
|
||||
FirstOrCreate(&pigType, models.PigType{
|
||||
BreedID: pigBreed.ID,
|
||||
AgeStageID: pigAgeStage.ID,
|
||||
Description: ptDesc.Description,
|
||||
DailyFeedIntake: ptDesc.DailyFeedIntake,
|
||||
DailyGainWeight: ptDesc.DailyGainWeight,
|
||||
MinDays: ptDesc.MinDays,
|
||||
MaxDays: ptDesc.MaxDays,
|
||||
MinWeight: ptDesc.MinWeight,
|
||||
MaxWeight: ptDesc.MaxWeight,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ageStageValue.ForEach(func(nutrientKey, nutrientValue gjson.Result) bool {
|
||||
nutrientName := nutrientKey.String()
|
||||
minReq := float32(nutrientValue.Get("min_requirement").Float())
|
||||
maxReq := float32(nutrientValue.Get("max_requirement").Float())
|
||||
|
||||
var nutrient models.Nutrient
|
||||
// 查找或创建 Nutrient (这里假设 Nutrient 已经在 seedNutrients 中处理,但为了健壮性,再次 FirstOrCreate)
|
||||
err = tx.Where(models.Nutrient{Name: nutrientName}).
|
||||
FirstOrCreate(&nutrient, models.Nutrient{
|
||||
Name: nutrientName,
|
||||
// Description 字段在 nutrient seeder 中处理,这里不设置
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
linkData := models.PigNutrientRequirement{
|
||||
PigTypeID: pigType.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
MinRequirement: minReq,
|
||||
MaxRequirement: maxReq,
|
||||
}
|
||||
// 使用 FirstOrCreate 确保关联的唯一性
|
||||
err = tx.Where(models.PigNutrientRequirement{
|
||||
PigTypeID: pigType.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
}).FirstOrCreate(&linkData, linkData).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
return err // 返回捕获到的错误
|
||||
}
|
||||
|
||||
// validateAndParsePigNutrientRequirementJSON 严格校验猪营养需求JSON文件
|
||||
func validateAndParsePigNutrientRequirementJSON(jsonData []byte) error {
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
if !dataNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'data' 字段")
|
||||
}
|
||||
if !dataNode.IsObject() {
|
||||
return errors.New("'data' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("'data' 字段解析起始符失败: %v", err)
|
||||
}
|
||||
|
||||
seenBreeds := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 PigBreed 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析猪品种名称失败: %w", err)
|
||||
}
|
||||
breedName := t.(string)
|
||||
if seenBreeds[breedName] {
|
||||
return fmt.Errorf("猪品种名称 '%s' 重复", breedName)
|
||||
}
|
||||
seenBreeds[breedName] = true
|
||||
|
||||
// 解析该品种的年龄阶段对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望猪品种 '%s' 的值是一个JSON对象", breedName)
|
||||
}
|
||||
|
||||
seenAgeStages := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 PigAgeStage 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在猪品种 '%s' 中解析年龄阶段名称失败: %w", breedName, err)
|
||||
}
|
||||
ageStageName := t.(string)
|
||||
if seenAgeStages[ageStageName] {
|
||||
return fmt.Errorf("在猪品种 '%s' 中, 年龄阶段名称 '%s' 重复", breedName, ageStageName)
|
||||
}
|
||||
seenAgeStages[ageStageName] = true
|
||||
|
||||
// 解析该年龄阶段的营养成分对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望年龄阶段 '%s' 的值是一个JSON对象", ageStageName)
|
||||
}
|
||||
|
||||
seenNutrients := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 Nutrient 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在年龄阶段 '%s' 中解析营养素名称失败: %w", ageStageName, err)
|
||||
}
|
||||
nutrientName := t.(string)
|
||||
if seenNutrients[nutrientName] {
|
||||
return fmt.Errorf("在年龄阶段 '%s' 中, 营养素名称 '%s' 重复", ageStageName, nutrientName)
|
||||
}
|
||||
seenNutrients[nutrientName] = true
|
||||
|
||||
// 解析 min_requirement 和 max_requirement 对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望营养素 '%s' 的值是一个JSON对象", nutrientName)
|
||||
}
|
||||
|
||||
for decoder.More() {
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析营养素 '%s' 的需求键失败: %w", nutrientName, err)
|
||||
}
|
||||
// key := t.(string) // 校验时不需要使用 key 的值
|
||||
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析营养素 '%s' 的需求值失败: %w", nutrientName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望营养素 '%s' 的需求值是数字, 但实际得到的类型是 %T, 值为 '%v'", nutrientName, t, t)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析营养素 '%s' 的值结束符 '}' 失败", nutrientName)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析年龄阶段 '%s' 的值结束符 '}' 失败", ageStageName)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析猪品种 '%s' 的值结束符 '}' 失败", breedName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateAndParseNutrientJSON 严格校验JSON文件
|
||||
func validateAndParseNutrientJSON(jsonData []byte) error {
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
if !descriptionsNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'descriptions' 字段")
|
||||
}
|
||||
if !descriptionsNode.IsObject() {
|
||||
return errors.New("'descriptions' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
rawMaterialsNode := descriptionsNode.Get("raw_materials")
|
||||
if !rawMaterialsNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'descriptions.raw_materials' 字段")
|
||||
}
|
||||
if !rawMaterialsNode.IsObject() {
|
||||
return errors.New("'descriptions.raw_materials' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
// 使用 json.Decoder 严格校验 raw_materials 的结构
|
||||
decoder := json.NewDecoder(bytes.NewReader([]byte(rawMaterialsNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("'descriptions.raw_materials' 字段解析起始符失败: %v", err)
|
||||
}
|
||||
|
||||
seenRawMaterials := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 1. 解析原料名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料名称失败: %w", err)
|
||||
}
|
||||
rawMaterialName := t.(string)
|
||||
if seenRawMaterials[rawMaterialName] {
|
||||
return fmt.Errorf("原料名称 '%s' 重复", rawMaterialName)
|
||||
}
|
||||
seenRawMaterials[rawMaterialName] = true
|
||||
|
||||
// 2. 解析该原料的描述和价格对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望原料 '%s' 的值是一个JSON对象", rawMaterialName)
|
||||
}
|
||||
|
||||
for decoder.More() {
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 内部键失败: %w", rawMaterialName, err)
|
||||
}
|
||||
key := t.(string)
|
||||
|
||||
switch key {
|
||||
case "descriptions":
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的 'descriptions' 值失败: %w", rawMaterialName, err)
|
||||
}
|
||||
if _, ok := t.(string); !ok {
|
||||
return fmt.Errorf("期望原料 '%s' 的 'descriptions' 值是字符串, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
|
||||
}
|
||||
case "unit_price":
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的 'unit_price' 值失败: %w", rawMaterialName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望原料 '%s' 的 'unit_price' 值是数字, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
|
||||
}
|
||||
default:
|
||||
// 忽略其他未知字段,但仍需读取其值以继续解析
|
||||
if _, err := decoder.Token(); err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的未知键 '%s' 的值失败: %w", rawMaterialName, key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取原料描述和价格对象的 "}"
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析原料 '%s' 的值结束符 '}' 失败", rawMaterialName)
|
||||
}
|
||||
}
|
||||
|
||||
// 校验 data 节点
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
if !dataNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'data' 字段")
|
||||
}
|
||||
if !dataNode.IsObject() {
|
||||
return errors.New("'data' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
// 重新初始化 decoder 用于 data 节点的校验
|
||||
decoder = json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return errors.New("'data' 字段解析起始符失败")
|
||||
}
|
||||
|
||||
seenRawMaterials = make(map[string]bool) // 重置 seenRawMaterials 用于 data 节点校验
|
||||
|
||||
for decoder.More() {
|
||||
// 1. 解析原料名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料名称失败: %w", err)
|
||||
}
|
||||
rawMaterialName := t.(string)
|
||||
if seenRawMaterials[rawMaterialName] {
|
||||
return fmt.Errorf("原料名称 '%s' 重复", rawMaterialName)
|
||||
}
|
||||
seenRawMaterials[rawMaterialName] = true
|
||||
|
||||
// 2. 解析该原料的营养成分对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望原料 '%s' 的值是一个JSON对象", rawMaterialName)
|
||||
}
|
||||
|
||||
seenNutrients := make(map[string]bool)
|
||||
for decoder.More() {
|
||||
// 解析营养素名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在原料 '%s' 中解析营养素名称失败: %w", rawMaterialName, err)
|
||||
}
|
||||
nutrientName := t.(string)
|
||||
if seenNutrients[nutrientName] {
|
||||
return fmt.Errorf("在原料 '%s' 中, 营养素名称 '%s' 重复", rawMaterialName, nutrientName)
|
||||
}
|
||||
seenNutrients[nutrientName] = true
|
||||
|
||||
// 解析营养素含量
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在原料 '%s' 中解析营养素 '%s' 的含量值失败: %w", rawMaterialName, nutrientName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望营养素 '%s' 的含量值是数字, 但实际得到的类型是 %T, 值为 '%v'", nutrientName, t, t)
|
||||
}
|
||||
}
|
||||
|
||||
// 读取营养成分对象的 "}"
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析原料 '%s' 的值结束符 '}' 失败", rawMaterialName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
279
internal/infra/database/seeder/nutrient_seeder.go
Normal file
279
internal/infra/database/seeder/nutrient_seeder.go
Normal file
@@ -0,0 +1,279 @@
|
||||
package seeder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// rawMaterialInfo 用于临时存储解析后的原料描述、价格和最大添加量信息。
|
||||
type rawMaterialInfo struct {
|
||||
Description string
|
||||
UnitPrice float32
|
||||
MaxRatio float32
|
||||
}
|
||||
|
||||
// SeedNutrients 先严格校验JSON源文件,然后以“有则跳过”的模式播种数据。
|
||||
func SeedNutrients(ctx context.Context, tx *gorm.DB, jsonData []byte) error {
|
||||
logger := logs.GetLogger(ctx)
|
||||
|
||||
// 检查 Nutrient 表是否为空,如果非空则跳过播种
|
||||
isEmpty, err := isTableEmpty(tx, &models.Nutrient{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 Nutrient 表是否为空失败: %w", err)
|
||||
}
|
||||
if !isEmpty {
|
||||
logger.Info("已存在原料数据, 跳过数据播种")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 严格校验JSON文件,检查内部重复键
|
||||
if err := validateAndParseNutrientJSON(jsonData); err != nil {
|
||||
return fmt.Errorf("JSON源文件校验失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 解析简介信息
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
rawMaterialInfos := make(map[string]rawMaterialInfo)
|
||||
nutrientDescriptions := make(map[string]string)
|
||||
|
||||
if descriptionsNode.Exists() {
|
||||
// 解析 raw_materials 描述、价格和最大添加量
|
||||
descriptionsNode.Get("raw_materials").ForEach(func(key, value gjson.Result) bool {
|
||||
rawMaterialInfos[key.String()] = rawMaterialInfo{
|
||||
Description: value.Get("descriptions").String(),
|
||||
UnitPrice: float32(value.Get("unit_price").Float()),
|
||||
MaxRatio: float32(value.Get("max_ratio").Float()),
|
||||
}
|
||||
return true
|
||||
})
|
||||
descriptionsNode.Get("nutrients").ForEach(func(key, value gjson.Result) bool {
|
||||
nutrientDescriptions[key.String()] = value.String()
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 将通过校验的、干净的数据写入数据库
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
dataNode.ForEach(func(rawMaterialKey, rawMaterialValue gjson.Result) bool {
|
||||
rawMaterialName := rawMaterialKey.String()
|
||||
var rawMaterial models.RawMaterial
|
||||
|
||||
// 获取原料的描述、价格和最大添加量信息
|
||||
info := rawMaterialInfos[rawMaterialName]
|
||||
|
||||
// 将 Description, ReferencePrice 和 MaxAdditionRatio 放入 Create 对象中
|
||||
err = tx.Where(models.RawMaterial{Name: rawMaterialName}).
|
||||
FirstOrCreate(&rawMaterial, models.RawMaterial{
|
||||
Name: rawMaterialName,
|
||||
Description: info.Description,
|
||||
ReferencePrice: info.UnitPrice,
|
||||
MaxAdditionRatio: info.MaxRatio,
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
|
||||
rawMaterialValue.ForEach(func(nutrientKey, nutrientValue gjson.Result) bool {
|
||||
nutrientName := nutrientKey.String()
|
||||
value := float32(nutrientValue.Float())
|
||||
|
||||
var nutrient models.Nutrient
|
||||
// 将 Description 放入 Create 对象中
|
||||
err = tx.Where(models.Nutrient{Name: nutrientName}).
|
||||
FirstOrCreate(&nutrient, models.Nutrient{
|
||||
Name: nutrientName,
|
||||
Description: nutrientDescriptions[nutrientName],
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
|
||||
linkData := models.RawMaterialNutrient{
|
||||
RawMaterialID: rawMaterial.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
}
|
||||
// 使用 FirstOrCreate 确保关联的唯一性
|
||||
err = tx.Where(linkData).FirstOrCreate(&linkData, models.RawMaterialNutrient{
|
||||
RawMaterialID: linkData.RawMaterialID,
|
||||
NutrientID: linkData.NutrientID,
|
||||
Value: value,
|
||||
}).Error
|
||||
if err != nil {
|
||||
// 返回 false 停止 ForEach 遍历
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
|
||||
return err // 返回捕获到的错误
|
||||
}
|
||||
|
||||
// validateAndParseNutrientJSON 严格校验JSON文件
|
||||
func validateAndParseNutrientJSON(jsonData []byte) error {
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
if !descriptionsNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'descriptions' 字段")
|
||||
}
|
||||
if !descriptionsNode.IsObject() {
|
||||
return errors.New("'descriptions' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
rawMaterialsNode := descriptionsNode.Get("raw_materials")
|
||||
if !rawMaterialsNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'descriptions.raw_materials' 字段")
|
||||
}
|
||||
if !rawMaterialsNode.IsObject() {
|
||||
return errors.New("'descriptions.raw_materials' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
// 使用 json.Decoder 严格校验 raw_materials 的结构
|
||||
decoder := json.NewDecoder(bytes.NewReader([]byte(rawMaterialsNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("'descriptions.raw_materials' 字段解析起始符失败: %v", err)
|
||||
}
|
||||
|
||||
seenRawMaterials := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 1. 解析原料名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料名称失败: %w", err)
|
||||
}
|
||||
rawMaterialName := t.(string)
|
||||
if seenRawMaterials[rawMaterialName] {
|
||||
return fmt.Errorf("原料名称 '%s' 重复", rawMaterialName)
|
||||
}
|
||||
seenRawMaterials[rawMaterialName] = true
|
||||
|
||||
// 2. 解析该原料的描述和价格对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望原料 '%s' 的值是一个JSON对象", rawMaterialName)
|
||||
}
|
||||
|
||||
for decoder.More() {
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 内部键失败: %w", rawMaterialName, err)
|
||||
}
|
||||
key := t.(string)
|
||||
|
||||
switch key {
|
||||
case "descriptions":
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的 'descriptions' 值失败: %w", rawMaterialName, err)
|
||||
}
|
||||
if _, ok := t.(string); !ok {
|
||||
return fmt.Errorf("期望原料 '%s' 的 'descriptions' 值是字符串, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
|
||||
}
|
||||
case "unit_price":
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的 'unit_price' 值失败: %w", rawMaterialName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望原料 '%s' 的 'unit_price' 值是数字, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
|
||||
}
|
||||
case "max_ratio":
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的 'max_ratio' 值失败: %w", rawMaterialName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望原料 '%s' 的 'max_ratio' 值是数字, 但实际得到的类型是 %T, 值为 '%v'", rawMaterialName, t, t)
|
||||
}
|
||||
default:
|
||||
// 忽略其他未知字段,但仍需读取其值以继续解析
|
||||
if _, err := decoder.Token(); err != nil {
|
||||
return fmt.Errorf("解析原料 '%s' 的未知键 '%s' 的值失败: %w", rawMaterialName, key, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 读取原料描述和价格对象的 "}"
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析原料 '%s' 的值结束符 '}' 失败", rawMaterialName)
|
||||
}
|
||||
}
|
||||
|
||||
// 校验 data 节点
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
if !dataNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'data' 字段")
|
||||
}
|
||||
if !dataNode.IsObject() {
|
||||
return errors.New("'data' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
// 重新初始化 decoder 用于 data 节点的校验
|
||||
decoder = json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return errors.New("'data' 字段解析起始符失败")
|
||||
}
|
||||
|
||||
seenRawMaterials = make(map[string]bool) // 重置 seenRawMaterials 用于 data 节点校验
|
||||
|
||||
for decoder.More() {
|
||||
// 1. 解析原料名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析原料名称失败: %w", err)
|
||||
}
|
||||
rawMaterialName := t.(string)
|
||||
if seenRawMaterials[rawMaterialName] {
|
||||
return fmt.Errorf("原料名称 '%s' 重复", rawMaterialName)
|
||||
}
|
||||
seenRawMaterials[rawMaterialName] = true
|
||||
|
||||
// 2. 解析该原料的营养成分对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望原料 '%s' 的值是一个JSON对象", rawMaterialName)
|
||||
}
|
||||
|
||||
seenNutrients := make(map[string]bool)
|
||||
for decoder.More() {
|
||||
// 解析营养素名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在原料 '%s' 中解析营养素名称失败: %w", rawMaterialName, err)
|
||||
}
|
||||
nutrientName := t.(string)
|
||||
if seenNutrients[nutrientName] {
|
||||
return fmt.Errorf("在原料 '%s' 中, 营养素名称 '%s' 重复", rawMaterialName, nutrientName)
|
||||
}
|
||||
seenNutrients[nutrientName] = true
|
||||
|
||||
// 解析营养素含量
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在原料 '%s' 中解析营养素 '%s' 的含量值失败: %w", rawMaterialName, nutrientName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望营养素 '%s' 的含量值是数字, 但实际得到的类型是 %T, 值为 '%v'", nutrientName, t, t)
|
||||
}
|
||||
}
|
||||
|
||||
// 读取营养成分对象的 "}"
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析原料 '%s' 的值结束符 '}' 失败", rawMaterialName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
package seeder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SeedPigNutrientRequirements 先严格校验JSON源文件,然后以“有则跳过”的模式播种数据。
|
||||
func SeedPigNutrientRequirements(ctx context.Context, tx *gorm.DB, jsonData []byte) error {
|
||||
logger := logs.GetLogger(ctx)
|
||||
|
||||
// 检查 PigBreed 表是否为空,如果非空则跳过播种
|
||||
isEmpty, err := isTableEmpty(tx, &models.PigBreed{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("检查 PigBreed 表是否为空失败: %w", err)
|
||||
}
|
||||
if !isEmpty {
|
||||
logger.Info("已存在猪种数据, 跳过数据播种")
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 严格校验JSON文件,检查内部重复键
|
||||
if err := validateAndParsePigNutrientRequirementJSON(jsonData); err != nil {
|
||||
return fmt.Errorf("JSON源文件校验失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 解析简介信息
|
||||
descriptionsNode := gjson.GetBytes(jsonData, "descriptions")
|
||||
pigBreedDescriptions := make(map[string]models.PigBreed)
|
||||
pigAgeStageDescriptions := make(map[string]models.PigAgeStage)
|
||||
pigTypeDescriptions := make(map[string]map[string]models.PigType)
|
||||
|
||||
if descriptionsNode.Exists() {
|
||||
// 解析 pig_breeds 描述
|
||||
descriptionsNode.Get("pig_breeds").ForEach(func(key, value gjson.Result) bool {
|
||||
var pb models.PigBreed
|
||||
pb.Name = key.String()
|
||||
pb.Description = value.Get("description").String()
|
||||
pb.ParentInfo = value.Get("parent_info").String()
|
||||
pb.AppearanceFeatures = value.Get("appearance_features").String()
|
||||
pb.BreedAdvantages = value.Get("breed_advantages").String()
|
||||
pb.BreedDisadvantages = value.Get("breed_disadvantages").String()
|
||||
pigBreedDescriptions[key.String()] = pb
|
||||
return true
|
||||
})
|
||||
|
||||
// 解析 pig_age_stages 描述
|
||||
descriptionsNode.Get("pig_age_stages").ForEach(func(key, value gjson.Result) bool {
|
||||
var pas models.PigAgeStage
|
||||
pas.Name = key.String()
|
||||
pas.Description = value.String()
|
||||
pigAgeStageDescriptions[key.String()] = pas
|
||||
return true
|
||||
})
|
||||
|
||||
// 解析 pig_breed_age_stages (PigType) 描述
|
||||
descriptionsNode.Get("pig_breed_age_stages").ForEach(func(breedKey, breedValue gjson.Result) bool {
|
||||
if _, ok := pigTypeDescriptions[breedKey.String()]; !ok {
|
||||
pigTypeDescriptions[breedKey.String()] = make(map[string]models.PigType)
|
||||
}
|
||||
breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool {
|
||||
var pt models.PigType
|
||||
pt.Description = ageStageValue.Get("description").String()
|
||||
pt.DailyFeedIntake = float32(ageStageValue.Get("daily_feed_intake").Float())
|
||||
pt.DailyGainWeight = float32(ageStageValue.Get("daily_gain_weight").Float())
|
||||
pt.MinDays = uint32(ageStageValue.Get("min_days").Uint())
|
||||
pt.MaxDays = uint32(ageStageValue.Get("max_days").Uint())
|
||||
pt.MinWeight = float32(ageStageValue.Get("min_weight").Float())
|
||||
pt.MaxWeight = float32(ageStageValue.Get("max_weight").Float())
|
||||
pigTypeDescriptions[breedKey.String()][ageStageKey.String()] = pt
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// 3. 将通过校验的、干净的数据写入数据库
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
dataNode.ForEach(func(breedKey, breedValue gjson.Result) bool {
|
||||
breedName := breedKey.String()
|
||||
var pigBreed models.PigBreed
|
||||
// 查找或创建 PigBreed
|
||||
pbDesc := pigBreedDescriptions[breedName]
|
||||
err = tx.Where(models.PigBreed{Name: breedName}).
|
||||
FirstOrCreate(&pigBreed, models.PigBreed{
|
||||
Name: breedName,
|
||||
Description: pbDesc.Description,
|
||||
ParentInfo: pbDesc.ParentInfo,
|
||||
AppearanceFeatures: pbDesc.AppearanceFeatures,
|
||||
BreedAdvantages: pbDesc.BreedAdvantages,
|
||||
BreedDisadvantages: pbDesc.BreedDisadvantages,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
breedValue.ForEach(func(ageStageKey, ageStageValue gjson.Result) bool {
|
||||
ageStageName := ageStageKey.String()
|
||||
var pigAgeStage models.PigAgeStage
|
||||
// 查找或创建 PigAgeStage
|
||||
pasDesc := pigAgeStageDescriptions[ageStageName]
|
||||
err = tx.Where(models.PigAgeStage{Name: ageStageName}).
|
||||
FirstOrCreate(&pigAgeStage, models.PigAgeStage{
|
||||
Name: ageStageName,
|
||||
Description: pasDesc.Description,
|
||||
}).Error
|
||||
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
var pigType models.PigType
|
||||
// 查找或创建 PigType
|
||||
ptDesc := pigTypeDescriptions[breedName][ageStageName]
|
||||
err = tx.Where(models.PigType{BreedID: pigBreed.ID, AgeStageID: pigAgeStage.ID}).
|
||||
FirstOrCreate(&pigType, models.PigType{
|
||||
BreedID: pigBreed.ID,
|
||||
AgeStageID: pigAgeStage.ID,
|
||||
Description: ptDesc.Description,
|
||||
DailyFeedIntake: ptDesc.DailyFeedIntake,
|
||||
DailyGainWeight: ptDesc.DailyGainWeight,
|
||||
MinDays: ptDesc.MinDays,
|
||||
MaxDays: ptDesc.MaxDays,
|
||||
MinWeight: ptDesc.MinWeight,
|
||||
MaxWeight: ptDesc.MaxWeight,
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ageStageValue.ForEach(func(nutrientKey, nutrientValue gjson.Result) bool {
|
||||
nutrientName := nutrientKey.String()
|
||||
minReq := float32(nutrientValue.Get("min_requirement").Float())
|
||||
maxReq := float32(nutrientValue.Get("max_requirement").Float())
|
||||
|
||||
var nutrient models.Nutrient
|
||||
// 查找或创建 Nutrient (这里假设 Nutrient 已经在 SeedNutrients 中处理,但为了健壮性,再次 FirstOrCreate)
|
||||
err = tx.Where(models.Nutrient{Name: nutrientName}).
|
||||
FirstOrCreate(&nutrient, models.Nutrient{
|
||||
Name: nutrientName,
|
||||
// Description 字段在 nutrient seeder 中处理,这里不设置
|
||||
}).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
linkData := models.PigNutrientRequirement{
|
||||
PigTypeID: pigType.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
MinRequirement: minReq,
|
||||
MaxRequirement: maxReq,
|
||||
}
|
||||
// 使用 FirstOrCreate 确保关联的唯一性
|
||||
err = tx.Where(models.PigNutrientRequirement{
|
||||
PigTypeID: pigType.ID,
|
||||
NutrientID: nutrient.ID,
|
||||
}).FirstOrCreate(&linkData, linkData).Error
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
return err == nil // 如果内部遍历有错误,则停止外部遍历
|
||||
})
|
||||
return err // 返回捕获到的错误
|
||||
}
|
||||
|
||||
// validateAndParsePigNutrientRequirementJSON 严格校验猪营养需求JSON文件
|
||||
func validateAndParsePigNutrientRequirementJSON(jsonData []byte) error {
|
||||
dataNode := gjson.GetBytes(jsonData, "data")
|
||||
if !dataNode.Exists() {
|
||||
return errors.New("JSON文件中缺少 'data' 字段")
|
||||
}
|
||||
if !dataNode.IsObject() {
|
||||
return errors.New("'data' 字段必须是一个JSON对象")
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(bytes.NewReader([]byte(dataNode.Raw)))
|
||||
decoder.UseNumber()
|
||||
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("'data' 字段解析起始符失败: %v", err)
|
||||
}
|
||||
|
||||
seenBreeds := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 PigBreed 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析猪品种名称失败: %w", err)
|
||||
}
|
||||
breedName := t.(string)
|
||||
if seenBreeds[breedName] {
|
||||
return fmt.Errorf("猪品种名称 '%s' 重复", breedName)
|
||||
}
|
||||
seenBreeds[breedName] = true
|
||||
|
||||
// 解析该品种的年龄阶段对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望猪品种 '%s' 的值是一个JSON对象", breedName)
|
||||
}
|
||||
|
||||
seenAgeStages := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 PigAgeStage 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在猪品种 '%s' 中解析年龄阶段名称失败: %w", breedName, err)
|
||||
}
|
||||
ageStageName := t.(string)
|
||||
if seenAgeStages[ageStageName] {
|
||||
return fmt.Errorf("在猪品种 '%s' 中, 年龄阶段名称 '%s' 重复", breedName, ageStageName)
|
||||
}
|
||||
seenAgeStages[ageStageName] = true
|
||||
|
||||
// 解析该年龄阶段的营养成分对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望年龄阶段 '%s' 的值是一个JSON对象", ageStageName)
|
||||
}
|
||||
|
||||
seenNutrients := make(map[string]bool)
|
||||
|
||||
for decoder.More() {
|
||||
// 解析 Nutrient 名称
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("在年龄阶段 '%s' 中解析营养素名称失败: %w", ageStageName, err)
|
||||
}
|
||||
nutrientName := t.(string)
|
||||
if seenNutrients[nutrientName] {
|
||||
return fmt.Errorf("在年龄阶段 '%s' 中, 营养素名称 '%s' 重复", ageStageName, nutrientName)
|
||||
}
|
||||
seenNutrients[nutrientName] = true
|
||||
|
||||
// 解析 min_requirement 和 max_requirement 对象
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('{') {
|
||||
return fmt.Errorf("期望营养素 '%s' 的值是一个JSON对象", nutrientName)
|
||||
}
|
||||
|
||||
for decoder.More() {
|
||||
t, err := decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析营养素 '%s' 的需求键失败: %w", nutrientName, err)
|
||||
}
|
||||
// key := t.(string) // 校验时不需要使用 key 的值
|
||||
|
||||
t, err = decoder.Token()
|
||||
if err != nil {
|
||||
return fmt.Errorf("解析营养素 '%s' 的需求值失败: %w", nutrientName, err)
|
||||
}
|
||||
if _, ok := t.(json.Number); !ok {
|
||||
return fmt.Errorf("期望营养素 '%s' 的需求值是数字, 但实际得到的类型是 %T, 值为 '%v'", nutrientName, t, t)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析营养素 '%s' 的值结束符 '}' 失败", nutrientName)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析年龄阶段 '%s' 的值结束符 '}' 失败", ageStageName)
|
||||
}
|
||||
}
|
||||
if t, err := decoder.Token(); err != nil || t != json.Delim('}') {
|
||||
return fmt.Errorf("解析猪品种 '%s' 的值结束符 '}' 失败", breedName)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
18
internal/infra/database/seeder/utils.go
Normal file
18
internal/infra/database/seeder/utils.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package seeder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// isTableEmpty 检查给定模型对应的数据库表是否为空。
|
||||
// 注意:此函数需要从 database 包中移动过来,或者在 seeder 包中重新定义,
|
||||
// 为了避免循环依赖,这里选择在 seeder 包中重新定义。
|
||||
func isTableEmpty(tx *gorm.DB, model interface{}) (bool, error) {
|
||||
var count int64
|
||||
if err := tx.Model(model).Count(&count).Error; err != nil {
|
||||
return false, fmt.Errorf("查询表记录数失败: %w", err)
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
@@ -21,9 +21,10 @@ const (
|
||||
// RawMaterial 代表一种原料的静态定义,是系统中的原料字典。
|
||||
type RawMaterial struct {
|
||||
Model
|
||||
Name string `gorm:"size:100;not null;comment:原料名称"`
|
||||
Description string `gorm:"size:255;comment:描述"`
|
||||
ReferencePrice float32 `gorm:"comment:参考价格(kg/元)"`
|
||||
Name string `gorm:"size:100;not null;comment:原料名称"`
|
||||
Description string `gorm:"size:255;comment:描述"`
|
||||
ReferencePrice float32 `gorm:"comment:参考价格(kg/元)"`
|
||||
MaxAdditionRatio float32 `gorm:"comment:该物质最大添加比例"`
|
||||
// RawMaterialNutrients 关联此原料的所有营养素含量信息
|
||||
RawMaterialNutrients []RawMaterialNutrient `gorm:"foreignKey:RawMaterialID"`
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ type RawMaterialRepository interface {
|
||||
DeleteRawMaterial(ctx context.Context, id uint32) error
|
||||
DeleteNutrientsByRawMaterialIDTx(ctx context.Context, db *gorm.DB, rawMaterialID uint32) error
|
||||
CreateBatchRawMaterialNutrientsTx(ctx context.Context, db *gorm.DB, nutrients []models.RawMaterialNutrient) error
|
||||
IsRawMaterialUsedInRecipes(ctx context.Context, rawMaterialID uint32) (bool, error)
|
||||
|
||||
// 库存日志相关方法
|
||||
CreateRawMaterialStockLog(ctx context.Context, log *models.RawMaterialStockLog) error
|
||||
@@ -143,9 +144,10 @@ func (r *gormRawMaterialRepository) UpdateRawMaterial(ctx context.Context, rawMa
|
||||
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateRawMaterial")
|
||||
// 使用 map 更新以避免 GORM 的零值问题,并确保只更新指定字段
|
||||
updateData := map[string]interface{}{
|
||||
"name": rawMaterial.Name,
|
||||
"description": rawMaterial.Description,
|
||||
"reference_price": rawMaterial.ReferencePrice,
|
||||
"name": rawMaterial.Name,
|
||||
"description": rawMaterial.Description,
|
||||
"reference_price": rawMaterial.ReferencePrice,
|
||||
"max_addition_ratio": rawMaterial.MaxAdditionRatio,
|
||||
}
|
||||
result := r.db.WithContext(repoCtx).Model(&models.RawMaterial{}).Where("id = ?", rawMaterial.ID).Updates(updateData)
|
||||
if result.Error != nil {
|
||||
@@ -329,3 +331,16 @@ func (r *gormRawMaterialRepository) ListStockLogs(ctx context.Context, opts Stoc
|
||||
|
||||
return logs, total, nil
|
||||
}
|
||||
|
||||
// IsRawMaterialUsedInRecipes 检查原料是否被任何配方使用
|
||||
func (r *gormRawMaterialRepository) IsRawMaterialUsedInRecipes(ctx context.Context, rawMaterialID uint32) (bool, error) {
|
||||
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsRawMaterialUsedInRecipes")
|
||||
var count int64
|
||||
err := r.db.WithContext(repoCtx).Model(&models.RecipeIngredient{}).
|
||||
Where("raw_material_id = ?", rawMaterialID).
|
||||
Count(&count).Error
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("查询原料是否被配方使用失败: %w", err)
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ internal/domain/recipe/pig_breed_service.go
|
||||
internal/domain/recipe/pig_type_service.go
|
||||
internal/domain/recipe/raw_material_service.go
|
||||
internal/domain/recipe/recipe_core_service.go
|
||||
internal/domain/recipe/recipe_generate_manager.go
|
||||
internal/domain/recipe/recipe_service.go
|
||||
internal/domain/task/alarm_notification_task.go
|
||||
internal/domain/task/area_threshold_check_task.go
|
||||
|
||||
Reference in New Issue
Block a user