85 خطوط
3.2 KiB
PHP
85 خطوط
3.2 KiB
PHP
<?php
|
|
|
|
namespace App\Modules\Taxonomy\Application;
|
|
|
|
use App\Models\User;
|
|
use App\Modules\Courses\Domain\CourseVersion;
|
|
use App\Modules\Courses\Domain\Enums\CourseVersionStatus;
|
|
use App\Modules\Taxonomy\Domain\ContentTaxonomyMapping;
|
|
use App\Modules\Taxonomy\Domain\Enums\MappableType;
|
|
use App\Modules\Taxonomy\Domain\Enums\MappingConfirmationStatus;
|
|
use App\Modules\Taxonomy\Domain\Enums\MappingSource;
|
|
use App\Modules\Taxonomy\Domain\Enums\MappingType;
|
|
use App\Modules\Taxonomy\Domain\TaxonomyNode;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
final class ContentMappingService
|
|
{
|
|
public function create(
|
|
string $organizationId,
|
|
string $courseVersionId,
|
|
MappableType $mappableType,
|
|
string $mappableId,
|
|
string $taxonomyNodeId,
|
|
MappingType $mappingType,
|
|
?string $masteryLevel,
|
|
float $weight,
|
|
MappingSource $source,
|
|
?float $confidence,
|
|
User $actor,
|
|
): ContentTaxonomyMapping {
|
|
$version = CourseVersion::query()->where('organization_id', $organizationId)->findOrFail($courseVersionId);
|
|
|
|
if ($version->status === CourseVersionStatus::Published) {
|
|
$this->invalid('courseVersionId', 'Published course versions cannot receive new mappings.');
|
|
}
|
|
|
|
TaxonomyNode::query()->where('organization_id', $organizationId)->findOrFail($taxonomyNodeId);
|
|
$this->resolveTarget($organizationId, $version, $mappableType, $mappableId);
|
|
|
|
$confirmation = $source === MappingSource::AiSuggested
|
|
? MappingConfirmationStatus::Draft
|
|
: MappingConfirmationStatus::Confirmed;
|
|
|
|
return ContentTaxonomyMapping::query()->create([
|
|
'organization_id' => $organizationId,
|
|
'course_version_id' => $version->getKey(),
|
|
'mappable_type' => $mappableType,
|
|
'mappable_id' => $mappableId,
|
|
'taxonomy_node_id' => $taxonomyNodeId,
|
|
'mapping_type' => $mappingType,
|
|
'mastery_level' => $masteryLevel,
|
|
'weight' => $weight,
|
|
'source' => $source,
|
|
'confidence' => $confidence,
|
|
'confirmation_status' => $confirmation,
|
|
'confirmed_by' => $confirmation === MappingConfirmationStatus::Confirmed ? $actor->getKey() : null,
|
|
'confirmed_at' => $confirmation === MappingConfirmationStatus::Confirmed ? now() : null,
|
|
]);
|
|
}
|
|
|
|
private function resolveTarget(string $organizationId, CourseVersion $version, MappableType $type, string $id): Model
|
|
{
|
|
$modelClass = $type->modelClass();
|
|
$query = $modelClass::query()->where('organization_id', $organizationId);
|
|
|
|
if ($type !== MappableType::Course) {
|
|
$query->where('course_version_id', $version->getKey());
|
|
}
|
|
|
|
$target = $query->find($id);
|
|
|
|
if (! $target || ($type === MappableType::Course && $target->getKey() !== $version->getKey())) {
|
|
$this->invalid('mappableId', 'The selected content does not belong to this course version.');
|
|
}
|
|
|
|
return $target;
|
|
}
|
|
|
|
private function invalid(string $field, string $message): never
|
|
{
|
|
throw ValidationException::withMessages([$field => [$message]]);
|
|
}
|
|
}
|