From ec3e3e48ffeca3f51be99320865c919e4af80a89 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 27 Oct 2025 14:21:11 +0800 Subject: [PATCH 01/25] [FIX] supported countries --- spp_demo_common/data/res_country.xml | 704 +++++++++++++-------------- 1 file changed, 352 insertions(+), 352 deletions(-) diff --git a/spp_demo_common/data/res_country.xml b/spp_demo_common/data/res_country.xml index 1d50c3b14..3e856bb7b 100644 --- a/spp_demo_common/data/res_country.xml +++ b/spp_demo_common/data/res_country.xml @@ -4,32 +4,32 @@ 38.4911 60.5284 74.8899 - fa_IR - True + en_US + False 39.6449 42.6611 19.1246 21.0574 - sq_AL - True + en_US + False 18.9681 37.0937 -8.6676 11.9973 - fr_FR - True + en_US + False -14.3757 -11.0496 -170.8496 -168.1433 - en - True + en_US + False 42.4284 @@ -44,16 +44,16 @@ -4.3881 11.6792 24.0821 - pt_BR - True + en_US + False 18.1600 18.2766 -63.1625 -62.9625 - en - True + en_US + False -90.0000 @@ -68,8 +68,8 @@ 17.7290 -61.9066 -61.6730 - en - True + en_US + False -55.0613 @@ -92,16 +92,16 @@ 12.6300 -70.0648 -69.8762 - nl_BE - True + en_US + False -43.6346 -10.6682 113.3389 153.5695 - en_AU - True + en_US + False 46.3723 @@ -124,16 +124,16 @@ 27.0409 -78.0982 -72.6956 - en - True + en_US + False 25.7963 26.3469 50.3578 50.8037 - ar_BH - True + en_US + False 20.7430 @@ -148,8 +148,8 @@ 13.3365 -59.6539 -59.4170 - en - True + en_US + False 51.2627 @@ -180,16 +180,16 @@ 12.4092 0.7723 3.7976 - fr_BE - True + en_US + False 32.2460 32.3961 -64.8877 -64.6512 - en - True + en_US + False 26.7025 @@ -212,32 +212,32 @@ 17.6350 -68.4200 -62.9830 - en - True + en_US + False 42.5613 45.2766 15.7280 19.6237 - bs_BA - True + en_US + False -26.9075 -17.7781 19.9986 29.3753 - en - True + en_US + False -54.4200 -54.4000 3.3000 3.4000 - no_NO - True + en_US + False -33.7500 @@ -252,8 +252,8 @@ -5.2000 71.2600 72.6000 - en - True + en_US + False 4.0025 @@ -268,24 +268,24 @@ 44.2349 22.3571 28.6117 - bg_BG - True + en_US + False 9.4105 15.0828 -5.5136 2.4085 - fr_BE - True + en_US + False -4.4693 -2.3097 29.0249 30.8496 - fr_BE - True + en_US + False 10.4150 @@ -300,48 +300,48 @@ 13.0833 8.4886 16.1919 - en - True + en_US + False 41.6766 83.1139 -141.0 -52.6481 - en_CA - True + en_US + False 14.8030 17.2000 -25.3600 -22.6700 - pt_BR - True + en_US + False 19.2630 19.7500 -81.4100 -79.7500 - en - True + en_US + False 2.2200 11.0000 14.4150 27.4580 - fr_BE - True + en_US + False 7.4411 23.4521 13.4735 24.0000 - ar_AA - True + en_US + False -55.9830 @@ -364,16 +364,16 @@ -10.4100 105.6300 105.7300 - en - True + en_US + False -12.2100 -11.8400 96.8200 96.9300 - en - True + en_US + False -4.2276 @@ -388,32 +388,32 @@ -11.3610 43.2250 44.5350 - ar_AA - True + en_US + False -5.0278 -1.4000 11.0048 18.6500 - fr_BE - True + en_US + False -21.9440 -10.0200 -161.0930 -157.3120 - en - True + en_US + False 8.0320 11.2170 -85.9500 -82.5462 - es - True + en_US + False 42.3903 @@ -428,24 +428,24 @@ 23.2260 -84.9570 -74.1310 - es - True + en_US + False 12.0320 12.3840 -69.1580 -68.7300 - en - True + en_US + False 34.5720 35.1730 32.2720 34.5750 - el_CY - True + en_US + False 48.5510 @@ -460,16 +460,16 @@ 10.7350 -8.6010 -2.4930 - fr_BE - True + en_US + False -13.4590 5.3860 12.0390 31.3050 - fr_BE - True + en_US + False 54.5590 @@ -484,80 +484,80 @@ 12.7130 41.7710 43.4920 - ar_AA - True + en_US + False 15.2060 15.6330 -61.4840 -61.2460 - en - True + en_US + False 17.5410 19.9320 -71.9450 -68.3220 - es - True + en_US + False -4.9591 1.3800 -81.0780 -75.2330 - es - True + en_US + False 22.0000 31.6700 25.0000 35.0000 - ar_EG - True + en_US + False 13.1630 14.4500 -90.0950 -87.6900 - es - True + en_US + False -1.4670 3.7790 5.4170 11.3330 - fr_BE - True + en_US + False 12.3600 18.0000 36.4380 43.1080 - ar_AA - True + en_US + False 57.5090 59.9380 21.8330 28.2100 - et_EE - True + en_US + False -27.3175 -25.7180 30.7900 32.1340 - en - True + en_US + False 3.4221 @@ -572,24 +572,24 @@ -51.2660 -61.3600 -57.7500 - en - True + en_US + False 61.3910 62.3940 -7.6880 -6.2560 - da_DK - True + en_US + False -18.2870 -16.0200 177.1290 -178.4500 - en - True + en_US + False 59.8080 @@ -612,40 +612,40 @@ 5.7500 -54.5390 -51.6340 - fr_BE - True + en_US + False -27.6530 -7.9070 -154.7670 -134.9470 - fr_BE - True + en_US + False -49.7200 -37.8000 39.6000 77.6000 - fr_BE - True + en_US + False -3.9788 2.3181 8.6979 14.5396 - fr_BE - True + en_US + False 13.0620 13.7970 -16.8240 -13.7920 - en - True + en_US + False 41.0550 @@ -668,16 +668,16 @@ 11.1740 -3.2600 1.1990 - en - True + en_US + False 36.1070 36.1550 -5.3660 -5.3380 - en - True + en_US + False 34.8020 @@ -700,16 +700,16 @@ 12.3100 -61.7800 -61.3700 - en - True + en_US + False 15.8320 16.5140 -61.8090 -61.0000 - fr_BE - True + en_US + False 13.2340 @@ -724,80 +724,80 @@ 17.8160 -92.2460 -88.2250 - es - True + en_US + False 49.3970 49.5090 -2.6750 -2.5010 - en - True + en_US + False 7.1930 12.5860 -15.1320 -7.6410 - fr_BE - True + en_US + False 11.7780 12.6860 -16.7170 -13.6360 - pt_BR - True + en_US + False 1.1850 8.5560 -61.3960 -56.4810 - en - True + en_US + False 18.0220 19.9330 -74.4800 -71.6240 - fr_BE - True + en_US + False -53.1000 -52.9000 72.6000 73.5000 - en - True + en_US + False 41.9000 41.9030 12.4450 12.4580 - it_CH - True + en_US + False 12.9800 16.5100 -89.3500 -83.1500 - es - True + en_US + False 22.1530 22.5610 113.8370 114.4320 - en - True + en_US + False 45.7370 @@ -812,8 +812,8 @@ 66.5360 -24.5460 -13.4950 - is_IS - True + en_US + False 6.5546 @@ -844,24 +844,24 @@ 37.3850 38.7930 48.6340 - ar_AA - True + en_US + False 51.4450 55.3820 -10.4800 -5.3400 - en_IE - True + en_US + False 54.0530 54.4170 -4.7100 -4.3100 - en - True + en_US + False 29.4900 @@ -884,8 +884,8 @@ 18.5240 -78.3660 -76.1870 - en - True + en_US + False 24.3963 @@ -900,40 +900,40 @@ 49.2620 -2.2540 -2.0110 - en - True + en_US + False 29.1860 33.3750 34.9590 39.1960 - ar_JO - True + en_US + False 40.5680 55.4410 46.4910 87.3150 - ru_RU - True + en_US + False -4.6796 4.6200 33.9090 41.8990 - en_KE - True + en_US + False 59.9060 60.4880 19.5170 21.0110 - sv_SE - True + en_US + False 39.1800 @@ -956,72 +956,72 @@ 58.0850 20.9680 28.2410 - lv_LV - True + en_US + False 33.0550 34.6920 35.1260 36.6110 - ar_AA - True + en_US + False -30.6750 -28.5700 27.0280 29.4550 - en - True + en_US + False 4.3530 8.5510 -11.4950 -7.3690 - en - True + en_US + False 19.5000 33.2360 9.3910 25.1500 - ar_AA - True + en_US + False 47.0480 47.2700 9.4710 9.6350 - de_LI - True + en_US + False 53.8960 56.4500 21.0550 26.8350 - lt_LT - True + en_US + False 49.4470 50.1820 5.7350 6.5280 - de_LU - True + en_US + False 22.1110 22.2170 113.5280 113.6050 - pt_BR - True + en_US + False 40.8530 @@ -1036,24 +1036,24 @@ -11.9510 43.2200 50.4760 - fr_BE - True + en_US + False -17.1290 -9.3670 32.6700 35.9180 - en - True + en_US + False 0.8530 7.3630 99.6400 119.2670 - en - True + en_US + False -0.6740 @@ -1068,56 +1068,56 @@ 25.0000 -12.2400 4.2440 - fr_BE - True + en_US + False 35.7990 36.0820 14.1830 14.5670 - en - True + en_US + False 4.5830 14.6040 160.8190 172.0290 - en - True + en_US + False 14.3920 14.8780 -61.2290 -60.8150 - fr_BE - True + en_US + False 14.7210 27.2980 -17.0680 -4.8330 - ar_AA - True + en_US + False -20.5250 -19.9820 57.3070 63.5000 - en - True + en_US + False -13.0000 -12.6360 45.0180 45.2990 - fr_BE - True + en_US + False 14.5388 @@ -1132,24 +1132,24 @@ 9.6130 138.0610 163.0410 - en - True + en_US + False 45.4670 48.4910 26.6180 30.1630 - ro_RO - True + en_US + False 43.7240 43.7510 7.4090 7.4390 - fr_BE - True + en_US + False 41.5810 @@ -1172,24 +1172,24 @@ 16.8240 -62.2420 -62.1440 - en_MS - True + en_US + False 21.4200 35.8970 -17.0200 -1.1240 - ar_AA - True + en_US + False -26.8600 -10.4710 30.2150 40.8490 - pt_BR - True + en_US + False 9.7840 @@ -1212,8 +1212,8 @@ -0.5020 166.9090 166.9580 - en - True + en_US + False 26.3470 @@ -1236,8 +1236,8 @@ -19.6260 163.5640 167.2770 - fr_BE - True + en_US + False -47.2860 @@ -1252,40 +1252,40 @@ 15.0330 -87.6680 -83.1470 - es - True + en_US + False 11.6930 23.5170 0.1680 15.9960 - fr_BE - True + en_US + False 4.2720 13.8920 2.6760 14.6770 - en_NG - True + en_US + False -19.0830 -18.9510 -169.9300 -169.7790 - en - True + en_US + False -29.1000 -28.9830 167.9120 167.9970 - en - True + en_US + False 14.1100 @@ -1308,48 +1308,48 @@ 26.3950 52.0000 60.3030 - ar_AA - True + en_US + False 23.6345 37.0841 60.8720 77.8375 - en_PK - True + en_US + False 2.7530 8.0950 131.1140 134.6450 - en - True + en_US + False 31.2200 32.5520 34.2080 35.5730 - ar_PS - True + en_US + False 7.2020 9.6110 -83.0510 -77.1740 - es - True + en_US + False -11.6340 -1.3460 140.8420 156.0190 - en - True + en_US + False -27.6060 @@ -1372,16 +1372,16 @@ 21.3210 116.9540 126.6040 - en_PH - True + en_US + False -25.0800 -24.3300 -130.1100 -128.3300 - en - True + en_US + False 49.0020 @@ -1404,24 +1404,24 @@ 18.5150 -67.2710 -65.5890 - en - True + en_US + False 24.3960 26.1600 50.7500 51.6130 - ar_AA - True + en_US + False -21.3890 -20.8710 55.2160 55.8360 - fr_BE - True + en_US + False 43.6180 @@ -1444,104 +1444,104 @@ -1.0470 28.8610 30.8990 - en - True + en_US + False 17.8960 17.9330 -62.8700 -62.8050 - fr_BE - True + en_US + False -16.0200 -5.6500 -14.4200 -5.6400 - en - True + en_US + False 17.2200 17.4200 -62.8600 -62.5500 - en - True + en_US + False 13.7120 14.1000 -61.0700 -60.8900 - en - True + en_US + False 18.0400 18.1300 -63.1600 -62.9900 - fr_BE - True + en_US + False 46.7800 47.1000 -56.4000 -56.2000 - fr_BE - True + en_US + False 12.5830 13.3830 -61.4800 -61.1300 - en - True + en_US + False -14.0520 -13.2380 -172.7840 -171.4440 - en - True + en_US + False 43.8930 43.9860 12.4030 12.5160 - it_CH - True + en_US + False -0.0130 1.7010 6.4700 7.4650 - pt_BR - True + en_US + False 16.3750 32.1540 34.4950 55.6660 - ar_SA - True + en_US + False 12.3070 16.6920 -17.5360 -11.3670 - fr_BE - True + en_US + False 42.2320 @@ -1564,24 +1564,24 @@ 10.0470 -13.3070 -10.2840 - en - True + en_US + False 1.1300 1.4700 103.6200 104.0100 - en - True + en_US + False 18.0200 18.0700 -63.1300 -62.9900 - en - True + en_US + False 47.7310 @@ -1604,40 +1604,40 @@ -6.5990 155.3420 168.0450 - en - True + en_US + False -1.6830 11.9780 40.9890 51.6170 - ar_AA - True + en_US + False -34.8330 -22.1260 16.4510 32.8910 - en_ZA - True + en_US + False -54.7500 -53.7500 -38.0000 -35.5000 - en - True + en_US + False 3.4880 12.2360 24.1330 35.8920 - en - True + en_US + False 27.6370 @@ -1660,24 +1660,24 @@ 22.2230 21.8140 38.6140 - ar_AA - True + en_US + False 1.8310 6.0110 -58.0700 -53.9860 - nl_BE - True + en_US + False 76.0000 80.0000 10.0000 35.0000 - no_NO - True + en_US + False 55.3370 @@ -1700,8 +1700,8 @@ 37.3190 35.7000 42.0000 - ar_AA - True + en_US + False 21.9020 @@ -1716,16 +1716,16 @@ 41.0450 67.3420 75.1530 - ru_RU - True + en_US + False -11.7450 -0.9850 29.3210 40.4490 - en - True + en_US + False 5.6120 @@ -1740,88 +1740,88 @@ -8.1260 124.0400 127.3430 - pt_BR - True + en_US + False 6.1110 11.1390 -0.1470 1.7790 - fr_BE - True + en_US + False -9.5000 -8.5000 -172.2330 -171.8330 - en - True + en_US + False -21.4630 -15.5590 -175.6340 -173.7630 - en - True + en_US + False 10.0420 11.3420 -61.9290 -60.8950 - en - True + en_US + False 30.2300 37.5340 7.5240 11.5830 - ar_AA - True + en_US + False 35.8080 42.1070 25.6680 44.7930 - tr_TR - True + en_US + False 35.1290 42.7970 52.4440 66.6870 - ru_RU - True + en_US + False 21.4990 21.9570 -72.4790 -71.6270 - en - True + en_US + False -8.5420 -5.6420 179.0830 179.3640 - en - True + en_US + False -1.4770 4.2340 29.5730 35.0000 - en - True + en_US + False 44.3860 @@ -1836,8 +1836,8 @@ 26.0760 51.5830 56.3960 - ar_AE - True + en_US + False 24.3963 @@ -1852,24 +1852,24 @@ 28.2190 -177.3830 166.6520 - en - True + en_US + False -34.9780 -30.0850 -58.4420 -53.2090 - es - True + en_US + False 37.1820 45.5900 55.9980 73.1330 - ru_RU - True + en_US + False -16.5970 @@ -1884,8 +1884,8 @@ 12.2010 -73.3520 -59.8050 - es - True + en_US + False 8.1950 @@ -1900,24 +1900,24 @@ 18.7500 -64.7000 -64.2680 - en - True + en_US + False 17.6230 18.4640 -65.0850 -64.5650 - en - True + en_US + False -14.3140 -13.0820 -178.1870 -176.1590 - fr_BE - True + en_US + False 20.7640 @@ -1932,16 +1932,16 @@ 19.0000 42.5390 54.5320 - ar_AA - True + en_US + False -18.0760 -8.2380 21.9990 33.7010 - en - True + en_US + False -22.4210 @@ -1956,24 +1956,24 @@ 4.7231 -174.543 -157.312 - en - True + en_US + False 41.856 43.269 19.983 21.801 - sq_AL - True + en_US + False 28.524 30.096 46.553 48.430 - ar_AA - True + en_US + False 33.115 @@ -1988,7 +1988,7 @@ 60.860 -8.649 1.768 - en_US - False + en_GB + True From 761c26bb30ab0cc387729ae852e21896c5dd4c03 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 27 Oct 2025 14:55:54 +0800 Subject: [PATCH 02/25] [FIX] name of groups --- spp_base_farmer_registry_demo/models/generate_demo_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 1cc91acf0..5296c90f7 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -147,9 +147,11 @@ def get_group_vals(self, fake): ) # Specific Head Farmer details on Group + farmer_family_name = fake.last_name() group_vals.update( { - "farmer_family_name": fake.last_name(), + "farmer_family_name": farmer_family_name, + "name": farmer_family_name, "farmer_given_name": fake.first_name(), "farmer_addtnl_name": fake.first_name() if random.choice([True, False]) else None, "farmer_mobile_tel": self.generate_phone_number(fake), From f117f7b04809efe66a74c777d36e18819a9f07bc Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 27 Oct 2025 15:24:58 +0800 Subject: [PATCH 03/25] [FIX] view context --- spp_base_farmer_registry_demo/__manifest__.py | 1 + .../views/generator_view.xml | 20 +++++++++++++++++++ .../views/demo_data_generator_view.xml | 14 +++++++++++-- 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 spp_base_farmer_registry_demo/views/generator_view.xml diff --git a/spp_base_farmer_registry_demo/__manifest__.py b/spp_base_farmer_registry_demo/__manifest__.py index aa90d11a3..2c60a7253 100644 --- a/spp_base_farmer_registry_demo/__manifest__.py +++ b/spp_base_farmer_registry_demo/__manifest__.py @@ -37,6 +37,7 @@ "data/feed_items_data.xml", "views/group_view.xml", "views/individual_view.xml", + "views/generator_view.xml", ], "assets": {}, "demo": [], diff --git a/spp_base_farmer_registry_demo/views/generator_view.xml b/spp_base_farmer_registry_demo/views/generator_view.xml new file mode 100644 index 000000000..2bd1b5a75 --- /dev/null +++ b/spp_base_farmer_registry_demo/views/generator_view.xml @@ -0,0 +1,20 @@ + + + + view_farm_groups_form + res.partner + + + + {'form_view_ref': 'spp_base_farmer_registry.view_farm_groups_form'} + + + {'form_view_ref': 'spp_base_farmer_registry.view_individuals_form'} + + + + diff --git a/spp_demo_common/views/demo_data_generator_view.xml b/spp_demo_common/views/demo_data_generator_view.xml index 73459b2f8..52868685d 100644 --- a/spp_demo_common/views/demo_data_generator_view.xml +++ b/spp_demo_common/views/demo_data_generator_view.xml @@ -209,7 +209,12 @@ - + @@ -221,7 +226,12 @@ - + From 6c03bc2b55ac9161df5914b50548fdcb9c4954d8 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 27 Oct 2025 15:27:03 +0800 Subject: [PATCH 04/25] [FIX] view context --- spp_base_farmer_registry_demo/views/generator_view.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spp_base_farmer_registry_demo/views/generator_view.xml b/spp_base_farmer_registry_demo/views/generator_view.xml index 2bd1b5a75..97657ffc1 100644 --- a/spp_base_farmer_registry_demo/views/generator_view.xml +++ b/spp_base_farmer_registry_demo/views/generator_view.xml @@ -1,9 +1,9 @@ - - view_farm_groups_form - res.partner - + + view_demo_data_generator_farmer_registry_form + spp.demo.data.generator + Date: Mon, 27 Oct 2025 15:29:24 +0800 Subject: [PATCH 05/25] [FIX] view context --- spp_base_farmer_registry_demo/__manifest__.py | 1 - .../views/generator_view.xml | 20 ------------------- 2 files changed, 21 deletions(-) delete mode 100644 spp_base_farmer_registry_demo/views/generator_view.xml diff --git a/spp_base_farmer_registry_demo/__manifest__.py b/spp_base_farmer_registry_demo/__manifest__.py index 2c60a7253..aa90d11a3 100644 --- a/spp_base_farmer_registry_demo/__manifest__.py +++ b/spp_base_farmer_registry_demo/__manifest__.py @@ -37,7 +37,6 @@ "data/feed_items_data.xml", "views/group_view.xml", "views/individual_view.xml", - "views/generator_view.xml", ], "assets": {}, "demo": [], diff --git a/spp_base_farmer_registry_demo/views/generator_view.xml b/spp_base_farmer_registry_demo/views/generator_view.xml deleted file mode 100644 index 97657ffc1..000000000 --- a/spp_base_farmer_registry_demo/views/generator_view.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - view_demo_data_generator_farmer_registry_form - spp.demo.data.generator - - - - {'form_view_ref': 'spp_base_farmer_registry.view_farm_groups_form'} - - - {'form_view_ref': 'spp_base_farmer_registry.view_individuals_form'} - - - - From 7a987a040460a366789e209bf91b52d811f5b721 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 10:07:09 +0800 Subject: [PATCH 06/25] [IMP] generation and add failure logs --- spp_demo_common/models/__init__.py | 1 + .../models/demo_data_generation_log.py | 56 +++++++ spp_demo_common/models/demo_data_generator.py | 155 +++++++++++++++--- spp_demo_common/security/ir.model.access.csv | 3 + .../views/demo_data_generator_view.xml | 45 +++++ 5 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 spp_demo_common/models/demo_data_generation_log.py diff --git a/spp_demo_common/models/__init__.py b/spp_demo_common/models/__init__.py index 69a9f939f..79674d1d9 100644 --- a/spp_demo_common/models/__init__.py +++ b/spp_demo_common/models/__init__.py @@ -2,6 +2,7 @@ from . import res_partner from . import res_country from . import demo_data_generator +from . import demo_data_generation_log from . import data_export from . import data_export_raw from . import data_import diff --git a/spp_demo_common/models/demo_data_generation_log.py b/spp_demo_common/models/demo_data_generation_log.py new file mode 100644 index 000000000..94fe8c5a6 --- /dev/null +++ b/spp_demo_common/models/demo_data_generation_log.py @@ -0,0 +1,56 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. + +from odoo import fields, models + + +class SPPDemoDataGenerationLog(models.Model): + _name = "spp.demo.data.generation.log" + _description = "Demo Data Generation Log" + _order = "create_date desc" + + demo_data_generator_id = fields.Many2one( + "spp.demo.data.generator", + string="Demo Data Generator", + required=True, + ondelete="cascade", + index=True, + ) + registrant_id = fields.Many2one( + "res.partner", + string="Registrant", + ondelete="set null", + help="The registrant for which the generation failed", + ) + registrant_type = fields.Selection( + [("group", "Group"), ("individual", "Individual")], + string="Registrant Type", + required=True, + ) + operation_type = fields.Selection( + [ + ("id_generation", "ID Generation"), + ("phone_generation", "Phone Number Generation"), + ("bank_account", "Bank Account"), + ("gps_coordinates", "GPS Coordinates"), + ("other", "Other"), + ], + string="Operation Type", + required=True, + ) + failure_reason = fields.Selection( + [ + ("max_attempts_reached", "Maximum Attempts Reached"), + ("regex_validation_failed", "Regex Validation Failed"), + ("generation_error", "Generation Error"), + ("data_validation_failed", "Data Validation Failed"), + ("other", "Other"), + ], + string="Failure Reason", + required=True, + ) + error_message = fields.Text(string="Error Message") + attempts = fields.Integer(string="Number of Attempts", default=0) + validation_regex = fields.Char(string="Validation Regex") + generated_value = fields.Char(string="Last Generated Value") + exception_details = fields.Text(string="Exception Details") + create_date = fields.Datetime(string="Date Created", readonly=True) diff --git a/spp_demo_common/models/demo_data_generator.py b/spp_demo_common/models/demo_data_generator.py index 866aedfb5..b3b07fb84 100644 --- a/spp_demo_common/models/demo_data_generator.py +++ b/spp_demo_common/models/demo_data_generator.py @@ -18,6 +18,9 @@ class SPPDemoDataGenerator(models.Model): _name = "spp.demo.data.generator" _description = "SPP Demo Data Generator" + # Cache for compiled regex patterns to avoid recompilation + _regex_cache = {} + def _default_number_of_groups(self): default_settings = self.env["ir.config_parameter"].sudo() return int(default_settings.get_param("spp_demo_common.number_of_groups", 10)) @@ -97,6 +100,20 @@ def _default_queue_job_minimum_size(self): string="Generated Individuals", readonly=True, ) + generation_log_ids = fields.One2many( + "spp.demo.data.generation.log", + "demo_data_generator_id", + string="Generation Logs", + readonly=True, + ) + generation_log_count = fields.Integer( + string="Failed Generations", + compute="_compute_generation_log_count", + ) + + def _compute_generation_log_count(self): + for rec in self: + rec.generation_log_count = len(rec.generation_log_ids) def generate_demo_data(self): self.ensure_one() @@ -297,6 +314,34 @@ def get_gender(self, gender): def get_random_date(self, fake, datefrom, dateto): return fake.date_between_dates(date_start=datefrom, date_end=dateto) + def _log_generation_failure( + self, + registrant, + operation_type, + failure_reason, + error_message, + attempts=0, + validation_regex=None, + generated_value=None, + exception_details=None, + ): + """ + Log a generation failure to the database for tracking and debugging. + """ + log_vals = { + "demo_data_generator_id": self.id, + "registrant_id": registrant.id if registrant else None, + "registrant_type": "group" if registrant and registrant.is_group else "individual", + "operation_type": operation_type, + "failure_reason": failure_reason, + "error_message": error_message, + "attempts": attempts, + "validation_regex": validation_regex, + "generated_value": generated_value, + "exception_details": exception_details, + } + self.env["spp.demo.data.generation.log"].create(log_vals) + def get_id_type(self, target_type): if self.id_type_ids: id_type = self.env["spp.demo.data.id.types"].search( @@ -449,17 +494,55 @@ def create_ids(self, fake, registrant): # Generate ID number based on regex or fallback to default if id_validation_regex: try: - while True: - id_number = self.generate_id_from_regex(id_validation_regex) - - # Validate generated ID against the regex - if not re.match(id_validation_regex, id_number): - continue - - break - except Exception: - # Fallback if generation failed - id_number = fake.bothify(text="??######") + # Use cached compiled regex for performance + if id_validation_regex not in self._regex_cache: + self._regex_cache[id_validation_regex] = re.compile(id_validation_regex) + compiled_regex = self._regex_cache[id_validation_regex] + + max_attempts = 10 # Prevent infinite loops + attempt = 0 + id_number = None + last_generated = None + + while attempt < max_attempts: + last_generated = self.generate_id_from_regex(id_validation_regex) + + # Validate generated ID against the compiled regex + if compiled_regex.match(last_generated): + id_number = last_generated + break + attempt += 1 + + if id_number is None: + # If we exhausted attempts, log to database and skip creation + self._log_generation_failure( + registrant=registrant, + operation_type="id_generation", + failure_reason="max_attempts_reached", + error_message=f"Failed to generate valid ID after {max_attempts} attempts for regex: {id_validation_regex}", + attempts=max_attempts, + validation_regex=id_validation_regex, + generated_value=last_generated, + ) + _logger.warning( + f"Failed to generate valid ID after {max_attempts} attempts " + f"for regex: {id_validation_regex}. No record created." + ) + return + except Exception as e: + # Log exception to database and skip creation + self._log_generation_failure( + registrant=registrant, + operation_type="id_generation", + failure_reason="generation_error", + error_message=f"Error generating ID from regex {id_validation_regex}", + attempts=attempt if "attempt" in locals() else 0, + validation_regex=id_validation_regex, + generated_value=last_generated if "last_generated" in locals() else None, + exception_details=str(e), + ) + _logger.error(f"Error generating ID from regex {id_validation_regex}: {e}. No record created.") + return else: # No regex provided, use default generation id_number = fake.bothify(text="??######") @@ -510,7 +593,10 @@ def create_bank_accounts(self, fake, registrant): self.env["res.partner.bank"].create(bank_account_vals) def generate_phone_number(self, fake): - while True: + max_attempts = 10 # Prevent infinite loops + attempt = 0 + + while attempt < max_attempts: try: phone_number = fake.phone_number() except Exception: @@ -524,24 +610,53 @@ def generate_phone_number(self, fake): if cleaned.startswith("+"): cleaned = cleaned[1:] if cleaned.isdigit(): - break - return cleaned + return cleaned + attempt += 1 + + # Return None if we couldn't generate a valid phone number + _logger.warning("Failed to generate valid phone number after %s attempts. Returning None.", max_attempts) + return None def create_phone_numbers(self, fake, registrant): num_phone_numbers = random.randint(1, 5) + phone_vals_list = [] + failed_count = 0 + for _ in range(num_phone_numbers): phone_number = self.generate_phone_number(fake) + + if phone_number is None: + # Log failure to database + failed_count += 1 + self._log_generation_failure( + registrant=registrant, + operation_type="phone_generation", + failure_reason="max_attempts_reached", + error_message="Failed to generate valid phone number after 10 attempts", + attempts=10, + ) + continue + date_collected = self.get_random_date( fake, datefrom=registrant.registration_date, dateto=fields.Date.today(), ) - phone_vals = { - "partner_id": registrant.id, - "phone_no": phone_number, - "date_collected": date_collected, - } - self.env["g2p.phone.number"].create(phone_vals) + phone_vals_list.append( + { + "partner_id": registrant.id, + "phone_no": phone_number, + "date_collected": date_collected, + } + ) + + # Batch create all phone numbers for this registrant + if phone_vals_list: + self.env["g2p.phone.number"].create(phone_vals_list) + + if failed_count > 0: + _logger.warning(f"Failed to generate {failed_count} phone number(s) for registrant {registrant.name}") + registrant.phone_number_ids_change() def create_gps_coordinates(self, fake, registrant): diff --git a/spp_demo_common/security/ir.model.access.csv b/spp_demo_common/security/ir.model.access.csv index 83745dc1f..02c02a529 100644 --- a/spp_demo_common/security/ir.model.access.csv +++ b/spp_demo_common/security/ir.model.access.csv @@ -1,6 +1,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink demo_data_generator_read_registry_access,Demo Data Generator Read Access,spp_demo_common.model_spp_demo_data_generator,spp_base_common.read_registry,1,0,0,0 +demo_data_generation_log_read_access,Demo Data Generation Log Read Access,spp_demo_common.model_spp_demo_data_generation_log,spp_base_common.read_registry,1,0,0,0 data_exporter_read_access,Data Exporter Read Access,spp_demo_common.model_spp_data_exporter,spp_base_common.read_registry,1,0,0,0 data_exporter_templates_read_access,Data Exporter Templates Read Access,spp_demo_common.model_spp_data_exporter_templates,spp_base_common.read_registry,1,0,0,0 data_exporter_raw_read_access,Data Exporter Raw Read Access,spp_demo_common.model_spp_data_exporter_raw,spp_base_common.read_registry,1,0,0,0 @@ -13,6 +14,7 @@ apps_wizard_read_access,Apps Wizard Read Access,spp_demo_common.model_spp_apps_w missing_module_read_access,Apps Missing Modules Read Access,spp_demo_common.model_spp_missing_module,spp_base_common.read_registry,1,0,0,0 demo_data_generator_write_registry_access,Demo Data Generator Write Access,spp_demo_common.model_spp_demo_data_generator,spp_base_common.write_registry,1,1,0,0 +demo_data_generation_log_write_access,Demo Data Generation Log Write Access,spp_demo_common.model_spp_demo_data_generation_log,spp_base_common.write_registry,1,1,0,0 data_exporter_write_access,Data Exporter Write Access,spp_demo_common.model_spp_data_exporter,spp_base_common.write_registry,1,1,0,0 data_exporter_templates_write_access,Data Exporter Templates Write Access,spp_demo_common.model_spp_data_exporter_templates,spp_base_common.write_registry,1,1,0,0 data_exporter_raw_write_access,Data Exporter Raw Write Access,spp_demo_common.model_spp_data_exporter_raw,spp_base_common.write_registry,1,1,0,0 @@ -25,6 +27,7 @@ apps_wizard_write_access,Apps Wizard Write Access,spp_demo_common.model_spp_apps missing_module_write_access,Apps Missing Modules Write Access,spp_demo_common.model_spp_missing_module,spp_base_common.write_registry,1,1,0,0 demo_data_generator_create_registry_access,Demo Data Generator Create Access,spp_demo_common.model_spp_demo_data_generator,spp_base_common.create_registry,1,0,1,0 +demo_data_generation_log_create_access,Demo Data Generation Log Create Access,spp_demo_common.model_spp_demo_data_generation_log,spp_base_common.create_registry,1,0,1,0 data_exporter_create_access,Data Exporter Create Access,spp_demo_common.model_spp_data_exporter,spp_base_common.create_registry,1,0,1,0 data_exporter_templates_create_access,Data Exporter Templates Create Access,spp_demo_common.model_spp_data_exporter_templates,spp_base_common.create_registry,1,0,1,0 data_exporter_raw_create_access,Data Exporter Raw Create Access,spp_demo_common.model_spp_data_exporter_raw,spp_base_common.create_registry,1,0,1,0 diff --git a/spp_demo_common/views/demo_data_generator_view.xml b/spp_demo_common/views/demo_data_generator_view.xml index 52868685d..d61178513 100644 --- a/spp_demo_common/views/demo_data_generator_view.xml +++ b/spp_demo_common/views/demo_data_generator_view.xml @@ -257,6 +257,51 @@ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+
+
From cca0beba3b631ff2a520daf212971385d26693d8 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 10:20:04 +0800 Subject: [PATCH 07/25] [IMP] generation and add failure logs --- spp_demo_common/models/demo_data_generator.py | 51 ++++++++++++++++--- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/spp_demo_common/models/demo_data_generator.py b/spp_demo_common/models/demo_data_generator.py index b3b07fb84..851783579 100644 --- a/spp_demo_common/models/demo_data_generator.py +++ b/spp_demo_common/models/demo_data_generator.py @@ -381,8 +381,35 @@ def generate_id_from_regex(self, regex_pattern): # noqa: C901 while i < len(pattern): char = pattern[i] + # Handle groups with alternation (...) + if char == "(": + # Find the matching closing parenthesis + depth = 1 + end = i + 1 + while end < len(pattern) and depth > 0: + if pattern[end] == "(": + depth += 1 + elif pattern[end] == ")": + depth -= 1 + end += 1 + + group_content = pattern[i + 1 : end - 1] + i = end + + # Check for alternation (|) + if "|" in group_content: + # Split by | and choose one randomly + alternatives = group_content.split("|") + chosen = random.choice(alternatives) + result.append(chosen) + else: + # No alternation, just recursively generate from the group content + generated = self.generate_id_from_regex("^" + group_content + "$") + if generated: + result.append(generated) + # Handle character classes [...] - if char == "[": + elif char == "[": end = pattern.index("]", i) char_class = pattern[i + 1 : end] i = end + 1 @@ -408,11 +435,23 @@ def generate_id_from_regex(self, regex_pattern): # noqa: C901 # Handle negation [^...] if char_class.startswith("^"): result.append(random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")) - # Handle ranges like [A-Z], [0-9], [a-z] - elif "-" in char_class and len(char_class) == 3 and char_class[1] == "-": - start_char = ord(char_class[0]) - end_char = ord(char_class[2]) - result.append(chr(random.randint(start_char, end_char))) + # Handle character classes with ranges (parse them properly) + elif "-" in char_class: + # Expand all ranges in the character class + chars = [] + j = 0 + while j < len(char_class): + # Check if this is a range (e.g., A-Z or 0-9) + if j + 2 < len(char_class) and char_class[j + 1] == "-": + start_char = ord(char_class[j]) + end_char = ord(char_class[j + 2]) + chars.extend([chr(c) for c in range(start_char, end_char + 1)]) + j += 3 + else: + # Single character (not part of a range) + chars.append(char_class[j]) + j += 1 + result.append(random.choice(chars)) # Handle explicit character list [ABC123] else: result.append(random.choice(char_class)) From 52258fa139fef6bcdd6334bef3b61f72473b3016 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 10:28:44 +0800 Subject: [PATCH 08/25] [FIX] phone and id regex validation --- spp_demo_common/models/demo_data_generator.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/spp_demo_common/models/demo_data_generator.py b/spp_demo_common/models/demo_data_generator.py index 851783579..4e874f0d0 100644 --- a/spp_demo_common/models/demo_data_generator.py +++ b/spp_demo_common/models/demo_data_generator.py @@ -392,16 +392,19 @@ def generate_id_from_regex(self, regex_pattern): # noqa: C901 elif pattern[end] == ")": depth -= 1 end += 1 - + group_content = pattern[i + 1 : end - 1] i = end - + # Check for alternation (|) if "|" in group_content: # Split by | and choose one randomly alternatives = group_content.split("|") chosen = random.choice(alternatives) - result.append(chosen) + # Recursively generate from the chosen alternative + generated = self.generate_id_from_regex("^" + chosen + "$") + if generated: + result.append(generated) else: # No alternation, just recursively generate from the group content generated = self.generate_id_from_regex("^" + group_content + "$") @@ -644,11 +647,11 @@ def generate_phone_number(self, fake): except Exception: phone_number = f"+{random.randint(1000000000, 9999999999)}" - # Accept only numbers, spaces, dashes, parentheses, and leading + - cleaned = phone_number.replace(" ", "").replace("-", "").replace("(", "").replace(")", "") - if cleaned.startswith("+"): - cleaned = cleaned[1:] - if cleaned.isdigit(): + # Extract only digits from the phone number (remove all non-digit characters) + cleaned = re.sub(r"\D", "", phone_number) + + # Ensure we have a valid length (at least 10 digits for most phone numbers) + if cleaned and len(cleaned) >= 10: return cleaned attempt += 1 From da899ee17c20dfe177a5711771c7f8198e846fa1 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 10:40:36 +0800 Subject: [PATCH 09/25] [ADD] farmer specific configuration --- spp_base_farmer_registry_demo/__manifest__.py | 1 + .../views/demo_data_generator_view.xml | 128 ++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 spp_base_farmer_registry_demo/views/demo_data_generator_view.xml diff --git a/spp_base_farmer_registry_demo/__manifest__.py b/spp_base_farmer_registry_demo/__manifest__.py index aa90d11a3..5083ee843 100644 --- a/spp_base_farmer_registry_demo/__manifest__.py +++ b/spp_base_farmer_registry_demo/__manifest__.py @@ -35,6 +35,7 @@ "data/chemical_data.xml", "data/fertilizer_data.xml", "data/feed_items_data.xml", + "views/demo_data_generator_view.xml", "views/group_view.xml", "views/individual_view.xml", ], diff --git a/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml new file mode 100644 index 000000000..6368d716c --- /dev/null +++ b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml @@ -0,0 +1,128 @@ + + + + + view_demo_data_generator_form_farmer + spp.demo.data.generator + + + + +
+
+ +
+

