Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ Models in this app often extend base classes from these packages (e.g., `User`,
### Model Domain Structure

Models are namespaced by domain under `app/Models/`:
- `Models/SampleFrame/` — Farm, Location, LocationLevel (the survey sample population)
- `Models/SurveyData/` — FarmSurveyData, Crop, Product and other ODK submission data
- `Models/SampleFrame/` — FarmEntity, Location, LocationLevel (the survey sample population)
- `Models/Holpa/` — LocalIndicator, Theme, Domain (custom indicator framework)
- `Models/Reference/` — Reference/lookup data

Expand Down
36 changes: 0 additions & 36 deletions app/Events/FarmImportCompleted.php

This file was deleted.

10 changes: 3 additions & 7 deletions app/Exports/DataExport/DatasetExport.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php

namespace App\Exports\DataExport;
use App\Models\SampleFrame\Farm;

use App\Models\Team;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
Expand All @@ -12,10 +12,9 @@

class DatasetExport implements FromCollection, WithHeadings, WithTitle
{

public array $headings;
public Collection $entities;

public Collection $entities;

public function __construct(public Team $team, public Dataset $dataset)
{
Expand All @@ -31,15 +30,11 @@ public function __construct(public Team $team, public Dataset $dataset)
->get();
}

/**
* @return Collection
*/
public function collection(): Collection
{
return $this->entities->map(function (Entity $entity) {

// get the farm_id and farm_name from the owner relationship
/** @var Farm $farm */
$farm = $entity->submission->primaryDataSubject;

$row = [
Expand All @@ -51,6 +46,7 @@ public function collection(): Collection
$value = $entity->values->firstWhere('dataset_variable_name', $heading);
$row[$heading] = $value ? $value->value : null;
}

return $row;
});
}
Expand Down
9 changes: 1 addition & 8 deletions app/Filament/Admin/Widgets/DataCollectedWidget.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

namespace App\Filament\Admin\Widgets;

use App\Models\SampleFrame\Farm;
use Filament\Widgets\StatsOverviewWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use Illuminate\Support\HtmlString;
Expand All @@ -16,14 +15,8 @@ protected function getStats(): array
{
$result = [];

// find number of farms that completed both household form and fieldwork form
$farmsSurveyed = Farm::where('household_form_completed', true)->where('fieldwork_form_completed', true)->count();
// $farmsSurveyed = Role::count();

array_push($result, Stat::make(new HtmlString('Farms surveyed'), $farmsSurveyed));

// find total number of submissions for each xlsform template
$xlsformTemplates = XlsFormTemplate::all();
$xlsformTemplates = XlsformTemplate::all();

foreach ($xlsformTemplates as $xlsformTemplate) {
$total = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,13 @@
use Filament\Tables\Table;
use Illuminate\Validation\Rules\Unique;

// New, ODK-Central-Entities-backed Farm CRUD page, built alongside the existing
// FarmResource (not replacing it yet). See docs/plans/odk-entities-farm-crud.md.
// Nav-hidden, same as FarmResource - reachable by direct link during development/testing.
// ODK-Central-Entities-backed Farm CRUD page. See docs/plans/odk-entities-farm-crud.md.
// Nav-hidden - reached via the Survey Locations index and the location-levels cluster nav.
class FarmEntityResource extends Resource
{
protected static ?string $model = FarmEntity::class;

protected static ?string $slug = 'farm-entities';
protected static ?string $slug = 'farms';

protected static bool $shouldRegisterNavigation = false;

Expand Down Expand Up @@ -108,13 +107,12 @@ public static function table(Table $table): Table
$dataset = $service->ensureDataset($team);

// Dynamic identifier/property columns are driven by DatasetVariable (schema-level,
// stays local), not by scanning records' JSON keys like the old FarmResource does.
// Values themselves are never persisted locally - $livewire->liveFarmData is the
// stays local). Values themselves are never persisted locally - $livewire->liveFarmData is the
// live feed ListFarmEntities::mount() fetched for this page load (see
// OdkFarmEntityService::refreshFromCentral()). The location cascade attributes
// (loc{n}/loc{n}_name/loc{n}_type) and GPS are excluded here via the 'loc'
// classification - GPS has its own dedicated form fields and the cascade attributes
// are owned by the dedicated Location column, matching the old FarmResource.
// are owned by the dedicated Location column.
$propertyColumns = $dataset->variables()
->where('name', '!=', 'team_code')
->get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource\Pages;

use App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource;
use App\Imports\FarmEntityImport;
use App\Imports\LocationImport;
use App\Jobs\QueueFarmEntityImport;
use App\Models\Import;
use App\Models\SampleFrame\FarmEntity;
use App\Models\SampleFrame\Location;
Expand All @@ -30,18 +30,15 @@
use Maatwebsite\Excel\Facades\Excel;
use Maatwebsite\Excel\HeadingRowImport;

// ODK-Entities-backed counterpart to FarmResource\Pages\ImportLocationsAndFarms - the
// column-mapping wizard is identical (it's about parsing a spreadsheet + location
// hierarchy, independent of storage backend). Only the farm half of save() differs:
// FarmEntityImport instead of FarmImport. Locations stay fully local either way. Reuses
// the same Blade view as the original page - it's generic form+actions boilerplate.
// Combined wizard: one spreadsheet holding both the location hierarchy and the farm list.
// Locations are imported into local tables; farms are pushed to ODK Central as entities.
class ImportLocationsAndFarmEntities extends Page implements HasForms
{
use InteractsWithForms;

protected static string $resource = FarmEntityResource::class;

protected string $view = 'filament.app.clusters.location-levels.resources.farm-resource.pages.import-locations-and-farms';
protected string $view = 'filament.app.clusters.location-levels.resources.farm-entity-resource.pages.import-locations-and-farm-entities';

public function getTitle(): string
{
Expand Down Expand Up @@ -79,7 +76,7 @@ public function save(): void
// copy it as a duplicate, which will be stored with the import model for farms
Storage::copy($data['upload'], $data['upload'].'_duplicate');

// No "replace all locations" option here (unlike the old FarmResource wizard) -
// No "replace all locations" option here -
// farm_entities.location_id used to cascadeOnDelete, which combined with mass
// location deletion here into a real bug (see docs/plans/odk-entities-farm-crud.md).
// The FK is now nullOnDelete instead, but a bulk "delete every location" action is
Expand All @@ -90,9 +87,6 @@ public function save(): void
]);

$locationImport->addMedia(Storage::path($data['upload']))->toMediaCollection();
$data['import_id'] = $locationImport->id;

Excel::import(new LocationImport($data), $locationImport->getFirstMediaPath());

// import farms as ODK Central entities
$farmImport = Import::create([
Expand All @@ -101,9 +95,27 @@ public function save(): void
]);

$farmImport->addMedia(Storage::path($data['upload']).'_duplicate')->toMediaCollection();
$data['import_id'] = $farmImport->id;

Excel::import(new FarmEntityImport($data), $farmImport->getFirstMediaPath());
// $data['level'] holds a whole LocationLevel model, and SerializesModels does not
// reduce models nested inside an array property - FarmEntityImport never reads it.
$farmData = $data;
unset($farmData['level']);
$farmData['import_id'] = $farmImport->id;

$data['import_id'] = $locationImport->id;
$data['dependent_import_id'] = $farmImport->id;

// Farm rows validate against locations this import is still creating
// (FarmEntityImport::rules() -> Rule::exists('locations', 'code')), so the farm import
// must not run concurrently. Appending to the location import's own chain - rather
// than dispatching a second chain - is what actually orders them: a sibling
// Bus::chain link would fire as soon as the location chunks had been *queued*, not
// once they had run. queueImport() is Excel::import() narrowed to the ShouldQueue
// case, so it always hands back the PendingDispatch wrapping that chain, and
// appendToChain() must happen before it falls out of scope and dispatches.
Excel::queueImport(new LocationImport($data), $locationImport->getFirstMediaPath())
// @phpstan-ignore-next-line PendingDispatch::__call() forwards to Queueable::appendToChain() on the underlying QueueImport job
->appendToChain(new QueueFarmEntityImport($farmData, $farmImport->id));

Notification::make()
->title(t('Locations and farms are being imported.'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
namespace App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource\Pages;

use App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource;
use App\Filament\App\Clusters\LocationLevels\Resources\FarmResource\Widgets\FarmListHeaderWidget;
use App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource\Widgets\FarmListHeaderWidget;
use App\Filament\App\Pages\SurveyDashboard;
use App\Filament\App\Pages\SurveyLocations\SurveyLocationsIndex;
use App\Filament\Tables\Actions\ImportFarmsAction;
Expand Down Expand Up @@ -34,8 +34,6 @@ public function getBreadcrumbs(): array
protected function getHeaderWidgets(): array
{
return [
// Reused as-is from FarmResource - it's a generic instructions panel with no
// Farm-model-specific logic.
FarmListHeaderWidget::class,
];
}
Expand All @@ -58,8 +56,6 @@ public function mount(): void
protected function getHeaderActions(): array
{
return [
// Divert to the combined wizard - identical to ListFarms' equivalent button,
// just pointed at this resource's own import route.
Action::make('import')
->label(fn () => t('Import Locations and Farm List'))
->extraAttributes(['class' => 'buttonb'])
Expand All @@ -70,9 +66,7 @@ protected function getHeaderActions(): array

// Reuses the existing column-mapping modal as-is (it's about parsing a
// spreadsheet, independent of storage backend) - only the underlying import
// class differs. NOTE: the Import audit record this creates is tagged
// model_type => Farm::class regardless (hardcoded in ImportFarmsAction), which
// is cosmetically inaccurate for entity imports but doesn't affect behaviour.
// class differs.
ImportFarmsAction::make()
->color('primary')
->extraAttributes(['class' => 'buttonb'])
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<?php

namespace App\Filament\App\Clusters\LocationLevels\Resources\FarmResource\Widgets;
namespace App\Filament\App\Clusters\LocationLevels\Resources\FarmEntityResource\Widgets;

use Filament\Widgets\Widget;
use App\Services\HelperService;
use Filament\Widgets\Widget;

class FarmListHeaderWidget extends Widget
{
protected string $view = 'filament.app.resources.farm-resource.widgets.farm-list-header-widget';
protected string $view = 'filament.app.resources.farm-entity-resource.widgets.farm-list-header-widget';

protected int|string|array $columnSpan = 'full';

Expand Down
Loading
Loading