id(); $table->string('nama'); $table->softDeletes(); }); Schema::create('master_kabupaten_kota', function (Blueprint $table) { $table->id(); $table->integer('provinsi_id'); }); Schema::create('master_kecamatan', function (Blueprint $table) { $table->id(); $table->integer('kabupaten_kota_id'); }); Schema::create('master_desa_kelurahan', function (Blueprint $table) { $table->id(); $table->integer('kecamatan_id'); }); Schema::create('petani', function (Blueprint $table) { $table->id(); $table->integer('komoditas_id'); $table->integer('desa_kelurahan_id'); $table->softDeletes(); }); Schema::create('map_lahan', function (Blueprint $table) { $table->id(); $table->integer('petani_id'); $table->decimal('luas_lahan', 12, 4); $table->softDeletes(); }); Schema::create('survey_panen', function (Blueprint $table) { $table->id(); $table->integer('petani_id'); $table->decimal('hasil_komoditas_utama_permusim', 12, 2)->nullable(); $table->decimal('hasil_komoditas_lainnya_permusim', 12, 2)->nullable(); $table->softDeletes(); }); DB::table('master_komoditas')->insert([ ['id' => 1, 'nama' => 'Kakao'], ['id' => 2, 'nama' => 'Vanili'], ['id' => 3, 'nama' => 'Kemiri'], ['id' => 4, 'nama' => 'Kopi'], ]); DB::table('master_kabupaten_kota')->insert(['id' => 1, 'provinsi_id' => 1]); DB::table('master_kecamatan')->insert(['id' => 1, 'kabupaten_kota_id' => 1]); DB::table('master_desa_kelurahan')->insert([ ['id' => 1, 'kecamatan_id' => 1], ['id' => 2, 'kecamatan_id' => 1], ]); DB::table('petani')->insert([ ['id' => 1, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null], ['id' => 2, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => null], ['id' => 3, 'komoditas_id' => 4, 'desa_kelurahan_id' => 2, 'deleted_at' => null], ['id' => 4, 'komoditas_id' => 1, 'desa_kelurahan_id' => 1, 'deleted_at' => '2026-01-01'], ['id' => 5, 'komoditas_id' => 2, 'desa_kelurahan_id' => 1, 'deleted_at' => null], ]); DB::table('map_lahan')->insert([ ['petani_id' => 1, 'luas_lahan' => 0.5, 'deleted_at' => null], ['petani_id' => 1, 'luas_lahan' => 1.5, 'deleted_at' => null], ['petani_id' => 2, 'luas_lahan' => 3, 'deleted_at' => null], ['petani_id' => 3, 'luas_lahan' => 2, 'deleted_at' => null], ['petani_id' => 4, 'luas_lahan' => 50, 'deleted_at' => null], ['petani_id' => 1, 'luas_lahan' => 100, 'deleted_at' => '2026-01-01'], ['petani_id' => 5, 'luas_lahan' => 0, 'deleted_at' => null], ]); DB::table('survey_panen')->insert([ ['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 300, 'deleted_at' => null], ['petani_id' => 2, 'hasil_komoditas_utama_permusim' => 600, 'deleted_at' => null], ['petani_id' => 3, 'hasil_komoditas_utama_permusim' => 400, 'deleted_at' => null], ['petani_id' => 4, 'hasil_komoditas_utama_permusim' => 10000, 'deleted_at' => null], ['petani_id' => 1, 'hasil_komoditas_utama_permusim' => 20000, 'deleted_at' => '2026-01-01'], ['petani_id' => 5, 'hasil_komoditas_utama_permusim' => 10, 'deleted_at' => null], ]); DB::table('survey_panen')->where('petani_id', 1)->update(['hasil_komoditas_lainnya_permusim' => 500]); $controller = app(SummaryController::class); $method = new ReflectionMethod($controller, 'actualProductivityByCommodityChart'); $this->chart = fn (array $filters = []) => $method->invoke($controller, new Request($filters)); }); test('actual productivity uses seasonal survey harvest and counts each plot once', function () { $chart = ($this->chart)(); expect($chart['labels'])->toBe(['Kakao', 'Vanili', 'Kemiri', 'Kopi']); expect($chart['series'])->toEqual([180, 0, 0, 200]); }); test('actual productivity respects region and commodity filters', function () { expect(($this->chart)(['desaKelurahan_id' => 1])['series'])->toEqual([180, 0, 0, 0]); expect(($this->chart)(['desaKelurahan_id' => 2, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 200]); expect(($this->chart)(['desaKelurahan_id' => 1, 'komoditas_id' => 4])['series'])->toEqual([0, 0, 0, 0]); expect(($this->chart)(['provinsi_id' => 99])['series'])->toEqual([0, 0, 0, 0]); }); test('actual productivity rounds kg per hectare to two decimals', function () { DB::table('map_lahan')->where('petani_id', 5)->update(['luas_lahan' => 0.3]); expect(($this->chart)()['series'][1])->toBe(33.33); }); test('productivity cards display the same values and commodities as the chart', function () { DB::table('map_lahan')->where('petani_id', 5)->update(['luas_lahan' => 0.3]); $controller = app(SummaryController::class); $method = new ReflectionMethod($controller, 'productivityKpiCards'); foreach ([[], ['desaKelurahan_id' => 2], ['komoditas_id' => 1]] as $filters) { $chart = ($this->chart)($filters); $cards = $method->invoke($controller, $chart); expect(array_column($cards, 'label'))->toBe(array_map( fn ($name) => 'Produktivitas Aktual '.$name, $chart['labels'], )); expect(array_column($cards, 'value'))->toBe(array_map( fn ($value) => number_format($value, 2, ',', '.'), $chart['series'], )); expect(array_column($cards, 'unit'))->toBe(array_fill(0, count($chart['labels']), 'kg/ha')); } });