Percentage Settings

+
+
+
+

+ +
+
+

+ +
+
+

+ +
+
+
+
+

+ +
+
+

+ +
+
+ +
+
+
+ + +
+

Limits and Maximums

+
+
+
+

+ +
+
+

+ +
+
+
+
+

+ +
+
+

+ +
+
+
+
+

+ +
+
+ +
+
+
+
+
+
+
+
+
+ +
+ From 872fa83210913406232c77bc1e4e48d1d748bdce Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 10:57:41 +0800 Subject: [PATCH 10/25] [FIX] season creation on generator --- .../models/generate_demo_data.py | 170 ++++++++++++++---- .../views/demo_data_generator_view.xml | 32 ++++ 2 files changed, 169 insertions(+), 33 deletions(-) diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 5296c90f7..1ab6c3e11 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -5,7 +5,8 @@ from faker import Faker -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -65,6 +66,34 @@ class SPPDemoDataGenerator(models.Model): required=True, help="Maximum number of agricultural activities per farm", ) + + # Season configuration fields + season_name = fields.Char( + string="Season Name", + default=lambda self: f"Demo Season {fields.Date.today().year}", + required=True, + help="Name of the agricultural season to use or create", + ) + season_start_date = fields.Date( + string="Season Start Date", + default=lambda self: fields.Date.today().replace(month=1, day=1), + required=True, + help="Start date of the agricultural season", + ) + season_end_date = fields.Date( + string="Season End Date", + default=lambda self: fields.Date.today().replace(month=12, day=31), + required=True, + help="End date of the agricultural season", + ) + + @api.constrains("season_start_date", "season_end_date") + def _check_season_dates(self): + """Validate that season end date is after start date""" + for record in self: + if record.season_end_date and record.season_start_date: + if record.season_end_date < record.season_start_date: + raise ValidationError(_("Season end date must be after start date")) # Selection options for farmer registry FARM_TYPES = [ @@ -119,7 +148,19 @@ def generate_demo_data(self): self._generate_chemical_data(fake) self._generate_fertilizer_data(fake) self._generate_feed_items_data(fake) - self._generate_season_data(fake) + + # Pre-initialize the season early (will be cached and reused by _get_random_season) + season_id = self._get_random_season() + if not season_id: + raise ValidationError( + _("Failed to create or find an active agricultural season. " + "Please check the season configuration and try again.") + ) + + _logger.info( + f"Demo data generation initialized with season: {self._active_season.name} " + f"({self._active_season.date_start} to {self._active_season.date_end})" + ) result = super().generate_demo_data() return result @@ -631,20 +672,32 @@ def _get_machinery_vals(self, fake, group): def _generate_agricultural_activities(self, fake, group): """Generate agricultural activities""" + # Ensure season is available + season_id = self._get_random_season() + if not season_id: + _logger.warning(f"No active season available for group {group.id}. Skipping agricultural activities.") + return + + season = self.env["spp.farm.season"].browse(season_id) + _logger.debug(f"Generating agricultural activities for group {group.id} using season: {season.name}") + num_activities = random.randint(1, self.max_activities_per_farm) for _ in range(num_activities): - activity_vals = self._get_agricultural_activity_vals(fake, group) - self.env["spp.farm.activity"].create(activity_vals) + activity_vals = self._get_agricultural_activity_vals(fake, group, season_id) + try: + self.env["spp.farm.activity"].create(activity_vals) + except Exception as e: + _logger.error(f"Failed to create agricultural activity for group {group.id}: {str(e)}") - def _get_agricultural_activity_vals(self, fake, group): + def _get_agricultural_activity_vals(self, fake, group, season_id): """Get agricultural activity values""" activity_type = random.choice(self.ACTIVITY_TYPES) vals = { "activity_type": activity_type[0], "purpose": random.choice(self.PRODUCTION_PURPOSES)[0], - "season_id": self._get_random_season(), + "season_id": season_id, } # Set the appropriate farm field based on activity type @@ -826,29 +879,68 @@ def _generate_feed_items_data(self, fake): self.env["spp.feed.items"].create(feed_info) def _generate_season_data(self, fake): - """Generate agricultural season data for the demo""" - current_year = fields.Date.today().year - seasons = [ - { - "name": f"Season {current_year}", - "description": f"Main agricultural season for {current_year}", - "date_start": fields.Date.today().replace(month=1, day=1), - "date_end": fields.Date.today().replace(month=12, day=31), - "state": "active", - }, - { - "name": f"Season {current_year - 1}", - "description": f"Previous agricultural season for {current_year - 1}", - "date_start": fields.Date.today().replace(year=current_year - 1, month=1, day=1), - "date_end": fields.Date.today().replace(year=current_year - 1, month=12, day=31), - "state": "closed", - }, - ] - - for season_info in seasons: - existing = self.env["spp.farm.season"].search([("name", "=", season_info["name"])]) - if not existing: - self.env["spp.farm.season"].create(season_info) + """Generate or find agricultural season data for the demo""" + try: + # Search for existing season matching name, dates, and active state + existing_season = self.env["spp.farm.season"].search([ + ("name", "=", self.season_name), + ("date_start", "=", self.season_start_date), + ("date_end", "=", self.season_end_date), + ("state", "=", "active"), + ], limit=1) + + if existing_season: + _logger.info(f"Using existing active season: {existing_season.name} (ID: {existing_season.id})") + return existing_season + + # Search for season with same name but different dates or status + existing_season_by_name = self.env["spp.farm.season"].search([ + ("name", "=", self.season_name), + ], limit=1) + + if existing_season_by_name: + # Update existing season if it's not closed + if existing_season_by_name.state != "closed": + _logger.info(f"Updating existing season: {existing_season_by_name.name}") + existing_season_by_name.write({ + "date_start": self.season_start_date, + "date_end": self.season_end_date, + }) + if existing_season_by_name.state == "draft": + existing_season_by_name.action_activate() + _logger.info(f"Season updated and activated: {existing_season_by_name.name} (ID: {existing_season_by_name.id})") + return existing_season_by_name + else: + _logger.warning(f"Season '{self.season_name}' exists but is closed. Creating new season with modified name.") + # Create new season with modified name + season_vals = { + "name": f"{self.season_name} (Demo)", + "description": f"Demo agricultural season created on {fields.Date.today()}", + "date_start": self.season_start_date, + "date_end": self.season_end_date, + "state": "draft", + } + new_season = self.env["spp.farm.season"].create(season_vals) + new_season.action_activate() + _logger.info(f"Created and activated new season: {new_season.name} (ID: {new_season.id})") + return new_season + + # Create new season if none exists + season_vals = { + "name": self.season_name, + "description": f"Demo agricultural season created on {fields.Date.today()}", + "date_start": self.season_start_date, + "date_end": self.season_end_date, + "state": "draft", + } + new_season = self.env["spp.farm.season"].create(season_vals) + new_season.action_activate() + _logger.info(f"Created and activated new season: {new_season.name} (ID: {new_season.id})") + return new_season + + except Exception as e: + _logger.error(f"Failed to generate/find season: {str(e)}") + raise ValidationError(_(f"Failed to create or find agricultural season: {str(e)}")) def _get_random_species(self, species_type): """Get a random species of the specified type""" @@ -882,8 +974,20 @@ def _get_random_feed_items(self, limit=3): return [(6, 0, [])] def _get_random_season(self): - """Get a random active season""" - seasons = self.env["spp.farm.season"].search([("state", "=", "active")]) - if seasons: - return random.choice(seasons).id + """Get the configured active season, creating it if necessary""" + # Use cached season if available + if hasattr(self, '_active_season') and self._active_season: + _logger.debug(f"Using cached season: {self._active_season.name} (ID: {self._active_season.id})") + return self._active_season.id + + # Generate/find the season if not already cached + _logger.info("Season not cached, generating/finding season now") + fake = Faker(self.locale_origin.faker_locale or "en_US") if self.locale_origin else Faker("en_US") + self._active_season = self._generate_season_data(fake) + + if self._active_season: + _logger.info(f"Using season: {self._active_season.name} (ID: {self._active_season.id})") + return self._active_season.id + + _logger.error("Failed to generate/find active season! Agricultural activities cannot be created.") return None diff --git a/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml index 6368d716c..8d74ebfbc 100644 --- a/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml +++ b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml @@ -9,6 +9,38 @@
+ +
+

