Apparently Nova maintainers believe this is "normal behaviour" for a belongsTo field: That you can't submit the form after changing the parent field.
I ended up solving it by using a BelongsTo field for everything except forms, and another Select field for the forms.
Here's the code in case someone's interested:
BelongsTo::make(__('Salesperson'), 'salesperson', Admin::class)
->exceptOnForms(),
Select::make(__('Salesperson'), 'salesperson_id')
->dependsOn(['country'], function (Select $field, NovaRequest $request, FormData $formData) {
$admin = Auth::guard('admin')->user();
$options = \App\Models\Admin::sales()
->whereHas('countries', function (Builder $query) use ($formData) {
$query->where('countries.id', $formData['country']);
})
->when($admin->isSales(), function (Builder $query) use ($admin) {
$query->where('id', $admin->id);
})
->pluck('name', 'id');
$field->options($options);
})
->nullable()
->rules([
'nullable',
new SalespersonRule(),
])
->onlyOnForms(),
This in turn made me create a custom validation rule, in order to ensure that child records can't be tampered with (BelongsTo DependsOn does this automatically):
<?php
namespace App\Rules;
use App\Models\Admin;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
class SalespersonRule implements ValidationRule
{
/**
* Run the validation rule.
*
* @param string $attribute
* @param mixed $value
* @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail
* @return void
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$admin = Auth::guard('admin')->user();
$query = Admin::sales()
->whereHas('countries', function (Builder $query) {
$query->where('countries.id', request()->input('country'));
})
->when($admin->isSales(), function (Builder $query) use ($admin) {
$query->where('id', $admin->id);
})
->where('id', $value);
if (! $query->exists()) {
$fail(__('Please select a valid Salesperson.'));
}
}
}