Season Configuration

+
+
+
+

+ +
+
+

+ +
+
+

+ +
+
+
+
From feb63ae2159fdd63a2517e406667de55cef9ef22 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 11:02:42 +0800 Subject: [PATCH 11/25] [FIX] season creation on generator --- .../models/generate_demo_data.py | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 1ab6c3e11..139fd3ce5 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -151,7 +151,7 @@ def generate_demo_data(self): # Pre-initialize the season early (will be cached and reused by _get_random_season) season_id = self._get_random_season() - if not season_id: + if not season_id or not hasattr(self, '_active_season') or not self._active_season: raise ValidationError( _("Failed to create or find an active agricultural season. " "Please check the season configuration and try again.") @@ -975,19 +975,32 @@ def _get_random_feed_items(self, limit=3): def _get_random_season(self): """Get the configured active season, creating it if necessary""" + # Initialize _active_season if not already set + if not hasattr(self, '_active_season'): + self._active_season = None + # Use cached season if available - if hasattr(self, '_active_season') and self._active_season: + if self._active_season: _logger.debug(f"Using cached season: {self._active_season.name} (ID: {self._active_season.id})") return self._active_season.id # Generate/find the season if not already cached _logger.info("Season not cached, generating/finding season now") - fake = Faker(self.locale_origin.faker_locale or "en_US") if self.locale_origin else Faker("en_US") - self._active_season = self._generate_season_data(fake) - - if self._active_season: - _logger.info(f"Using season: {self._active_season.name} (ID: {self._active_season.id})") - return self._active_season.id + try: + # Get faker locale safely + faker_locale = "en_US" + if hasattr(self, 'locale_origin') and self.locale_origin: + faker_locale = self.locale_origin.faker_locale or "en_US" + + fake = Faker(faker_locale) + self._active_season = self._generate_season_data(fake) + + if self._active_season: + _logger.info(f"Using season: {self._active_season.name} (ID: {self._active_season.id})") + return self._active_season.id + except Exception as e: + _logger.error(f"Exception while generating season: {str(e)}", exc_info=True) + self._active_season = None _logger.error("Failed to generate/find active season! Agricultural activities cannot be created.") return None From cc722ffe791930dfff32354e66329f14a6c941f3 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 11:05:58 +0800 Subject: [PATCH 12/25] [FIX] season creation on generator --- .../models/generate_demo_data.py | 32 ++++++------------- 1 file changed, 10 insertions(+), 22 deletions(-) diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 139fd3ce5..6082a3eee 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -149,17 +149,17 @@ def generate_demo_data(self): self._generate_fertilizer_data(fake) self._generate_feed_items_data(fake) - # Pre-initialize the season early (will be cached and reused by _get_random_season) - season_id = self._get_random_season() - if not season_id or not hasattr(self, '_active_season') or not self._active_season: + # Validate that season can be created/found + season = self._generate_season_data(fake) + if not season: raise ValidationError( _("Failed to create or find an active agricultural season. " "Please check the season configuration and try again.") ) _logger.info( - f"Demo data generation initialized with season: {self._active_season.name} " - f"({self._active_season.date_start} to {self._active_season.date_end})" + f"Demo data generation initialized with season: {season.name} " + f"({season.date_start} to {season.date_end})" ) result = super().generate_demo_data() @@ -975,17 +975,6 @@ def _get_random_feed_items(self, limit=3): def _get_random_season(self): """Get the configured active season, creating it if necessary""" - # Initialize _active_season if not already set - if not hasattr(self, '_active_season'): - self._active_season = None - - # Use cached season if available - if self._active_season: - _logger.debug(f"Using cached season: {self._active_season.name} (ID: {self._active_season.id})") - return self._active_season.id - - # Generate/find the season if not already cached - _logger.info("Season not cached, generating/finding season now") try: # Get faker locale safely faker_locale = "en_US" @@ -993,14 +982,13 @@ def _get_random_season(self): faker_locale = self.locale_origin.faker_locale or "en_US" fake = Faker(faker_locale) - self._active_season = self._generate_season_data(fake) + season = self._generate_season_data(fake) - if self._active_season: - _logger.info(f"Using season: {self._active_season.name} (ID: {self._active_season.id})") - return self._active_season.id + if season: + _logger.debug(f"Using season: {season.name} (ID: {season.id})") + return season.id except Exception as e: - _logger.error(f"Exception while generating season: {str(e)}", exc_info=True) - self._active_season = None + _logger.error(f"Exception while getting season: {str(e)}", exc_info=True) _logger.error("Failed to generate/find active season! Agricultural activities cannot be created.") return None From 816ee4ec852d20284c8929a717017d3b9b107460 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 28 Oct 2025 14:07:40 +0800 Subject: [PATCH 13/25] [FIX] phone number generation --- spp_demo_common/models/demo_data_generator.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spp_demo_common/models/demo_data_generator.py b/spp_demo_common/models/demo_data_generator.py index 4e874f0d0..f19030c01 100644 --- a/spp_demo_common/models/demo_data_generator.py +++ b/spp_demo_common/models/demo_data_generator.py @@ -647,11 +647,18 @@ def generate_phone_number(self, fake): except Exception: phone_number = f"+{random.randint(1000000000, 9999999999)}" - # Extract only digits from the phone number (remove all non-digit characters) + # Preserve the leading '+' if present, then remove all non-digit characters + has_plus = phone_number.startswith('+') cleaned = re.sub(r"\D", "", phone_number) + + # Add back the '+' prefix if it was originally there + if has_plus: + cleaned = f"+{cleaned}" # Ensure we have a valid length (at least 10 digits for most phone numbers) - if cleaned and len(cleaned) >= 10: + # Check digit count (excluding the + sign) + digit_count = len(cleaned.lstrip('+')) + if cleaned and digit_count >= 10: return cleaned attempt += 1 From fdbf2c4fd31a9099d4a89837199e38a400ffeb9c Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Thu, 30 Oct 2025 10:09:59 +0800 Subject: [PATCH 14/25] [IMP] phone number onchange --- spp_base_common/models/res_partner.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/spp_base_common/models/res_partner.py b/spp_base_common/models/res_partner.py index e9956cd8a..a8219d884 100644 --- a/spp_base_common/models/res_partner.py +++ b/spp_base_common/models/res_partner.py @@ -1,6 +1,6 @@ import logging -from odoo import fields, models +from odoo import api, fields, models _logger = logging.getLogger(__name__) @@ -14,3 +14,12 @@ class SPPResPartner(models.Model): default=lambda self: self.env.company, required=False, ) + + @api.onchange("phone_number_ids") + def phone_number_ids_change(self): + phone = "" + if self.phone_number_ids: + phone = ", ".join( + [p for p in self.phone_number_ids.filtered(lambda rec: not rec.disabled).mapped("phone_no")] + ) + self.phone = phone \ No newline at end of file From aa23da7afca6e5be332168b6e1a57af2cc76a455 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Fri, 31 Oct 2025 10:10:24 +0800 Subject: [PATCH 15/25] [FIX] set 100 percent by default --- .../models/generate_demo_data.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 6082a3eee..1a7d5f22a 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -17,28 +17,28 @@ class SPPDemoDataGenerator(models.Model): # Farmer Registry specific fields percentage_with_farm_details = fields.Integer( string="% with Farm Details", - default=80, + default=100, required=True, help="Percentage of farmers that will have farm details", ) percentage_with_land_records = fields.Integer( string="% with Land Records", - default=70, + default=100, required=True, help="Percentage of farmers that will have land records", ) percentage_with_farm_assets = fields.Integer( - string="% with Farm Assets", default=60, required=True, help="Percentage of farmers that will have farm assets" + string="% with Farm Assets", default=100, required=True, help="Percentage of farmers that will have farm assets" ) percentage_with_agricultural_activities = fields.Integer( string="% with Agricultural Activities", - default=85, + default=100, required=True, help="Percentage of farmers that will have agricultural activities", ) percentage_with_extension_services = fields.Integer( string="% with Extension Services", - default=40, + default=100, required=True, help="Percentage of farmers that will have extension services", ) From 315f4569a94431eca5c0ad57366e2aa4ec1999fd Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 16:05:46 +0800 Subject: [PATCH 16/25] [IMP] test coverage --- spp_base_common/models/res_partner.py | 2 +- .../models/generate_demo_data.py | 87 +- .../tests/__init__.py | 6 + .../tests/test_agricultural_activity.py | 514 ++++++++++ .../tests/test_farm.py | 241 ++++- .../tests/test_farm_asset.py | 400 ++++++++ .../tests/test_farm_details.py | 661 +++++++++++++ .../tests/test_farmer.py | 310 ++++++ .../tests/test_generate_demo_data.py | 902 ++++++++++++++++++ .../tests/test_land_record.py | 399 ++++++++ .../views/demo_data_generator_view.xml | 37 +- spp_demo_common/models/demo_data_generator.py | 12 +- 12 files changed, 3516 insertions(+), 55 deletions(-) create mode 100644 spp_base_farmer_registry_demo/tests/test_agricultural_activity.py create mode 100644 spp_base_farmer_registry_demo/tests/test_farm_asset.py create mode 100644 spp_base_farmer_registry_demo/tests/test_farm_details.py create mode 100644 spp_base_farmer_registry_demo/tests/test_farmer.py create mode 100644 spp_base_farmer_registry_demo/tests/test_generate_demo_data.py create mode 100644 spp_base_farmer_registry_demo/tests/test_land_record.py diff --git a/spp_base_common/models/res_partner.py b/spp_base_common/models/res_partner.py index a8219d884..dd5085a15 100644 --- a/spp_base_common/models/res_partner.py +++ b/spp_base_common/models/res_partner.py @@ -22,4 +22,4 @@ def phone_number_ids_change(self): phone = ", ".join( [p for p in self.phone_number_ids.filtered(lambda rec: not rec.disabled).mapped("phone_no")] ) - self.phone = phone \ No newline at end of file + self.phone = phone diff --git a/spp_base_farmer_registry_demo/models/generate_demo_data.py b/spp_base_farmer_registry_demo/models/generate_demo_data.py index 1a7d5f22a..cb16d4d2a 100644 --- a/spp_base_farmer_registry_demo/models/generate_demo_data.py +++ b/spp_base_farmer_registry_demo/models/generate_demo_data.py @@ -66,7 +66,7 @@ class SPPDemoDataGenerator(models.Model): required=True, help="Maximum number of agricultural activities per farm", ) - + # Season configuration fields season_name = fields.Char( string="Season Name", @@ -148,15 +148,17 @@ def generate_demo_data(self): self._generate_chemical_data(fake) self._generate_fertilizer_data(fake) self._generate_feed_items_data(fake) - + # Validate that season can be created/found season = self._generate_season_data(fake) if not season: raise ValidationError( - _("Failed to create or find an active agricultural season. " - "Please check the season configuration and try again.") + _( + "Failed to create or find an active agricultural season. " + "Please check the season configuration and try again." + ) ) - + _logger.info( f"Demo data generation initialized with season: {season.name} " f"({season.date_start} to {season.date_end})" @@ -584,7 +586,7 @@ def _generate_land_records(self, fake, group): """Generate land records for a farm""" num_parcels = random.randint(1, self.max_land_parcels_per_farm) - for _ in range(num_parcels): + for _i in range(num_parcels): land_vals = self._get_land_record_vals(fake, group) land_record = self.env["spp.land.record"].create(land_vals) @@ -641,7 +643,7 @@ def _generate_farm_assets(self, fake, group): """Generate farm assets and machinery""" num_assets = random.randint(1, self.max_assets_per_farm) - for _ in range(num_assets): + for _i in range(num_assets): # Generate farm assets asset_vals = self._get_farm_asset_vals(fake, group) self.env["spp.farm.asset"].create(asset_vals) @@ -677,13 +679,13 @@ def _generate_agricultural_activities(self, fake, group): if not season_id: _logger.warning(f"No active season available for group {group.id}. Skipping agricultural activities.") return - + season = self.env["spp.farm.season"].browse(season_id) _logger.debug(f"Generating agricultural activities for group {group.id} using season: {season.name}") - + num_activities = random.randint(1, self.max_activities_per_farm) - for _ in range(num_activities): + for _i in range(num_activities): activity_vals = self._get_agricultural_activity_vals(fake, group, season_id) try: self.env["spp.farm.activity"].create(activity_vals) @@ -763,7 +765,7 @@ def _generate_extension_services(self, fake, group): """Generate farm extension services""" num_services = random.randint(1, 3) - for _ in range(num_services): + for _i in range(num_services): extension_vals = self._get_extension_service_vals(fake, group) self.env["spp.farm.extension"].create(extension_vals) @@ -882,36 +884,49 @@ def _generate_season_data(self, fake): """Generate or find agricultural season data for the demo""" try: # Search for existing season matching name, dates, and active state - existing_season = self.env["spp.farm.season"].search([ - ("name", "=", self.season_name), - ("date_start", "=", self.season_start_date), - ("date_end", "=", self.season_end_date), - ("state", "=", "active"), - ], limit=1) - + existing_season = self.env["spp.farm.season"].search( + [ + ("name", "=", self.season_name), + ("date_start", "=", self.season_start_date), + ("date_end", "=", self.season_end_date), + ("state", "=", "active"), + ], + limit=1, + ) + if existing_season: _logger.info(f"Using existing active season: {existing_season.name} (ID: {existing_season.id})") return existing_season - + # Search for season with same name but different dates or status - existing_season_by_name = self.env["spp.farm.season"].search([ - ("name", "=", self.season_name), - ], limit=1) - + existing_season_by_name = self.env["spp.farm.season"].search( + [ + ("name", "=", self.season_name), + ], + limit=1, + ) + if existing_season_by_name: # Update existing season if it's not closed if existing_season_by_name.state != "closed": _logger.info(f"Updating existing season: {existing_season_by_name.name}") - existing_season_by_name.write({ - "date_start": self.season_start_date, - "date_end": self.season_end_date, - }) + existing_season_by_name.write( + { + "date_start": self.season_start_date, + "date_end": self.season_end_date, + } + ) if existing_season_by_name.state == "draft": existing_season_by_name.action_activate() - _logger.info(f"Season updated and activated: {existing_season_by_name.name} (ID: {existing_season_by_name.id})") + _logger.info( + f"Season updated and activated: {existing_season_by_name.name} " + f"(ID: {existing_season_by_name.id})" + ) return existing_season_by_name else: - _logger.warning(f"Season '{self.season_name}' exists but is closed. Creating new season with modified name.") + _logger.warning( + f"Season '{self.season_name}' exists but is closed. Creating new season with modified name." + ) # Create new season with modified name season_vals = { "name": f"{self.season_name} (Demo)", @@ -924,7 +939,7 @@ def _generate_season_data(self, fake): new_season.action_activate() _logger.info(f"Created and activated new season: {new_season.name} (ID: {new_season.id})") return new_season - + # Create new season if none exists season_vals = { "name": self.season_name, @@ -937,10 +952,10 @@ def _generate_season_data(self, fake): new_season.action_activate() _logger.info(f"Created and activated new season: {new_season.name} (ID: {new_season.id})") return new_season - + except Exception as e: _logger.error(f"Failed to generate/find season: {str(e)}") - raise ValidationError(_(f"Failed to create or find agricultural season: {str(e)}")) + raise ValidationError(_(f"Failed to create or find agricultural season: {str(e)}")) from e def _get_random_species(self, species_type): """Get a random species of the specified type""" @@ -978,17 +993,17 @@ def _get_random_season(self): try: # Get faker locale safely faker_locale = "en_US" - if hasattr(self, 'locale_origin') and self.locale_origin: + if hasattr(self, "locale_origin") and self.locale_origin: faker_locale = self.locale_origin.faker_locale or "en_US" - + fake = Faker(faker_locale) season = self._generate_season_data(fake) - + if season: _logger.debug(f"Using season: {season.name} (ID: {season.id})") return season.id except Exception as e: _logger.error(f"Exception while getting season: {str(e)}", exc_info=True) - + _logger.error("Failed to generate/find active season! Agricultural activities cannot be created.") return None diff --git a/spp_base_farmer_registry_demo/tests/__init__.py b/spp_base_farmer_registry_demo/tests/__init__.py index 033b71417..bb105a50d 100644 --- a/spp_base_farmer_registry_demo/tests/__init__.py +++ b/spp_base_farmer_registry_demo/tests/__init__.py @@ -1 +1,7 @@ from . import test_farm +from . import test_generate_demo_data +from . import test_farmer +from . import test_farm_details +from . import test_agricultural_activity +from . import test_land_record +from . import test_farm_asset diff --git a/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py new file mode 100644 index 000000000..af99f9df3 --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py @@ -0,0 +1,514 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import datetime +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestAgriculturalActivity(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test farm (group) + cls.farm = cls.env["res.partner"].create( + { + "name": "Test Farm Activity", + "is_group": True, + "is_registrant": True, + } + ) + + # Create test season + cls.season = cls.env["spp.farm.season"].create( + { + "name": "Test Season 2024", + "date_start": datetime.date(2024, 1, 1), + "date_end": datetime.date(2024, 12, 31), + "state": "draft", + } + ) + cls.season.action_activate() + + # Create test species + cls.crop_species = cls.env["spp.farm.species"].create( + { + "name": "Test Crop", + "species_type": "crop", + } + ) + + cls.livestock_species = cls.env["spp.farm.species"].create( + { + "name": "Test Livestock", + "species_type": "livestock", + } + ) + + cls.aqua_species = cls.env["spp.farm.species"].create( + { + "name": "Test Fish", + "species_type": "aquaculture", + } + ) + + # Create test chemicals + cls.chemical = cls.env["spp.farm.chemical"].create( + { + "name": "Test Herbicide", + } + ) + + # Create test fertilizers + cls.fertilizer = cls.env["spp.fertilizer"].create( + { + "name": "Test Fertilizer", + } + ) + + # Create test feed items + cls.feed_item = cls.env["spp.feed.items"].create( + { + "name": "Test Feed", + } + ) + + def test_01_crop_cultivation_water_source(self): + """Test cultivation water source field""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_water_source": "irrigated", + } + ) + + self.assertEqual(activity.cultivation_water_source, "irrigated") + + # Test rainfed + activity.write({"cultivation_water_source": "rainfed"}) + self.assertEqual(activity.cultivation_water_source, "rainfed") + + def test_02_crop_production_system(self): + """Test cultivation production system field""" + production_systems = [ + "Mono-cropping", + "Mixed-cropping", + "Agroforestry", + "Plantation", + "Greenhouse", + ] + + for system in production_systems: + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_production_system": system, + } + ) + self.assertEqual(activity.cultivation_production_system, system) + + def test_03_crop_chemical_interventions(self): + """Test cultivation chemical interventions many2many field""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_chemical_interventions": [(6, 0, [self.chemical.id])], + } + ) + + self.assertIn(self.chemical, activity.cultivation_chemical_interventions) + + def test_04_crop_fertilizer_interventions(self): + """Test cultivation fertilizer interventions many2many field""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_fertilizer_interventions": [(6, 0, [self.fertilizer.id])], + } + ) + + self.assertIn(self.fertilizer, activity.cultivation_fertilizer_interventions) + + def test_05_livestock_production_system(self): + """Test livestock production system field""" + production_systems = [ + "ranching", + "communal grazing", + "pastoralism", + "rotational grazing", + "zero grazing", + "semi zero grazing", + "feedlots", + "free range", + "tethering", + "other", + ] + + for system in production_systems: + activity = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + "livestock_production_system": system, + } + ) + self.assertEqual(activity.livestock_production_system, system) + + def test_06_livestock_feed_items(self): + """Test livestock feed items many2many field""" + activity = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + "livestock_feed_items": [(6, 0, [self.feed_item.id])], + } + ) + + self.assertIn(self.feed_item, activity.livestock_feed_items) + + def test_07_aquaculture_production_system(self): + """Test aquaculture production system field""" + production_systems = [ + "ponds", + "cages", + "tanks", + "raceways", + "recirculating systems", + "aquaponics", + "other", + ] + + for system in production_systems: + activity = self.env["spp.farm.activity"].create( + { + "aqua_farm_id": self.farm.id, + "activity_type": "aquaculture", + "species_id": self.aqua_species.id, + "season_id": self.season.id, + "aquaculture_production_system": system, + } + ) + self.assertEqual(activity.aquaculture_production_system, system) + + def test_08_aquaculture_number_of_fingerlings(self): + """Test aquaculture number of fingerlings field""" + activity = self.env["spp.farm.activity"].create( + { + "aqua_farm_id": self.farm.id, + "activity_type": "aquaculture", + "species_id": self.aqua_species.id, + "season_id": self.season.id, + "aquaculture_number_of_fingerlings": 5000, + } + ) + + self.assertEqual(activity.aquaculture_number_of_fingerlings, 5000) + + def test_09_onchange_farm_id(self): + """Test onchange farm_id clears land_id""" + # Create a land record + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Test Land", + "land_acreage": 10.0, + } + ) + + # Create activity with land + activity = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + "land_id": land_record.id, + } + ) + + # Trigger onchange + activity._onchange_farm_id() + + # land_id should be cleared + self.assertFalse(activity.land_id) + + def test_10_crop_activity_comprehensive(self): + """Test creating crop activity with all fields""" + # Create multiple chemicals and fertilizers + chemical2 = self.env["spp.farm.chemical"].create({"name": "Test Insecticide"}) + fertilizer2 = self.env["spp.fertilizer"].create({"name": "Test NPK"}) + + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_water_source": "irrigated", + "cultivation_production_system": "Greenhouse", + "cultivation_chemical_interventions": [(6, 0, [self.chemical.id, chemical2.id])], + "cultivation_fertilizer_interventions": [(6, 0, [self.fertilizer.id, fertilizer2.id])], + } + ) + + self.assertEqual(activity.cultivation_water_source, "irrigated") + self.assertEqual(activity.cultivation_production_system, "Greenhouse") + self.assertEqual(len(activity.cultivation_chemical_interventions), 2) + self.assertEqual(len(activity.cultivation_fertilizer_interventions), 2) + + def test_11_livestock_activity_comprehensive(self): + """Test creating livestock activity with all fields""" + # Create multiple feed items + feed2 = self.env["spp.feed.items"].create({"name": "Test Hay"}) + feed3 = self.env["spp.feed.items"].create({"name": "Test Grain"}) + + activity = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + "livestock_production_system": "zero grazing", + "livestock_feed_items": [(6, 0, [self.feed_item.id, feed2.id, feed3.id])], + } + ) + + self.assertEqual(activity.livestock_production_system, "zero grazing") + self.assertEqual(len(activity.livestock_feed_items), 3) + + def test_12_aquaculture_activity_comprehensive(self): + """Test creating aquaculture activity with all fields""" + activity = self.env["spp.farm.activity"].create( + { + "aqua_farm_id": self.farm.id, + "activity_type": "aquaculture", + "species_id": self.aqua_species.id, + "season_id": self.season.id, + "aquaculture_production_system": "ponds", + "aquaculture_number_of_fingerlings": 10000, + } + ) + + self.assertEqual(activity.aquaculture_production_system, "ponds") + self.assertEqual(activity.aquaculture_number_of_fingerlings, 10000) + + def test_13_multiple_activities_same_farm(self): + """Test creating multiple activities for the same farm""" + activity1 = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_water_source": "irrigated", + } + ) + + activity2 = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + "livestock_production_system": "free range", + } + ) + + activity3 = self.env["spp.farm.activity"].create( + { + "aqua_farm_id": self.farm.id, + "activity_type": "aquaculture", + "species_id": self.aqua_species.id, + "season_id": self.season.id, + "aquaculture_production_system": "tanks", + } + ) + + # All should be created successfully + self.assertTrue(activity1) + self.assertTrue(activity2) + self.assertTrue(activity3) + + def test_14_update_activity_fields(self): + """Test updating activity fields""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_water_source": "rainfed", + "cultivation_production_system": "Mono-cropping", + } + ) + + # Update fields + activity.write( + { + "cultivation_water_source": "irrigated", + "cultivation_production_system": "Mixed-cropping", + } + ) + + self.assertEqual(activity.cultivation_water_source, "irrigated") + self.assertEqual(activity.cultivation_production_system, "Mixed-cropping") + + def test_15_add_remove_chemical_interventions(self): + """Test adding and removing chemical interventions""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + } + ) + + # Add chemical + activity.write({"cultivation_chemical_interventions": [(6, 0, [self.chemical.id])]}) + self.assertIn(self.chemical, activity.cultivation_chemical_interventions) + + # Remove chemical + activity.write({"cultivation_chemical_interventions": [(6, 0, [])]}) + self.assertEqual(len(activity.cultivation_chemical_interventions), 0) + + def test_16_add_remove_feed_items(self): + """Test adding and removing feed items""" + activity = self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + } + ) + + # Add feed item + activity.write({"livestock_feed_items": [(6, 0, [self.feed_item.id])]}) + self.assertIn(self.feed_item, activity.livestock_feed_items) + + # Remove feed item + activity.write({"livestock_feed_items": [(6, 0, [])]}) + self.assertEqual(len(activity.livestock_feed_items), 0) + + def test_17_search_activities_by_type(self): + """Test searching activities by type""" + # Create activities + self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + } + ) + self.env["spp.farm.activity"].create( + { + "live_farm_id": self.farm.id, + "activity_type": "livestock", + "species_id": self.livestock_species.id, + "season_id": self.season.id, + } + ) + + # Search for crop activities + crop_activities = self.env["spp.farm.activity"].search([("activity_type", "=", "crop")]) + self.assertGreaterEqual(len(crop_activities), 1) + + # Search for livestock activities + livestock_activities = self.env["spp.farm.activity"].search([("activity_type", "=", "livestock")]) + self.assertGreaterEqual(len(livestock_activities), 1) + + def test_18_search_activities_by_production_system(self): + """Test searching activities by production system""" + self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + "cultivation_production_system": "Greenhouse", + } + ) + + # Search for greenhouse activities + greenhouse = self.env["spp.farm.activity"].search([("cultivation_production_system", "=", "Greenhouse")]) + self.assertGreaterEqual(len(greenhouse), 1) + + def test_19_activities_with_different_seasons(self): + """Test activities with different seasons""" + season2 = self.env["spp.farm.season"].create( + { + "name": "Test Season 2025", + "date_start": datetime.date(2025, 1, 1), + "date_end": datetime.date(2025, 12, 31), + "state": "draft", + } + ) + season2.action_activate() + + activity1 = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + } + ) + + activity2 = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": season2.id, + } + ) + + self.assertEqual(activity1.season_id, self.season) + self.assertEqual(activity2.season_id, season2) + + def test_20_field_types_verification(self): + """Test field types are correct""" + activity = self.env["spp.farm.activity"].create( + { + "crop_farm_id": self.farm.id, + "activity_type": "crop", + "species_id": self.crop_species.id, + "season_id": self.season.id, + } + ) + + # Check field types + self.assertEqual(activity._fields["cultivation_water_source"].type, "selection") + self.assertEqual(activity._fields["cultivation_production_system"].type, "selection") + self.assertEqual(activity._fields["cultivation_chemical_interventions"].type, "many2many") + self.assertEqual(activity._fields["cultivation_fertilizer_interventions"].type, "many2many") + self.assertEqual(activity._fields["livestock_production_system"].type, "selection") + self.assertEqual(activity._fields["livestock_feed_items"].type, "many2many") + self.assertEqual(activity._fields["aquaculture_production_system"].type, "selection") + self.assertEqual(activity._fields["aquaculture_number_of_fingerlings"].type, "integer") diff --git a/spp_base_farmer_registry_demo/tests/test_farm.py b/spp_base_farmer_registry_demo/tests/test_farm.py index a0434d896..715f1582b 100644 --- a/spp_base_farmer_registry_demo/tests/test_farm.py +++ b/spp_base_farmer_registry_demo/tests/test_farm.py @@ -1,6 +1,11 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import logging + from odoo import fields from odoo.tests.common import TransactionCase +_logger = logging.getLogger(__name__) + class TestFarm(TransactionCase): @classmethod @@ -37,7 +42,8 @@ def setUpClass(cls): } ) - def test_open_member_form(self): + def test_01_open_member_form(self): + """Test opening member form""" member_form = self.group_membership_1.open_member_form() self.assertIsInstance(member_form, dict) @@ -51,6 +57,8 @@ def test_open_member_form(self): self.assertEqual(member_form["context"], {"default_is_group": False}) self.assertEqual(member_form["flags"], {"mode": "readonly"}) + def test_02_open_member_form_group(self): + """Test opening member form for group""" farm_id = self.env["res.partner"].create( { "family_name": "Test", @@ -82,6 +90,231 @@ def test_open_member_form(self): self.assertEqual(member_form["context"], {"default_is_group": True}) self.assertEqual(member_form["flags"], {"mode": "readonly"}) - # membership.individual = False - # with self.assertRaises(UserError, "A group or individual must be specified for this member."): - # membership.open_member_form() + def test_03_household_size_field_exists(self): + """Test that household_size field exists on partner""" + partner = self.env["res.partner"].create( + { + "name": "Test Farm Partner", + "is_group": True, + "is_registrant": True, + } + ) + + self.assertIn("household_size", partner._fields) + + def test_04_household_size_can_be_set(self): + """Test that household_size can be set and retrieved""" + partner = self.env["res.partner"].create( + { + "name": "Test Farm Household", + "is_group": True, + "is_registrant": True, + "household_size": 5, + } + ) + + self.assertEqual(partner.household_size, 5) + + def test_05_household_size_can_be_updated(self): + """Test that household_size can be updated""" + partner = self.env["res.partner"].create( + { + "name": "Test Farm Update", + "is_group": True, + "is_registrant": True, + "household_size": 3, + } + ) + + partner.write({"household_size": 7}) + self.assertEqual(partner.household_size, 7) + + def test_06_household_size_filter(self): + """Test filtering by household_size""" + partner1 = self.env["res.partner"].create( + { + "name": "Farm 1", + "is_group": True, + "is_registrant": True, + "household_size": 5, + } + ) + + partner2 = self.env["res.partner"].create( + { + "name": "Farm 2", + "is_group": True, + "is_registrant": True, + "household_size": 10, + } + ) + + # Test filtering + found = self.env["res.partner"].search([("household_size", "=", 5)]) + self.assertIn(partner1, found) + self.assertNotIn(partner2, found) + + def test_07_household_size_default_value(self): + """Test household_size default value""" + partner = self.env["res.partner"].create( + { + "name": "Test Farm Default", + "is_group": True, + "is_registrant": True, + } + ) + + # Default should be 0 or False + self.assertIn(partner.household_size, [0, False]) + + def test_08_household_size_various_values(self): + """Test household_size with various values""" + test_values = [0, 1, 5, 10, 20, 50, 100] + + for value in test_values: + partner = self.env["res.partner"].create( + { + "name": f"Farm Size {value}", + "is_group": True, + "is_registrant": True, + "household_size": value, + } + ) + self.assertEqual(partner.household_size, value) + + def test_09_household_size_search_operators(self): + """Test household_size search with different operators""" + # Create test data + self.env["res.partner"].create( + { + "name": "Small Farm", + "is_group": True, + "is_registrant": True, + "household_size": 2, + } + ) + self.env["res.partner"].create( + { + "name": "Medium Farm", + "is_group": True, + "is_registrant": True, + "household_size": 5, + } + ) + self.env["res.partner"].create( + { + "name": "Large Farm", + "is_group": True, + "is_registrant": True, + "household_size": 10, + } + ) + + # Test greater than + large = self.env["res.partner"].search([("household_size", ">", 5)]) + self.assertGreaterEqual(len(large), 1) + + # Test less than + small = self.env["res.partner"].search([("household_size", "<", 5)]) + self.assertGreaterEqual(len(small), 1) + + # Test range + medium = self.env["res.partner"].search([("household_size", ">=", 2), ("household_size", "<=", 10)]) + self.assertGreaterEqual(len(medium), 3) + + def test_10_household_size_on_individual(self): + """Test household_size field on individual (non-group) partner""" + individual = self.env["res.partner"].create( + { + "name": "Individual Farmer", + "is_group": False, + "is_registrant": True, + "household_size": 4, + } + ) + + # Field should work on individuals too + self.assertEqual(individual.household_size, 4) + + def test_11_household_size_bulk_update(self): + """Test bulk updating household_size""" + partners = self.env["res.partner"].create( + [ + { + "name": "Farm A", + "is_group": True, + "is_registrant": True, + "household_size": 5, + }, + { + "name": "Farm B", + "is_group": True, + "is_registrant": True, + "household_size": 5, + }, + ] + ) + + # Bulk update + partners.write({"household_size": 8}) + + for partner in partners: + self.assertEqual(partner.household_size, 8) + + def test_12_household_size_with_members(self): + """Test household_size field with actual group members""" + farm = self.env["res.partner"].create( + { + "name": "Farm with Members", + "is_group": True, + "is_registrant": True, + "household_size": 0, + } + ) + + # Create members + member1 = self.env["res.partner"].create( + { + "name": "Member 1", + "is_group": False, + "is_registrant": True, + } + ) + member2 = self.env["res.partner"].create( + { + "name": "Member 2", + "is_group": False, + "is_registrant": True, + } + ) + + # Add members to group + self.env["g2p.group.membership"].create( + { + "group": farm.id, + "individual": member1.id, + } + ) + self.env["g2p.group.membership"].create( + { + "group": farm.id, + "individual": member2.id, + } + ) + + # Set household_size to match actual members + farm.write({"household_size": 2}) + self.assertEqual(farm.household_size, 2) + + def test_13_household_size_integer_field_type(self): + """Test that household_size is an integer field""" + partner = self.env["res.partner"].create( + { + "name": "Type Test Farm", + "is_group": True, + "is_registrant": True, + } + ) + + field = partner._fields["household_size"] + self.assertEqual(field.type, "integer") diff --git a/spp_base_farmer_registry_demo/tests/test_farm_asset.py b/spp_base_farmer_registry_demo/tests/test_farm_asset.py new file mode 100644 index 000000000..d42e8a9a7 --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_farm_asset.py @@ -0,0 +1,400 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestFarmAsset(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test farm (group) + cls.farm = cls.env["res.partner"].create( + { + "name": "Test Farm Asset", + "is_group": True, + "is_registrant": True, + } + ) + + def test_01_farm_asset_aquaculture_active_fields(self): + """Test aquaculture active fields""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + "active_area": 100.5, + "active_volume": 500.75, + } + ) + + self.assertEqual(farm_asset.number_active, 5) + self.assertEqual(farm_asset.active_area, 100.5) + self.assertEqual(farm_asset.active_volume, 500.75) + + def test_02_farm_asset_aquaculture_inactive_fields(self): + """Test aquaculture inactive fields""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_inactive": 3, + "inactive_area": 50.25, + "inactive_volume": 250.5, + } + ) + + self.assertEqual(farm_asset.number_inactive, 3) + self.assertEqual(farm_asset.inactive_area, 50.25) + self.assertEqual(farm_asset.inactive_volume, 250.5) + + def test_03_farm_asset_all_aquaculture_fields(self): + """Test all aquaculture fields together""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 10, + "active_area": 200.0, + "active_volume": 1000.0, + "number_inactive": 5, + "inactive_area": 100.0, + "inactive_volume": 500.0, + } + ) + + # Verify active fields + self.assertEqual(farm_asset.number_active, 10) + self.assertEqual(farm_asset.active_area, 200.0) + self.assertEqual(farm_asset.active_volume, 1000.0) + + # Verify inactive fields + self.assertEqual(farm_asset.number_inactive, 5) + self.assertEqual(farm_asset.inactive_area, 100.0) + self.assertEqual(farm_asset.inactive_volume, 500.0) + + def test_04_farm_asset_field_types(self): + """Test farm asset field types""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + } + ) + + # Check field types + self.assertEqual(farm_asset._fields["number_active"].type, "integer") + self.assertEqual(farm_asset._fields["active_area"].type, "float") + self.assertEqual(farm_asset._fields["active_volume"].type, "float") + self.assertEqual(farm_asset._fields["number_inactive"].type, "integer") + self.assertEqual(farm_asset._fields["inactive_area"].type, "float") + self.assertEqual(farm_asset._fields["inactive_volume"].type, "float") + + def test_05_farm_asset_default_values(self): + """Test farm asset default values""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + } + ) + + # Default values should be 0 or False + self.assertIn(farm_asset.number_active, [0, False]) + self.assertIn(farm_asset.active_area, [0.0, False]) + self.assertIn(farm_asset.active_volume, [0.0, False]) + self.assertIn(farm_asset.number_inactive, [0, False]) + self.assertIn(farm_asset.inactive_area, [0.0, False]) + self.assertIn(farm_asset.inactive_volume, [0.0, False]) + + def test_06_farm_asset_update_fields(self): + """Test updating farm asset fields""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + "active_area": 100.0, + } + ) + + # Update fields + farm_asset.write( + { + "number_active": 10, + "active_area": 200.0, + } + ) + + self.assertEqual(farm_asset.number_active, 10) + self.assertEqual(farm_asset.active_area, 200.0) + + def test_07_farm_asset_zero_values(self): + """Test farm asset with zero values""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 0, + "active_area": 0.0, + "active_volume": 0.0, + "number_inactive": 0, + "inactive_area": 0.0, + "inactive_volume": 0.0, + } + ) + + self.assertEqual(farm_asset.number_active, 0) + self.assertEqual(farm_asset.active_area, 0.0) + self.assertEqual(farm_asset.active_volume, 0.0) + + def test_08_farm_asset_large_values(self): + """Test farm asset with large values""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 1000, + "active_area": 10000.5, + "active_volume": 50000.75, + } + ) + + self.assertEqual(farm_asset.number_active, 1000) + self.assertEqual(farm_asset.active_area, 10000.5) + self.assertEqual(farm_asset.active_volume, 50000.75) + + def test_09_farm_asset_decimal_precision(self): + """Test farm asset with decimal precision""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "active_area": 123.456789, + "active_volume": 987.654321, + } + ) + + # Should preserve decimal values + self.assertAlmostEqual(farm_asset.active_area, 123.456789, places=5) + self.assertAlmostEqual(farm_asset.active_volume, 987.654321, places=5) + + def test_10_multiple_farm_assets_same_farm(self): + """Test multiple farm assets for the same farm""" + asset1 = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + "active_area": 100.0, + } + ) + + asset2 = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 10, + "active_area": 200.0, + } + ) + + # Both should belong to the same farm + self.assertEqual(asset1.asset_farm_id, self.farm) + self.assertEqual(asset2.asset_farm_id, self.farm) + + def test_11_farm_assets_different_farms(self): + """Test farm assets for different farms""" + farm2 = self.env["res.partner"].create( + { + "name": "Farm 2", + "is_group": True, + "is_registrant": True, + } + ) + + asset1 = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + + asset2 = self.env["spp.farm.asset"].create( + { + "asset_farm_id": farm2.id, + "number_active": 10, + } + ) + + self.assertEqual(asset1.asset_farm_id, self.farm) + self.assertEqual(asset2.asset_farm_id, farm2) + + def test_12_search_farm_assets_by_farm(self): + """Test searching farm assets by farm""" + self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 10, + } + ) + + # Search for all assets of this farm + assets = self.env["spp.farm.asset"].search([("asset_farm_id", "=", self.farm.id)]) + + self.assertGreaterEqual(len(assets), 2) + + def test_13_search_farm_assets_by_number_active(self): + """Test searching farm assets by number active""" + self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 15, + } + ) + + # Search for assets with more than 10 active + large = self.env["spp.farm.asset"].search([("number_active", ">", 10)]) + self.assertGreaterEqual(len(large), 1) + + def test_14_farm_asset_with_asset_type(self): + """Test farm asset with asset_type field (if exists)""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + + # Check if asset_type field exists (from parent model) + if "asset_type" in farm_asset._fields: + self.assertIn("asset_type", farm_asset._fields) + + def test_15_farm_asset_with_machinery_fields(self): + """Test farm asset with machinery fields (if exists)""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + + # Check if machinery_farm_id field exists + if "machinery_farm_id" in farm_asset._fields: + farm_asset2 = self.env["spp.farm.asset"].create( + { + "machinery_farm_id": self.farm.id, + "number_active": 3, + } + ) + self.assertEqual(farm_asset2.machinery_farm_id, self.farm) + + def test_16_farm_asset_bulk_create(self): + """Test bulk creating farm assets""" + vals_list = [ + { + "asset_farm_id": self.farm.id, + "number_active": i, + "active_area": float(i * 10), + } + for i in range(1, 6) + ] + + assets = self.env["spp.farm.asset"].create(vals_list) + + self.assertEqual(len(assets), 5) + for i, asset in enumerate(assets, 1): + self.assertEqual(asset.number_active, i) + + def test_17_farm_asset_delete(self): + """Test deleting farm asset""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 5, + } + ) + + asset_id = farm_asset.id + farm_asset.unlink() + + # Verify deleted + found = self.env["spp.farm.asset"].search([("id", "=", asset_id)]) + self.assertEqual(len(found), 0) + + def test_18_farm_asset_inherit_check(self): + """Test that spp.farm.asset is properly inherited""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + } + ) + + # Verify our custom fields exist + custom_fields = [ + "number_active", + "active_area", + "active_volume", + "number_inactive", + "inactive_area", + "inactive_volume", + ] + + for field in custom_fields: + self.assertIn(field, farm_asset._fields) + + def test_19_farm_asset_active_vs_inactive(self): + """Test comparing active vs inactive values""" + farm_asset = self.env["spp.farm.asset"].create( + { + "asset_farm_id": self.farm.id, + "number_active": 10, + "active_area": 200.0, + "active_volume": 1000.0, + "number_inactive": 5, + "inactive_area": 100.0, + "inactive_volume": 500.0, + } + ) + + # Active should be greater than inactive + self.assertGreater(farm_asset.number_active, farm_asset.number_inactive) + self.assertGreater(farm_asset.active_area, farm_asset.inactive_area) + self.assertGreater(farm_asset.active_volume, farm_asset.inactive_volume) + + def test_20_farm_asset_comprehensive(self): + """Test creating comprehensive farm asset with all fields""" + vals = { + "asset_farm_id": self.farm.id, + "number_active": 15, + "active_area": 300.5, + "active_volume": 1500.75, + "number_inactive": 8, + "inactive_area": 150.25, + "inactive_volume": 750.5, + } + + # Add optional fields if they exist + if "quantity" in self.env["spp.farm.asset"]._fields: + vals["quantity"] = 5 + + farm_asset = self.env["spp.farm.asset"].create(vals) + + # Verify all fields + self.assertEqual(farm_asset.number_active, 15) + self.assertEqual(farm_asset.active_area, 300.5) + self.assertEqual(farm_asset.active_volume, 1500.75) + self.assertEqual(farm_asset.number_inactive, 8) + self.assertEqual(farm_asset.inactive_area, 150.25) + self.assertEqual(farm_asset.inactive_volume, 750.5) diff --git a/spp_base_farmer_registry_demo/tests/test_farm_details.py b/spp_base_farmer_registry_demo/tests/test_farm_details.py new file mode 100644 index 000000000..9ba3b1dac --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_farm_details.py @@ -0,0 +1,661 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestFarmDetails(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test farm (group) + cls.farm = cls.env["res.partner"].create( + { + "name": "Test Farm", + "is_group": True, + "is_registrant": True, + } + ) + + def test_01_farm_details_lease_fields(self): + """Test farm details lease fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "lease_term": 5, + "lease_agreement_number": "LR-123456", + } + ) + + self.assertEqual(farm_details.lease_term, 5) + self.assertEqual(farm_details.lease_agreement_number, "LR-123456") + + def test_02_farm_details_another_farm_field(self): + """Test another_farm boolean field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "another_farm": True, + } + ) + + self.assertTrue(farm_details.another_farm) + + def test_03_farm_details_crop_fields(self): + """Test crop-related boolean fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "growing_crops_subsistence": True, + "growing_crops_sale": True, + } + ) + + self.assertTrue(farm_details.growing_crops_subsistence) + self.assertTrue(farm_details.growing_crops_sale) + + def test_04_farm_details_livestock_fields(self): + """Test livestock-related boolean fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "rearing_livestock_subsistence": True, + "rearing_livestock_sale": False, + } + ) + + self.assertTrue(farm_details.rearing_livestock_subsistence) + self.assertFalse(farm_details.rearing_livestock_sale) + + def test_05_farm_details_tree_farming(self): + """Test tree farming field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "tree_farming": True, + } + ) + + self.assertTrue(farm_details.tree_farming) + + def test_06_livestock_fertilizer_and_pasture(self): + """Test livestock fertilizer and pasture fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "livestock_fertilizer_for_fodder": True, + "livestock_certified_pasture": False, + } + ) + + self.assertTrue(farm_details.livestock_fertilizer_for_fodder) + self.assertFalse(farm_details.livestock_certified_pasture) + + def test_07_livestock_assisted_reproductive_tech(self): + """Test livestock assisted reproductive technology fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "livestock_assisted_reproductive_health_technology_ai": True, + "livestock_assisted_reproductive_health_technology_animal_horm": True, + "livestock_assisted_reproductive_health_technology_embryo_transf": False, + } + ) + + self.assertTrue(farm_details.livestock_assisted_reproductive_health_technology_ai) + self.assertTrue(farm_details.livestock_assisted_reproductive_health_technology_animal_horm) + self.assertFalse(farm_details.livestock_assisted_reproductive_health_technology_embryo_transf) + + def test_08_livestock_animal_health_services(self): + """Test livestock animal health services fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "livestock_animal_health_services_routine_vaccination": True, + "livestock_animal_health_services_disease_control": True, + } + ) + + self.assertTrue(farm_details.livestock_animal_health_services_routine_vaccination) + self.assertTrue(farm_details.livestock_animal_health_services_disease_control) + + def test_09_aquaculture_type_field(self): + """Test aquaculture type selection field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "aquaculture_type": "freshwater", + } + ) + + self.assertEqual(farm_details.aquaculture_type, "freshwater") + + # Test other types + farm_details.write({"aquaculture_type": "marine"}) + self.assertEqual(farm_details.aquaculture_type, "marine") + + farm_details.write({"aquaculture_type": "brackish"}) + self.assertEqual(farm_details.aquaculture_type, "brackish") + + def test_10_aquaculture_subsistence_and_sale(self): + """Test aquaculture subsistence and sale fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "aquaculture_subsistence": True, + "aquaculture_sale": True, + } + ) + + self.assertTrue(farm_details.aquaculture_subsistence) + self.assertTrue(farm_details.aquaculture_sale) + + def test_11_aquaculture_main_inputs(self): + """Test aquaculture main inputs fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "aquaculture_main_inputs_fingerlings": True, + "aquaculture_main_inputs_feeds": True, + "aquaculture_main_inputs_fertilizers": False, + } + ) + + self.assertTrue(farm_details.aquaculture_main_inputs_fingerlings) + self.assertTrue(farm_details.aquaculture_main_inputs_feeds) + self.assertFalse(farm_details.aquaculture_main_inputs_fertilizers) + + def test_12_aquaculture_production_level(self): + """Test aquaculture production level field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "aquaculture_production_level": "extensive", + } + ) + + self.assertEqual(farm_details.aquaculture_production_level, "extensive") + + # Test other levels + for level in ["semi-intensive", "intensive"]: + farm_details.write({"aquaculture_production_level": level}) + self.assertEqual(farm_details.aquaculture_production_level, level) + + def test_13_aquaculture_esp_beneficiary(self): + """Test aquaculture ESP beneficiary field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "aquaculture_beneficiary_esp": True, + } + ) + + self.assertTrue(farm_details.aquaculture_beneficiary_esp) + + def test_14_farm_technology_power_source(self): + """Test farm technology power source field""" + power_sources = [ + "manual labor", + "animal drought", + "motorized", + "wind", + "solar", + "grid electricity", + "other", + ] + + for source in power_sources: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "farm_technology_power_source": source, + } + ) + self.assertEqual(farm_details.farm_technology_power_source, source) + + def test_15_farm_technology_labor_source(self): + """Test farm technology labor source field""" + labor_sources = ["family members", "temporary hired help", "permanent hired help"] + + for source in labor_sources: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "farm_technology_labor_source": source, + } + ) + self.assertEqual(farm_details.farm_technology_labor_source, source) + + def test_16_farm_technology_own_equipment(self): + """Test farm technology own equipment field""" + equipment_owners = ["self", "community", "hirer"] + + for owner in equipment_owners: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "farm_technology_own_equipment": owner, + } + ) + self.assertEqual(farm_details.farm_technology_own_equipment, owner) + + def test_17_farm_technology_structures(self): + """Test farm technology structure boolean fields""" + structure_fields = { + "farm_technology_structure_spray_race": True, + "farm_technology_structure_animal_dip": False, + "farm_technology_structure_loading_ramp": True, + "farm_technology_structure_zero_grazing_unit": False, + "farm_technology_structure_hay_store": True, + "farm_technology_structure_feed_store": True, + "farm_technology_structure_sick_bay": False, + "farm_technology_structure_cattle_boma": True, + "farm_technology_structure_milking_parlor": False, + "farm_technology_structure_animal_crush": True, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **structure_fields, + } + ) + + for field, expected_value in structure_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_18_more_farm_technology_structures(self): + """Test additional farm technology structure fields""" + structure_fields = { + "farm_technology_structure_traditional_granary": True, + "farm_technology_structure_modern_granary": False, + "farm_technology_structure_general_store": True, + "farm_technology_structure_hay_bailers": False, + "farm_technology_structure_green_house": True, + "farm_technology_structure_bee_house": False, + "farm_technology_structure_hatchery": True, + "farm_technology_structure_apriary": False, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **structure_fields, + } + ) + + for field, expected_value in structure_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_19_land_water_management_practices(self): + """Test land and water management practice fields""" + management_fields = { + "land_water_management_crop_rotation": True, + "land_water_management_green_cover_crop": False, + "land_water_management_contour_ploughing": True, + "land_water_management_deep_ripping": False, + "land_water_management_grass_strips": True, + "land_water_management_trash_line": False, + "land_water_management_cambered_beds": True, + "land_water_management_biogas_production": False, + "land_water_management_mulching": True, + "land_water_management_minimum_tillage": False, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **management_fields, + } + ) + + for field, expected_value in management_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_20_more_land_water_management(self): + """Test additional land and water management fields""" + management_fields = { + "land_water_management_manuring_composting": True, + "land_water_management_organic_farming": False, + "land_water_management_terracing": True, + "land_water_management_water_harvesting": False, + "land_water_management_zai_pits": True, + "land_water_management_cut_off_drains": False, + "land_water_management_conservation_agriculture": True, + "land_water_management_integrated_pest_management": False, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **management_fields, + } + ) + + for field, expected_value in management_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_21_land_water_management_subsidized_and_lime(self): + """Test subsidized fertilizer and lime usage fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_subsidized_fertilizer": True, + "land_water_management_use_lime": False, + "land_water_management_soil_testing": True, + "land_water_management_undertake_irrigation": True, + } + ) + + self.assertTrue(farm_details.land_water_management_subsidized_fertilizer) + self.assertFalse(farm_details.land_water_management_use_lime) + self.assertTrue(farm_details.land_water_management_soil_testing) + self.assertTrue(farm_details.land_water_management_undertake_irrigation) + + def test_22_irrigation_type_field(self): + """Test irrigation type selection field""" + irrigation_types = [ + "furrow_canal", + "basin", + "bucket", + "centre_pivot", + "drip", + "furrow", + "sprinkler", + "flooding", + "other", + ] + + for irr_type in irrigation_types: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_irrigation_type": irr_type, + } + ) + self.assertEqual(farm_details.land_water_management_irrigation_type, irr_type) + + def test_23_irrigation_source_field(self): + """Test irrigation source selection field""" + irrigation_sources = [ + "locality water supply", + "water trucking", + "rain", + "natural rivers and streams", + "man made dam", + "shallow well or borehole", + "adjacent water body", + "harvested water", + "road runoff", + "water pan", + ] + + for source in irrigation_sources: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_irrigation_source": source, + } + ) + self.assertEqual(farm_details.land_water_management_irrigation_source, source) + + def test_24_irrigation_area_and_project(self): + """Test irrigation area and project fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_total_irrigated_area": 25.5, + "land_water_management_type_of_irrigation_project": "public irrigation scheme", + "land_water_management_type_of_irrigation_project_name": "Test Project", + } + ) + + self.assertEqual(farm_details.land_water_management_total_irrigated_area, 25.5) + self.assertEqual(farm_details.land_water_management_type_of_irrigation_project, "public irrigation scheme") + self.assertEqual(farm_details.land_water_management_type_of_irrigation_project_name, "Test Project") + + def test_25_irrigation_implementing_body(self): + """Test irrigation implementing body field""" + implementing_bodies = [ + "county government", + "national government", + "implementing agents", + "national govt ministry", + "self", + "other", + ] + + for body in implementing_bodies: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_implementing_body": body, + } + ) + self.assertEqual(farm_details.land_water_management_implementing_body, body) + + def test_26_irrigation_scheme_membership(self): + """Test irrigation scheme membership field""" + memberships = ["full member", "out grower"] + + for membership in memberships: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "land_water_management_irrigation_scheme_membership": membership, + } + ) + self.assertEqual(farm_details.land_water_management_irrigation_scheme_membership, membership) + + def test_27_financial_services_main_income_source(self): + """Test financial services main income source field""" + income_sources = [ + "sale of farming produce", + "non-farm trading", + "salary from employment elsewhere", + "casual labor elsewhere", + "pension", + "remittances", + "cash transfer", + "other", + ] + + for source in income_sources: + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "financial_services_main_income_source": source, + } + ) + self.assertEqual(farm_details.financial_services_main_income_source, source) + + def test_28_financial_services_income_percentage(self): + """Test financial services income percentage field""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "financial_services_percentage_of_income_from_farming": 75.5, + } + ) + + self.assertEqual(farm_details.financial_services_percentage_of_income_from_farming, 75.5) + + def test_29_financial_services_organization_memberships(self): + """Test financial services organization membership fields""" + org_fields = { + "financial_services_vulnerable_marginalized_group": True, + "financial_services_faith_based_organization": False, + "financial_services_community_based_organization": True, + "financial_services_producer_group": False, + "financial_services_marketing_group": True, + "financial_services_table_banking_group": False, + "financial_services_common_interest_group": True, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **org_fields, + } + ) + + for field, expected_value in org_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_30_financial_services_finance_sources(self): + """Test financial services finance source fields""" + finance_fields = { + "financial_services_mobile_money_saving_loans": True, + "financial_services_farmer_organization": False, + "financial_services_other_money_lenders": True, + "financial_services_self_salary_or_savings": False, + "financial_services_family": True, + "financial_services_commercial_bank": False, + "financial_services_business_partners": True, + "financial_services_savings_credit_groups": False, + "financial_services_cooperatives": True, + "financial_services_micro_finance_institutions": False, + "financial_services_non_governmental_donors": True, + } + + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + **finance_fields, + } + ) + + for field, expected_value in finance_fields.items(): + self.assertEqual(getattr(farm_details, field), expected_value) + + def test_31_financial_services_insurance_fields(self): + """Test financial services insurance fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "financial_services_crop_insurance": True, + "financial_services_livestock_insurance": False, + "financial_services_fish_insurance": True, + "financial_services_farm_building_insurance": False, + } + ) + + self.assertTrue(farm_details.financial_services_crop_insurance) + self.assertFalse(farm_details.financial_services_livestock_insurance) + self.assertTrue(farm_details.financial_services_fish_insurance) + self.assertFalse(farm_details.financial_services_farm_building_insurance) + + def test_32_financial_services_records_and_information(self): + """Test financial services records and information fields""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "financial_services_written_farm_records": True, + "financial_services_main_source_of_information_on_good_agricultu": "extension services", + "financial_services_mode_of_extension_service": "face-to-face", + "financial_services_main_extension_service_provider": "county government", + } + ) + + self.assertTrue(farm_details.financial_services_written_farm_records) + self.assertEqual( + farm_details.financial_services_main_source_of_information_on_good_agricultu, "extension services" + ) + self.assertEqual(farm_details.financial_services_mode_of_extension_service, "face-to-face") + self.assertEqual(farm_details.financial_services_main_extension_service_provider, "county government") + + def test_33_create_multiple_farm_details(self): + """Test creating multiple farm details for different farms""" + farm1 = self.env["res.partner"].create( + { + "name": "Farm 1", + "is_group": True, + "is_registrant": True, + } + ) + + farm2 = self.env["res.partner"].create( + { + "name": "Farm 2", + "is_group": True, + "is_registrant": True, + } + ) + + details1 = self.env["spp.farm.details"].create( + { + "details_farm_id": farm1.id, + "lease_term": 5, + } + ) + + details2 = self.env["spp.farm.details"].create( + { + "details_farm_id": farm2.id, + "lease_term": 10, + } + ) + + self.assertEqual(details1.lease_term, 5) + self.assertEqual(details2.lease_term, 10) + + def test_34_update_farm_details(self): + """Test updating farm details""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "lease_term": 3, + "aquaculture_type": "freshwater", + } + ) + + farm_details.write( + { + "lease_term": 7, + "aquaculture_type": "marine", + } + ) + + self.assertEqual(farm_details.lease_term, 7) + self.assertEqual(farm_details.aquaculture_type, "marine") + + def test_35_farm_details_comprehensive(self): + """Test creating farm details with many fields at once""" + farm_details = self.env["spp.farm.details"].create( + { + "details_farm_id": self.farm.id, + "lease_term": 5, + "lease_agreement_number": "LR-789012", + "another_farm": True, + "growing_crops_subsistence": True, + "growing_crops_sale": True, + "rearing_livestock_subsistence": True, + "rearing_livestock_sale": False, + "tree_farming": True, + "aquaculture_type": "freshwater", + "aquaculture_subsistence": True, + "farm_technology_power_source": "solar", + "land_water_management_undertake_irrigation": True, + "land_water_management_irrigation_type": "drip", + "financial_services_main_income_source": "sale of farming produce", + "financial_services_crop_insurance": True, + } + ) + + # Verify all fields were set correctly + self.assertEqual(farm_details.lease_term, 5) + self.assertTrue(farm_details.another_farm) + self.assertTrue(farm_details.growing_crops_subsistence) + self.assertEqual(farm_details.aquaculture_type, "freshwater") + self.assertEqual(farm_details.farm_technology_power_source, "solar") + self.assertEqual(farm_details.land_water_management_irrigation_type, "drip") + self.assertTrue(farm_details.financial_services_crop_insurance) diff --git a/spp_base_farmer_registry_demo/tests/test_farmer.py b/spp_base_farmer_registry_demo/tests/test_farmer.py new file mode 100644 index 000000000..ce4a89322 --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_farmer.py @@ -0,0 +1,310 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestFarmer(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + def test_01_farmer_marital_status_fields(self): + """Test farmer marital status field extensions""" + partner = self.env["res.partner"].create( + { + "name": "Test Farmer Partner", + "is_registrant": True, + } + ) + + # Test that marital_status field exists and has the extended selections + self.assertIn("marital_status", partner._fields) + + # Test setting married_monogamous + partner.write({"marital_status": "married_monogamous"}) + self.assertEqual(partner.marital_status, "married_monogamous") + + # Test setting married_polygamous + partner.write({"marital_status": "married_polygamous"}) + self.assertEqual(partner.marital_status, "married_polygamous") + + def test_02_farmer_education_level_fields(self): + """Test farmer education level field extensions""" + partner = self.env["res.partner"].create( + { + "name": "Test Farmer Education", + "is_registrant": True, + } + ) + + # Test that highest_education_level field exists + self.assertIn("highest_education_level", partner._fields) + + # Test setting various education levels + education_levels = ["none", "primary", "secondary", "certificate", "diploma", "university", "tertiary"] + + for level in education_levels: + partner.write({"highest_education_level": level}) + self.assertEqual(partner.highest_education_level, level) + + def test_03_farmer_marital_status_filter(self): + """Test that marital_status field allows filtering""" + partner = self.env["res.partner"].create( + { + "name": "Test Farmer Filter", + "is_registrant": True, + "marital_status": "married_monogamous", + } + ) + + # Test filtering + found = self.env["res.partner"].search([("marital_status", "=", "married_monogamous")]) + self.assertIn(partner, found) + + def test_04_farmer_education_level_filter(self): + """Test that highest_education_level field allows filtering""" + partner = self.env["res.partner"].create( + { + "name": "Test Education Filter", + "is_registrant": True, + "highest_education_level": "university", + } + ) + + # Test filtering + found = self.env["res.partner"].search([("highest_education_level", "=", "university")]) + self.assertIn(partner, found) + + def test_05_spp_farmer_marital_status(self): + """Test spp.farmer model marital status extensions""" + # Check if spp.farmer model exists (temp farmer) + if "spp.farmer" not in self.env: + self.skipTest("spp.farmer model not available") + + # Create a farmer record + farmer = self.env["spp.farmer"].create( + { + "farmer_given_name": "Test", + "farmer_family_name": "Farmer", + } + ) + + # Test that farmer_marital_status field exists and has extended selections + self.assertIn("farmer_marital_status", farmer._fields) + + # Test setting values + farmer.write({"farmer_marital_status": "married_monogamous"}) + self.assertEqual(farmer.farmer_marital_status, "married_monogamous") + + def test_06_spp_farmer_education_level(self): + """Test spp.farmer model education level extensions""" + # Check if spp.farmer model exists + if "spp.farmer" not in self.env: + self.skipTest("spp.farmer model not available") + + # Create a farmer record + farmer = self.env["spp.farmer"].create( + { + "farmer_given_name": "Test", + "farmer_family_name": "Farmer", + } + ) + + # Test that farmer_highest_education_level field exists + self.assertIn("farmer_highest_education_level", farmer._fields) + + # Test setting various education levels + education_levels = ["none", "primary", "secondary", "certificate", "diploma", "university", "tertiary"] + + for level in education_levels: + farmer.write({"farmer_highest_education_level": level}) + self.assertEqual(farmer.farmer_highest_education_level, level) + + def test_07_multiple_farmers_with_different_status(self): + """Test multiple farmers with different marital statuses""" + farmer1 = self.env["res.partner"].create( + { + "name": "Monogamous Farmer", + "is_registrant": True, + "marital_status": "married_monogamous", + } + ) + + farmer2 = self.env["res.partner"].create( + { + "name": "Polygamous Farmer", + "is_registrant": True, + "marital_status": "married_polygamous", + } + ) + + farmer3 = self.env["res.partner"].create( + { + "name": "Single Farmer", + "is_registrant": True, + "marital_status": "single", + } + ) + + # Verify each farmer has correct status + self.assertEqual(farmer1.marital_status, "married_monogamous") + self.assertEqual(farmer2.marital_status, "married_polygamous") + self.assertEqual(farmer3.marital_status, "single") + + def test_08_multiple_farmers_with_different_education(self): + """Test multiple farmers with different education levels""" + farmers = [] + education_levels = ["none", "primary", "secondary", "university"] + + for i, level in enumerate(education_levels): + farmer = self.env["res.partner"].create( + { + "name": f"Farmer {i}", + "is_registrant": True, + "highest_education_level": level, + } + ) + farmers.append(farmer) + + # Verify each farmer has correct education level + for i, level in enumerate(education_levels): + self.assertEqual(farmers[i].highest_education_level, level) + + def test_09_search_farmers_by_marital_status(self): + """Test searching farmers by marital status""" + # Create farmers with different statuses + self.env["res.partner"].create( + { + "name": "Monogamous 1", + "is_registrant": True, + "marital_status": "married_monogamous", + } + ) + self.env["res.partner"].create( + { + "name": "Monogamous 2", + "is_registrant": True, + "marital_status": "married_monogamous", + } + ) + self.env["res.partner"].create( + { + "name": "Polygamous 1", + "is_registrant": True, + "marital_status": "married_polygamous", + } + ) + + # Search for monogamous farmers + monogamous = self.env["res.partner"].search([("marital_status", "=", "married_monogamous")]) + self.assertGreaterEqual(len(monogamous), 2) + + # Search for polygamous farmers + polygamous = self.env["res.partner"].search([("marital_status", "=", "married_polygamous")]) + self.assertGreaterEqual(len(polygamous), 1) + + def test_10_search_farmers_by_education_level(self): + """Test searching farmers by education level""" + # Create farmers with different education levels + self.env["res.partner"].create( + { + "name": "University Farmer 1", + "is_registrant": True, + "highest_education_level": "university", + } + ) + self.env["res.partner"].create( + { + "name": "University Farmer 2", + "is_registrant": True, + "highest_education_level": "university", + } + ) + self.env["res.partner"].create( + { + "name": "Primary Farmer", + "is_registrant": True, + "highest_education_level": "primary", + } + ) + + # Search for university educated farmers + university = self.env["res.partner"].search([("highest_education_level", "=", "university")]) + self.assertGreaterEqual(len(university), 2) + + # Search for primary educated farmers + primary = self.env["res.partner"].search([("highest_education_level", "=", "primary")]) + self.assertGreaterEqual(len(primary), 1) + + def test_11_farmer_field_update(self): + """Test updating farmer fields""" + farmer = self.env["res.partner"].create( + { + "name": "Updateable Farmer", + "is_registrant": True, + "marital_status": "single", + "highest_education_level": "none", + } + ) + + # Update fields + farmer.write( + { + "marital_status": "married_monogamous", + "highest_education_level": "university", + } + ) + + self.assertEqual(farmer.marital_status, "married_monogamous") + self.assertEqual(farmer.highest_education_level, "university") + + def test_12_farmer_without_marital_status(self): + """Test farmer without marital status set""" + farmer = self.env["res.partner"].create( + { + "name": "No Status Farmer", + "is_registrant": True, + } + ) + + # marital_status can be False + self.assertIn(farmer.marital_status, [False, None, ""]) + + def test_13_farmer_without_education_level(self): + """Test farmer without education level set""" + farmer = self.env["res.partner"].create( + { + "name": "No Education Farmer", + "is_registrant": True, + } + ) + + # highest_education_level can be False + self.assertIn(farmer.highest_education_level, [False, None, ""]) + + def test_14_inherit_model_check(self): + """Test that res.partner is properly inherited""" + farmer = self.env["res.partner"].create( + { + "name": "Inherit Test Farmer", + "is_registrant": True, + } + ) + + # Verify standard partner fields still exist + self.assertIn("name", farmer._fields) + self.assertIn("email", farmer._fields) + self.assertIn("is_registrant", farmer._fields) + + # Verify our extended fields are there + self.assertIn("marital_status", farmer._fields) + self.assertIn("highest_education_level", farmer._fields) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py new file mode 100644 index 000000000..eb1c40221 --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -0,0 +1,902 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import datetime +import logging + +from faker import Faker + +from odoo.exceptions import ValidationError +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestFarmerRegistryDemoDataGenerator(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + # Set context to avoid job queue delay + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test country with faker locale + cls.test_country = cls.env["res.country"].create( + { + "name": "Test Country Farmer Registry", + "code": "TF", + "faker_locale": "en_US", + "faker_locale_available": True, + "lat_min": -10.0, + "lat_max": 10.0, + "lon_min": -10.0, + "lon_max": 10.0, + } + ) + + # Create test group type + cls.group_type = cls.env["g2p.group.kind"].create( + { + "name": "Test Farmer Group Type", + } + ) + + def test_01_season_date_validation(self): + """Test season date validation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Season Validation", + "locale_origin": self.test_country.id, + "season_start_date": datetime.date(2024, 12, 31), + "season_end_date": datetime.date(2024, 1, 1), + } + ) + + with self.assertRaises(ValidationError): + generator._check_season_dates() + + def test_02_farmer_registry_specific_fields(self): + """Test farmer registry specific fields exist""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farmer Fields", + "locale_origin": self.test_country.id, + } + ) + + # Check that farmer registry fields exist + self.assertIn("percentage_with_farm_details", generator._fields) + self.assertIn("percentage_with_land_records", generator._fields) + self.assertIn("percentage_with_farm_assets", generator._fields) + self.assertIn("percentage_with_agricultural_activities", generator._fields) + self.assertIn("max_farm_size", generator._fields) + self.assertIn("min_farm_size", generator._fields) + self.assertIn("season_name", generator._fields) + self.assertIn("season_start_date", generator._fields) + self.assertIn("season_end_date", generator._fields) + + def test_03_get_group_vals_with_farmer_fields(self): + """Test group values generation includes farmer registry fields""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Group Vals Farmer", + "locale_origin": self.test_country.id, + "group_type_id": self.group_type.id, + } + ) + + fake = Faker("en_US") + group_vals = generator.get_group_vals(fake) + + # Check that farmer registry fields are in group vals + self.assertIn("household_size", group_vals) + self.assertIn("experience_years", group_vals) + self.assertIn("formal_agricultural_training", group_vals) + self.assertIn("farmer_household_size", group_vals) + self.assertIn("farmer_postal_address", group_vals) + self.assertIn("marital_status", group_vals) + self.assertIn("highest_education_level", group_vals) + self.assertIn("farmer_family_name", group_vals) + self.assertIn("farmer_given_name", group_vals) + self.assertIn("farmer_sex", group_vals) + self.assertIn("farmer_birthdate", group_vals) + + def test_04_get_individual_vals_with_farmer_fields(self): + """Test individual values generation includes farmer registry fields""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Individual Vals Farmer", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + individual_vals = generator.get_individual_vals(fake) + + # Check that farmer registry fields are in individual vals + self.assertIn("experience_years", individual_vals) + self.assertIn("formal_agricultural_training", individual_vals) + self.assertIn("farmer_household_size", individual_vals) + self.assertIn("farmer_postal_address", individual_vals) + self.assertIn("marital_status", individual_vals) + self.assertIn("highest_education_level", individual_vals) + + def test_05_generate_species_data(self): + """Test species data generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Species Generator", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Clear any existing test species + test_species = self.env["spp.farm.species"].search([("name", "in", ["Rice", "Wheat", "Cattle", "Tilapia"])]) + if test_species: + test_species.unlink() + + generator._generate_species_data(fake) + + # Verify species were created + crop_species = self.env["spp.farm.species"].search([("species_type", "=", "crop")]) + livestock_species = self.env["spp.farm.species"].search([("species_type", "=", "livestock")]) + aqua_species = self.env["spp.farm.species"].search([("species_type", "=", "aquaculture")]) + + self.assertGreaterEqual(len(crop_species), 1) + self.assertGreaterEqual(len(livestock_species), 1) + self.assertGreaterEqual(len(aqua_species), 1) + + def test_06_generate_chemical_data(self): + """Test chemical data generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Chemical Generator", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Clear existing test chemicals + test_chemicals = self.env["spp.farm.chemical"].search([("name", "in", ["Herbicide", "Insecticide"])]) + if test_chemicals: + test_chemicals.unlink() + + generator._generate_chemical_data(fake) + + # Verify chemicals were created + chemicals = self.env["spp.farm.chemical"].search([]) + self.assertGreaterEqual(len(chemicals), 1) + + def test_07_generate_fertilizer_data(self): + """Test fertilizer data generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Fertilizer Generator", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Clear existing test fertilizers + test_fertilizers = self.env["spp.fertilizer"].search([("name", "in", ["Urea", "Compost"])]) + if test_fertilizers: + test_fertilizers.unlink() + + generator._generate_fertilizer_data(fake) + + # Verify fertilizers were created + fertilizers = self.env["spp.fertilizer"].search([]) + self.assertGreaterEqual(len(fertilizers), 1) + + def test_08_generate_feed_items_data(self): + """Test feed items data generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Feed Items Generator", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Clear existing test feed items + test_feeds = self.env["spp.feed.items"].search([("name", "in", ["Grass", "Hay"])]) + if test_feeds: + test_feeds.unlink() + + generator._generate_feed_items_data(fake) + + # Verify feed items were created + feed_items = self.env["spp.feed.items"].search([]) + self.assertGreaterEqual(len(feed_items), 1) + + def test_09_generate_season_data(self): + """Test season data generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Season Generator", + "locale_origin": self.test_country.id, + "season_name": "Test Season 2024", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Clear any existing test season + test_season = self.env["spp.farm.season"].search([("name", "=", "Test Season 2024")]) + if test_season: + test_season.unlink() + + season = generator._generate_season_data(fake) + + self.assertIsNotNone(season) + self.assertEqual(season.name, "Test Season 2024") + self.assertEqual(season.date_start, datetime.date(2024, 1, 1)) + self.assertEqual(season.date_end, datetime.date(2024, 12, 31)) + self.assertEqual(season.state, "active") + + def test_10_get_farm_details_vals(self): + """Test farm details values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farm Details Vals", + "locale_origin": self.test_country.id, + "min_farm_size": 1.0, + "max_farm_size": 50.0, + } + ) + + fake = Faker("en_US") + farm_vals = generator._get_farm_details_vals(fake) + + # Check required fields + self.assertIn("details_farm_type", farm_vals) + self.assertIn("farm_total_size", farm_vals) + self.assertIn("details_legal_status", farm_vals) + + # Check that farm size is within bounds + self.assertGreaterEqual(farm_vals["farm_total_size"], 1.0) + self.assertLessEqual(farm_vals["farm_total_size"], 50.0) + + def test_11_get_land_record_vals(self): + """Test land record values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Land Record Vals", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Ensure species data exists + generator._generate_species_data(fake) + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + land_vals = generator._get_land_record_vals(fake, group) + + # Check required fields + self.assertIn("land_farm_id", land_vals) + self.assertEqual(land_vals["land_farm_id"], group.id) + self.assertIn("land_name", land_vals) + self.assertIn("land_acreage", land_vals) + self.assertIn("land_use", land_vals) + self.assertIn("owner_id", land_vals) + + def test_12_get_farm_asset_vals(self): + """Test farm asset values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farm Asset Vals", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + asset_vals = generator._get_farm_asset_vals(fake, group) + + # Check required fields + self.assertIn("asset_farm_id", asset_vals) + self.assertEqual(asset_vals["asset_farm_id"], group.id) + self.assertIn("quantity", asset_vals) + self.assertGreaterEqual(asset_vals["quantity"], 1) + + def test_13_get_machinery_vals(self): + """Test machinery values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Machinery Vals", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + machinery_vals = generator._get_machinery_vals(fake, group) + + # Check required fields + self.assertIn("machinery_farm_id", machinery_vals) + self.assertEqual(machinery_vals["machinery_farm_id"], group.id) + self.assertIn("quantity", machinery_vals) + self.assertIn("machine_working_status", machinery_vals) + + def test_14_get_agricultural_activity_vals(self): + """Test agricultural activity values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Activity Vals", + "locale_origin": self.test_country.id, + "season_name": "Test Season Activity", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Create season + season = generator._generate_season_data(fake) + + # Ensure species data exists + generator._generate_species_data(fake) + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + activity_vals = generator._get_agricultural_activity_vals(fake, group, season.id) + + # Check required fields + self.assertIn("activity_type", activity_vals) + self.assertIn("purpose", activity_vals) + self.assertIn("season_id", activity_vals) + self.assertEqual(activity_vals["season_id"], season.id) + + def test_15_get_crop_activity_vals(self): + """Test crop activity values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Crop Activity", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Ensure required data exists + generator._generate_species_data(fake) + generator._generate_chemical_data(fake) + generator._generate_fertilizer_data(fake) + + crop_vals = generator._get_crop_activity_vals(fake) + + # Check crop-specific fields + self.assertIn("cultivation_water_source", crop_vals) + self.assertIn("cultivation_production_system", crop_vals) + + def test_16_get_livestock_activity_vals(self): + """Test livestock activity values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Livestock Activity", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Ensure required data exists + generator._generate_species_data(fake) + generator._generate_feed_items_data(fake) + + livestock_vals = generator._get_livestock_activity_vals(fake) + + # Check livestock-specific fields + self.assertIn("livestock_production_system", livestock_vals) + + def test_17_get_aquaculture_activity_vals(self): + """Test aquaculture activity values generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Aquaculture Activity", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + + # Ensure required data exists + generator._generate_species_data(fake) + + aqua_vals = generator._get_aquaculture_activity_vals(fake) + + # Check aquaculture-specific fields + self.assertIn("aquaculture_production_system", aqua_vals) + self.assertIn("aquaculture_number_of_fingerlings", aqua_vals) + self.assertGreaterEqual(aqua_vals["aquaculture_number_of_fingerlings"], 100) + + def test_18_get_random_species(self): + """Test random species retrieval""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Random Species", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + generator._generate_species_data(fake) + + # Test each species type + crop_species = generator._get_random_species("crop") + livestock_species = generator._get_random_species("livestock") + aqua_species = generator._get_random_species("aquaculture") + + self.assertIsNotNone(crop_species) + self.assertIsNotNone(livestock_species) + self.assertIsNotNone(aqua_species) + + def test_19_get_random_chemicals(self): + """Test random chemicals retrieval""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Random Chemicals", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + generator._generate_chemical_data(fake) + + chemicals = generator._get_random_chemicals(2) + + self.assertIsInstance(chemicals, list) + self.assertEqual(len(chemicals), 1) # Returns list with one tuple + self.assertEqual(chemicals[0][0], 6) # Command code 6 = set + + def test_20_get_random_fertilizers(self): + """Test random fertilizers retrieval""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Random Fertilizers", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + generator._generate_fertilizer_data(fake) + + fertilizers = generator._get_random_fertilizers(2) + + self.assertIsInstance(fertilizers, list) + self.assertEqual(len(fertilizers), 1) + self.assertEqual(fertilizers[0][0], 6) + + def test_21_get_random_feed_items(self): + """Test random feed items retrieval""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Random Feed Items", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + generator._generate_feed_items_data(fake) + + feed_items = generator._get_random_feed_items(2) + + self.assertIsInstance(feed_items, list) + self.assertEqual(len(feed_items), 1) + self.assertEqual(feed_items[0][0], 6) + + def test_22_get_random_season(self): + """Test random season retrieval""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Random Season", + "locale_origin": self.test_country.id, + "season_name": "Test Season Random", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + season_id = generator._get_random_season() + + self.assertIsNotNone(season_id) + + def test_23_generate_groups_with_farm_details(self): + """Test group generation with farm details""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Group Farm Details", + "locale_origin": self.test_country.id, + "percentage_with_farm_details": 100, + "percentage_with_land_records": 0, + "percentage_with_farm_assets": 0, + "percentage_with_agricultural_activities": 0, + "season_name": "Test Season Group", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Generate required reference data + generator._generate_species_data(fake) + generator._generate_season_data(fake) + + group = generator.generate_groups(fake) + + self.assertTrue(group.is_group) + self.assertTrue(group.is_registrant) + # Farm details should be created (if farm_detail_id field exists) + if hasattr(group, "farm_detail_id"): + self.assertTrue(group.farm_detail_id or True) # May or may not have depending on module setup + + def test_24_generate_farm_details(self): + """Test farm details generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farm Details Gen", + "locale_origin": self.test_country.id, + "percentage_with_land_records": 0, + "percentage_with_farm_assets": 0, + "percentage_with_agricultural_activities": 0, + } + ) + + fake = Faker("en_US") + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + generator._generate_farm_details(fake, group) + + # Verify farm details were created + farm_details = self.env["spp.farm.details"].search([("details_farm_id", "=", group.id)]) + self.assertGreaterEqual(len(farm_details), 1) + + def test_25_generate_land_records(self): + """Test land records generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Land Records Gen", + "locale_origin": self.test_country.id, + "max_land_parcels_per_farm": 3, + } + ) + + fake = Faker("en_US") + generator._generate_species_data(fake) + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + generator._generate_land_records(fake, group) + + # Verify land records were created + land_records = self.env["spp.land.record"].search([("land_farm_id", "=", group.id)]) + self.assertGreaterEqual(len(land_records), 1) + self.assertLessEqual(len(land_records), 3) + + def test_26_generate_farm_assets(self): + """Test farm assets generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farm Assets Gen", + "locale_origin": self.test_country.id, + "max_assets_per_farm": 3, + } + ) + + fake = Faker("en_US") + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + generator._generate_farm_assets(fake, group) + + # Verify farm assets were created + farm_assets = self.env["spp.farm.asset"].search( + ["|", ("asset_farm_id", "=", group.id), ("machinery_farm_id", "=", group.id)] + ) + self.assertGreaterEqual(len(farm_assets), 1) + + def test_27_generate_agricultural_activities(self): + """Test agricultural activities generation""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Agricultural Activities Gen", + "locale_origin": self.test_country.id, + "max_activities_per_farm": 3, + "season_name": "Test Season Activities", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Generate required reference data + generator._generate_species_data(fake) + generator._generate_chemical_data(fake) + generator._generate_fertilizer_data(fake) + generator._generate_feed_items_data(fake) + generator._generate_season_data(fake) + + # Create a test group + group = self.env["res.partner"].create( + { + "name": "Test Farm Group", + "is_group": True, + "is_registrant": True, + } + ) + + generator._generate_agricultural_activities(fake, group) + + # Verify agricultural activities were created + activities = self.env["spp.farm.activity"].search( + [ + "|", + "|", + ("crop_farm_id", "=", group.id), + ("live_farm_id", "=", group.id), + ("aqua_farm_id", "=", group.id), + ] + ) + self.assertGreaterEqual(len(activities), 1) + + def test_28_percentage_fields_defaults(self): + """Test that percentage fields have correct defaults""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Percentage Defaults", + "locale_origin": self.test_country.id, + } + ) + + self.assertEqual(generator.percentage_with_farm_details, 100) + self.assertEqual(generator.percentage_with_land_records, 100) + self.assertEqual(generator.percentage_with_farm_assets, 100) + self.assertEqual(generator.percentage_with_agricultural_activities, 100) + + def test_29_farm_size_fields_defaults(self): + """Test that farm size fields have correct defaults""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Farm Size Defaults", + "locale_origin": self.test_country.id, + } + ) + + self.assertEqual(generator.max_farm_size, 100.0) + self.assertEqual(generator.min_farm_size, 0.5) + self.assertEqual(generator.max_land_parcels_per_farm, 5) + self.assertEqual(generator.max_assets_per_farm, 10) + self.assertEqual(generator.max_activities_per_farm, 8) + + def test_30_season_name_default(self): + """Test that season name has a default value""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Season Name Default", + "locale_origin": self.test_country.id, + } + ) + + self.assertIsNotNone(generator.season_name) + self.assertIn("Demo Season", generator.season_name) + + def test_31_demo_farm_details_vals_livestock(self): + """Test demo farm details values for livestock farm""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Demo Farm Details Livestock", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + vals = generator._get_demo_farm_details_vals(fake, "livestock") + + # Check livestock-specific fields might be present + self.assertIsInstance(vals, dict) + + def test_32_demo_farm_details_vals_aquaculture(self): + """Test demo farm details values for aquaculture farm""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Demo Farm Details Aquaculture", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + vals = generator._get_demo_farm_details_vals(fake, "aquaculture") + + # Check aquaculture-specific fields might be present + self.assertIsInstance(vals, dict) + + def test_33_demo_farm_details_vals_crop(self): + """Test demo farm details values for crop farm""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Demo Farm Details Crop", + "locale_origin": self.test_country.id, + } + ) + + fake = Faker("en_US") + vals = generator._get_demo_farm_details_vals(fake, "crop") + + # Check crop farm fields + self.assertIsInstance(vals, dict) + + def test_34_season_data_reuse_existing(self): + """Test that existing season is reused if available""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Season Reuse", + "locale_origin": self.test_country.id, + "season_name": "Test Season Reuse", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Generate season first time + season1 = generator._generate_season_data(fake) + + # Generate season second time + season2 = generator._generate_season_data(fake) + + # Should be the same season + self.assertEqual(season1.id, season2.id) + + def test_35_edge_case_zero_percentage_farm_details(self): + """Test with 0% percentage for farm details""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Zero Farm Details Percentage", + "locale_origin": self.test_country.id, + "percentage_with_farm_details": 0, + "season_name": "Test Season Zero", + "season_start_date": datetime.date(2024, 1, 1), + "season_end_date": datetime.date(2024, 12, 31), + } + ) + + fake = Faker("en_US") + + # Generate season + generator._generate_season_data(fake) + + # Create group - should not have farm details + group = generator.generate_groups(fake) + + # Farm details should not be created + self.env["spp.farm.details"].search([("details_farm_id", "=", group.id)]) + # With 0% there's still a small chance, so we just check it ran without error + self.assertTrue(True) + + def test_36_edge_case_max_values(self): + """Test with maximum values for size fields""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Max Values Test", + "locale_origin": self.test_country.id, + "max_farm_size": 10000.0, + "min_farm_size": 100.0, + "max_land_parcels_per_farm": 100, + "max_assets_per_farm": 100, + "max_activities_per_farm": 50, + } + ) + + # Should not raise error + self.assertEqual(generator.max_farm_size, 10000.0) + self.assertEqual(generator.max_land_parcels_per_farm, 100) + + def test_37_edge_case_min_values(self): + """Test with minimum values for size fields""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Min Values Test", + "locale_origin": self.test_country.id, + "max_farm_size": 1.0, + "min_farm_size": 0.1, + "max_land_parcels_per_farm": 1, + "max_assets_per_farm": 1, + "max_activities_per_farm": 1, + } + ) + + # Should not raise error + self.assertEqual(generator.min_farm_size, 0.1) + self.assertEqual(generator.max_land_parcels_per_farm, 1) + + def test_38_constants_defined(self): + """Test that all constant lists are properly defined""" + generator = self.env["spp.demo.data.generator"].create( + { + "name": "Test Constants", + "locale_origin": self.test_country.id, + } + ) + + # Check that constants exist + self.assertTrue(hasattr(generator, "FARM_TYPES")) + self.assertTrue(hasattr(generator, "LAND_USES")) + self.assertTrue(hasattr(generator, "CULTIVATION_METHODS")) + self.assertTrue(hasattr(generator, "LEGAL_STATUSES")) + self.assertTrue(hasattr(generator, "ACTIVITY_TYPES")) + self.assertTrue(hasattr(generator, "PRODUCTION_PURPOSES")) + + # Check that they are not empty + self.assertGreater(len(generator.FARM_TYPES), 0) + self.assertGreater(len(generator.LAND_USES), 0) + self.assertGreater(len(generator.ACTIVITY_TYPES), 0) diff --git a/spp_base_farmer_registry_demo/tests/test_land_record.py b/spp_base_farmer_registry_demo/tests/test_land_record.py new file mode 100644 index 000000000..b3857122b --- /dev/null +++ b/spp_base_farmer_registry_demo/tests/test_land_record.py @@ -0,0 +1,399 @@ +# Part of OpenSPP. See LICENSE file for full copyright and licensing details. +import logging + +from odoo.tests.common import TransactionCase + +_logger = logging.getLogger(__name__) + + +class TestLandRecord(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.env = cls.env( + context=dict( + cls.env.context, + test_queue_job_no_delay=True, + ) + ) + + # Create test farm (group) + cls.farm = cls.env["res.partner"].create( + { + "name": "Test Farm Land", + "is_group": True, + "is_registrant": True, + } + ) + + def test_01_land_record_ke_access_cities_reg_field(self): + """Test ke_access_cities_reg field exists""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Test Parcel", + "land_acreage": 10.0, + } + ) + + self.assertIn("ke_access_cities_reg", land_record._fields) + + def test_02_land_record_ke_access_cities_reg_readonly(self): + """Test ke_access_cities_reg field is readonly""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Test Parcel", + "land_acreage": 10.0, + } + ) + + field = land_record._fields["ke_access_cities_reg"] + self.assertTrue(field.readonly) + + def test_03_land_record_ke_access_cities_reg_type(self): + """Test ke_access_cities_reg field is float type""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Test Parcel", + "land_acreage": 10.0, + } + ) + + field = land_record._fields["ke_access_cities_reg"] + self.assertEqual(field.type, "float") + + def test_04_land_record_basic_creation(self): + """Test creating a basic land record""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Parcel 1", + "land_acreage": 15.5, + } + ) + + self.assertEqual(land_record.land_name, "Parcel 1") + self.assertEqual(land_record.land_acreage, 15.5) + self.assertEqual(land_record.land_farm_id, self.farm) + + def test_05_land_record_with_ke_access(self): + """Test land record with ke_access_cities_reg value""" + # Since it's readonly, we need to use sudo or direct write + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Parcel with Access", + "land_acreage": 20.0, + } + ) + + # Try to set value through SQL or sudo + # Note: In real scenario this might be computed or set by external process + self.assertIn(land_record.ke_access_cities_reg, [0.0, False, None]) + + def test_06_multiple_land_records_same_farm(self): + """Test creating multiple land records for the same farm""" + land1 = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Parcel A", + "land_acreage": 10.0, + } + ) + + land2 = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Parcel B", + "land_acreage": 15.0, + } + ) + + land3 = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Parcel C", + "land_acreage": 8.5, + } + ) + + # All should belong to the same farm + self.assertEqual(land1.land_farm_id, self.farm) + self.assertEqual(land2.land_farm_id, self.farm) + self.assertEqual(land3.land_farm_id, self.farm) + + def test_07_land_records_different_farms(self): + """Test land records for different farms""" + farm2 = self.env["res.partner"].create( + { + "name": "Farm 2", + "is_group": True, + "is_registrant": True, + } + ) + + land1 = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Farm 1 Parcel", + "land_acreage": 10.0, + } + ) + + land2 = self.env["spp.land.record"].create( + { + "land_farm_id": farm2.id, + "land_name": "Farm 2 Parcel", + "land_acreage": 20.0, + } + ) + + self.assertEqual(land1.land_farm_id, self.farm) + self.assertEqual(land2.land_farm_id, farm2) + + def test_08_search_land_records_by_farm(self): + """Test searching land records by farm""" + self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Searchable 1", + "land_acreage": 5.0, + } + ) + self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Searchable 2", + "land_acreage": 7.0, + } + ) + + # Search for all land records of this farm + land_records = self.env["spp.land.record"].search([("land_farm_id", "=", self.farm.id)]) + + self.assertGreaterEqual(len(land_records), 2) + + def test_09_land_record_update(self): + """Test updating land record fields""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Original Name", + "land_acreage": 10.0, + } + ) + + # Update fields + land_record.write( + { + "land_name": "Updated Name", + "land_acreage": 12.5, + } + ) + + self.assertEqual(land_record.land_name, "Updated Name") + self.assertEqual(land_record.land_acreage, 12.5) + + def test_10_land_record_with_various_acreages(self): + """Test land records with various acreage values""" + test_acreages = [0.5, 1.0, 5.5, 10.0, 25.75, 50.0, 100.0, 1000.5] + + for i, acreage in enumerate(test_acreages): + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": f"Parcel {i}", + "land_acreage": acreage, + } + ) + self.assertEqual(land_record.land_acreage, acreage) + + def test_11_search_land_records_by_acreage(self): + """Test searching land records by acreage""" + self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Small Parcel", + "land_acreage": 2.0, + } + ) + self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Medium Parcel", + "land_acreage": 10.0, + } + ) + self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Large Parcel", + "land_acreage": 50.0, + } + ) + + # Search for large parcels + large = self.env["spp.land.record"].search([("land_acreage", ">", 20)]) + self.assertGreaterEqual(len(large), 1) + + # Search for small parcels + small = self.env["spp.land.record"].search([("land_acreage", "<", 5)]) + self.assertGreaterEqual(len(small), 1) + + def test_12_land_record_with_owner(self): + """Test land record with owner field""" + # Check if owner_id field exists + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Owned Parcel", + "land_acreage": 10.0, + } + ) + + if "owner_id" in land_record._fields: + land_record.write({"owner_id": self.farm.id}) + self.assertEqual(land_record.owner_id, self.farm) + + def test_13_land_record_with_land_use(self): + """Test land record with land_use field""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Cultivated Parcel", + "land_acreage": 10.0, + } + ) + + if "land_use" in land_record._fields: + land_record.write({"land_use": "cultivation"}) + self.assertEqual(land_record.land_use, "cultivation") + + def test_14_land_record_with_species(self): + """Test land record with species field""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Species Parcel", + "land_acreage": 10.0, + } + ) + + if "species" in land_record._fields: + # Create a test species + species = self.env["spp.farm.species"].create( + { + "name": "Test Crop Species", + "species_type": "crop", + } + ) + + land_record.write({"species": [(6, 0, [species.id])]}) + self.assertIn(species, land_record.species) + + def test_15_land_record_delete(self): + """Test deleting land record""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "To Delete", + "land_acreage": 10.0, + } + ) + + record_id = land_record.id + land_record.unlink() + + # Verify deleted + found = self.env["spp.land.record"].search([("id", "=", record_id)]) + self.assertEqual(len(found), 0) + + def test_16_land_record_bulk_create(self): + """Test bulk creating land records""" + vals_list = [ + { + "land_farm_id": self.farm.id, + "land_name": f"Bulk Parcel {i}", + "land_acreage": float(i * 5), + } + for i in range(1, 6) + ] + + land_records = self.env["spp.land.record"].create(vals_list) + + self.assertEqual(len(land_records), 5) + for i, record in enumerate(land_records, 1): + self.assertEqual(record.land_name, f"Bulk Parcel {i}") + + def test_17_land_record_inherit_check(self): + """Test that spp.land.record is properly inherited""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Inherit Test", + "land_acreage": 10.0, + } + ) + + # Verify standard fields exist + self.assertIn("land_name", land_record._fields) + self.assertIn("land_acreage", land_record._fields) + self.assertIn("land_farm_id", land_record._fields) + + # Verify our custom field exists + self.assertIn("ke_access_cities_reg", land_record._fields) + + def test_18_land_record_name_variations(self): + """Test land record with various name formats""" + name_formats = [ + "Parcel-001", + "Plot A", + "Land Lot 123", + "Section-45-B", + "AB12345", + ] + + for name in name_formats: + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": name, + "land_acreage": 10.0, + } + ) + self.assertEqual(land_record.land_name, name) + + def test_19_land_record_zero_acreage(self): + """Test land record with zero acreage""" + land_record = self.env["spp.land.record"].create( + { + "land_farm_id": self.farm.id, + "land_name": "Zero Acreage", + "land_acreage": 0.0, + } + ) + + self.assertEqual(land_record.land_acreage, 0.0) + + def test_20_land_record_comprehensive(self): + """Test creating land record with all available fields""" + vals = { + "land_farm_id": self.farm.id, + "land_name": "Comprehensive Parcel", + "land_acreage": 25.5, + } + + # Add optional fields if they exist + land_record_model = self.env["spp.land.record"] + if "owner_id" in land_record_model._fields: + vals["owner_id"] = self.farm.id + if "land_use" in land_record_model._fields: + vals["land_use"] = "cultivation" + + land_record = self.env["spp.land.record"].create(vals) + + self.assertEqual(land_record.land_name, "Comprehensive Parcel") + self.assertEqual(land_record.land_acreage, 25.5) + self.assertEqual(land_record.land_farm_id, self.farm) diff --git a/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml index 8d74ebfbc..d973a7354 100644 --- a/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml +++ b/spp_base_farmer_registry_demo/views/demo_data_generator_view.xml @@ -40,7 +40,7 @@
- +
@@ -48,7 +48,10 @@
-

+

-

+

-

+

-

+

-

+

-

+

-

+

-

+

- diff --git a/spp_demo_common/models/demo_data_generator.py b/spp_demo_common/models/demo_data_generator.py index f19030c01..239fc77e7 100644 --- a/spp_demo_common/models/demo_data_generator.py +++ b/spp_demo_common/models/demo_data_generator.py @@ -561,7 +561,11 @@ def create_ids(self, fake, registrant): registrant=registrant, operation_type="id_generation", failure_reason="max_attempts_reached", - error_message=f"Failed to generate valid ID after {max_attempts} attempts for regex: {id_validation_regex}", + error_message=( + f"Failed to generate valid ID after " + f"{max_attempts} attempts for regex: " + f"{id_validation_regex}" + ), attempts=max_attempts, validation_regex=id_validation_regex, generated_value=last_generated, @@ -648,16 +652,16 @@ def generate_phone_number(self, fake): phone_number = f"+{random.randint(1000000000, 9999999999)}" # Preserve the leading '+' if present, then remove all non-digit characters - has_plus = phone_number.startswith('+') + has_plus = phone_number.startswith("+") cleaned = re.sub(r"\D", "", phone_number) - + # Add back the '+' prefix if it was originally there if has_plus: cleaned = f"+{cleaned}" # Ensure we have a valid length (at least 10 digits for most phone numbers) # Check digit count (excluding the + sign) - digit_count = len(cleaned.lstrip('+')) + digit_count = len(cleaned.lstrip("+")) if cleaned and digit_count >= 10: return cleaned attempt += 1 From 07c1aacb73fdb86cb90a09c8dfccbfef089f6b94 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 16:30:42 +0800 Subject: [PATCH 17/25] [FIX] tests --- .../tests/test_agricultural_activity.py | 27 ++++++++++++++----- .../tests/test_generate_demo_data.py | 2 +- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py index af99f9df3..dc583fcc3 100644 --- a/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py +++ b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py @@ -27,16 +27,29 @@ def setUpClass(cls): } ) - # Create test season - cls.season = cls.env["spp.farm.season"].create( + # Create test user with farm manager role + cls.farm_manager = cls.env["res.users"].create( { - "name": "Test Season 2024", - "date_start": datetime.date(2024, 1, 1), - "date_end": datetime.date(2024, 12, 31), - "state": "draft", + "name": "Farm Manager", + "login": "farm_manager", + "groups_id": [(4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id)], } ) - cls.season.action_activate() + + # Create test season + cls.season = ( + cls.env["spp.farm.season"] + .with_user(cls.farm_manager) + .create( + { + "name": "Test Season 2024", + "date_start": datetime.date(2024, 1, 1), + "date_end": datetime.date(2024, 12, 31), + "state": "draft", + } + ) + ) + cls.season.with_user(cls.farm_manager).action_activate() # Create test species cls.crop_species = cls.env["spp.farm.species"].create( diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index eb1c40221..cadee8ca7 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -26,7 +26,7 @@ def setUpClass(cls): cls.test_country = cls.env["res.country"].create( { "name": "Test Country Farmer Registry", - "code": "TF", + "code": "TF2", "faker_locale": "en_US", "faker_locale_available": True, "lat_min": -10.0, From bee51ae9168022574b0fdf9ef99653a1c9d476be Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 17:37:21 +0800 Subject: [PATCH 18/25] [FIX] tests --- spp_demo_common/tests/test_demo_data_generator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/spp_demo_common/tests/test_demo_data_generator.py b/spp_demo_common/tests/test_demo_data_generator.py index 6872613de..97b1a2acd 100644 --- a/spp_demo_common/tests/test_demo_data_generator.py +++ b/spp_demo_common/tests/test_demo_data_generator.py @@ -414,8 +414,6 @@ def test_16_generate_phone_number(self): phone_number = generator.generate_phone_number(fake) self.assertIsNotNone(phone_number) - # Cleaned phone should be digits only - self.assertTrue(phone_number.isdigit()) def test_17_create_phone_numbers(self): """Test phone number creation for registrants""" From 0f205e5acef5f515c82f25f7170802312c84f305 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 17:59:50 +0800 Subject: [PATCH 19/25] [FIX] test --- spp_base_farmer_registry_demo/tests/test_generate_demo_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index cadee8ca7..4986fff0f 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -26,7 +26,7 @@ def setUpClass(cls): cls.test_country = cls.env["res.country"].create( { "name": "Test Country Farmer Registry", - "code": "TF2", + "code": "T2", "faker_locale": "en_US", "faker_locale_available": True, "lat_min": -10.0, From 77b35024b6baa716d1f7747d3f081bd1df83fe9d Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 18:35:06 +0800 Subject: [PATCH 20/25] [FIX] tests --- .../tests/test_agricultural_activity.py | 20 ++++---- .../tests/test_generate_demo_data.py | 47 ++++++++++++++----- 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py index dc583fcc3..877b76214 100644 --- a/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py +++ b/spp_base_farmer_registry_demo/tests/test_agricultural_activity.py @@ -474,15 +474,19 @@ def test_18_search_activities_by_production_system(self): def test_19_activities_with_different_seasons(self): """Test activities with different seasons""" - season2 = self.env["spp.farm.season"].create( - { - "name": "Test Season 2025", - "date_start": datetime.date(2025, 1, 1), - "date_end": datetime.date(2025, 12, 31), - "state": "draft", - } + season2 = ( + self.env["spp.farm.season"] + .with_user(self.farm_manager) + .create( + { + "name": "Test Season 2025", + "date_start": datetime.date(2025, 1, 1), + "date_end": datetime.date(2025, 12, 31), + "state": "draft", + } + ) ) - season2.action_activate() + season2.with_user(self.farm_manager).action_activate() activity1 = self.env["spp.farm.activity"].create( { diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index 4986fff0f..e99afb705 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -22,6 +22,15 @@ def setUpClass(cls): ) ) + # Create test user with farm manager role + cls.farm_manager = cls.env["res.users"].create( + { + "name": "Farm Manager", + "login": "farm_manager_test", + "groups_id": [(4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id)], + } + ) + # Create test country with faker locale cls.test_country = cls.env["res.country"].create( { @@ -235,7 +244,7 @@ def test_09_generate_season_data(self): if test_season: test_season.unlink() - season = generator._generate_season_data(fake) + season = generator.with_user(self.farm_manager)._generate_season_data(fake) self.assertIsNotNone(season) self.assertEqual(season.name, "Test Season 2024") @@ -369,8 +378,11 @@ def test_14_get_agricultural_activity_vals(self): fake = Faker("en_US") + # Use farm manager for season operations + generator_with_user = generator.with_user(self.farm_manager) + # Create season - season = generator._generate_season_data(fake) + season = generator_with_user._generate_season_data(fake) # Ensure species data exists generator._generate_species_data(fake) @@ -564,9 +576,12 @@ def test_23_generate_groups_with_farm_details(self): fake = Faker("en_US") + # Use farm manager for season operations + generator_with_user = generator.with_user(self.farm_manager) + # Generate required reference data generator._generate_species_data(fake) - generator._generate_season_data(fake) + generator_with_user._generate_season_data(fake) group = generator.generate_groups(fake) @@ -678,12 +693,15 @@ def test_27_generate_agricultural_activities(self): fake = Faker("en_US") + # Use farm manager for season-related operations + generator_with_user = generator.with_user(self.farm_manager) + # Generate required reference data - generator._generate_species_data(fake) - generator._generate_chemical_data(fake) - generator._generate_fertilizer_data(fake) - generator._generate_feed_items_data(fake) - generator._generate_season_data(fake) + generator_with_user._generate_species_data(fake) + generator_with_user._generate_chemical_data(fake) + generator_with_user._generate_fertilizer_data(fake) + generator_with_user._generate_feed_items_data(fake) + generator_with_user._generate_season_data(fake) # Create a test group group = self.env["res.partner"].create( @@ -694,7 +712,7 @@ def test_27_generate_agricultural_activities(self): } ) - generator._generate_agricultural_activities(fake, group) + generator_with_user._generate_agricultural_activities(fake, group) # Verify agricultural activities were created activities = self.env["spp.farm.activity"].search( @@ -808,11 +826,14 @@ def test_34_season_data_reuse_existing(self): fake = Faker("en_US") + # Use farm manager for season operations + generator_with_user = generator.with_user(self.farm_manager) + # Generate season first time - season1 = generator._generate_season_data(fake) + season1 = generator_with_user._generate_season_data(fake) # Generate season second time - season2 = generator._generate_season_data(fake) + season2 = generator_with_user._generate_season_data(fake) # Should be the same season self.assertEqual(season1.id, season2.id) @@ -832,8 +853,8 @@ def test_35_edge_case_zero_percentage_farm_details(self): fake = Faker("en_US") - # Generate season - generator._generate_season_data(fake) + # Generate season with farm manager + generator.with_user(self.farm_manager)._generate_season_data(fake) # Create group - should not have farm details group = generator.generate_groups(fake) From 760b58bd94929e288b0979d8c32197abb241f1d7 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 18:53:05 +0800 Subject: [PATCH 21/25] [FIX] tests --- spp_base_farmer_registry_demo/tests/test_generate_demo_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index e99afb705..d958c1fb6 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -554,7 +554,7 @@ def test_22_get_random_season(self): } ) - season_id = generator._get_random_season() + season_id = generator.with_user(self.farm_manager)._get_random_season() self.assertIsNotNone(season_id) From 8c5891d8ea0533a340d2c069d3afa34b534cebc0 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 18:55:08 +0800 Subject: [PATCH 22/25] [FIX] tests --- .../tests/test_generate_demo_data.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index d958c1fb6..118fe4641 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -27,7 +27,9 @@ def setUpClass(cls): { "name": "Farm Manager", "login": "farm_manager_test", - "groups_id": [(4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id)], + "groups_id": [ + (4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id), + (4. cls.env.ref("g2p_registry_base.group_g2p_admin"))], } ) From 1a8a122f3ff0db207b77a1df8c9b8f37ba95a93d Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Mon, 3 Nov 2025 18:57:16 +0800 Subject: [PATCH 23/25] [FIX] tests --- spp_base_farmer_registry_demo/tests/test_generate_demo_data.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index 118fe4641..15335dcf2 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -29,7 +29,8 @@ def setUpClass(cls): "login": "farm_manager_test", "groups_id": [ (4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id), - (4. cls.env.ref("g2p_registry_base.group_g2p_admin"))], + (4, cls.env.ref("g2p_registry_base.group_g2p_admin")), + ], } ) From ad635412164fffaa3a0063a1d2fd3d5e0a82c96f Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 4 Nov 2025 09:02:54 +0800 Subject: [PATCH 24/25] [FIX] tests --- spp_base_farmer_registry_demo/tests/test_generate_demo_data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index 15335dcf2..6b1a344c3 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -29,7 +29,7 @@ def setUpClass(cls): "login": "farm_manager_test", "groups_id": [ (4, cls.env.ref("spp_base_farmer_registry.group_spp_farm_manager").id), - (4, cls.env.ref("g2p_registry_base.group_g2p_admin")), + (4, cls.env.ref("g2p_registry_base.group_g2p_admin").id), ], } ) From e979a105772c4baa002868bc99fdcd05d08bf8d3 Mon Sep 17 00:00:00 2001 From: emjay0921 Date: Tue, 4 Nov 2025 09:21:19 +0800 Subject: [PATCH 25/25] [FIX] test --- .../tests/test_generate_demo_data.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py index 6b1a344c3..bb2f35598 100644 --- a/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py +++ b/spp_base_farmer_registry_demo/tests/test_generate_demo_data.py @@ -57,17 +57,16 @@ def setUpClass(cls): def test_01_season_date_validation(self): """Test season date validation""" - generator = self.env["spp.demo.data.generator"].create( - { - "name": "Test Season Validation", - "locale_origin": self.test_country.id, - "season_start_date": datetime.date(2024, 12, 31), - "season_end_date": datetime.date(2024, 1, 1), - } - ) with self.assertRaises(ValidationError): - generator._check_season_dates() + self.env["spp.demo.data.generator"].create( + { + "name": "Test Season Validation", + "locale_origin": self.test_country.id, + "season_start_date": datetime.date(2024, 12, 31), + "season_end_date": datetime.date(2024, 1, 1), + } + ) def test_02_farmer_registry_specific_fields(self): """Test farmer registry specific fields exist"""