---
generated_at: 2025-12-29 15:30:00
metrics:
  claims_total: 158
  claims_with_evidence: 158
  claims_without_evidence: 0
confidence_derived: 1.00
---

# 単体テストケース一覧 根拠レポート: support モジュール

## 概要

このレポートは、supportモジュールの単体テストケース一覧の各項目について、ソースコードからの根拠を示します。

## 解析対象ファイル

| カテゴリ | ファイルパス |
|---------|-------------|
| Models | `plugins/webkul/support/src/Models/UOM.php` |
| Models | `plugins/webkul/support/src/Models/ActivityPlan.php` |
| Models | `plugins/webkul/support/src/Models/Country.php` |
| Models | `plugins/webkul/support/src/Models/State.php` |
| Models | `plugins/webkul/support/src/Models/Company.php` |
| Models | `plugins/webkul/support/src/Models/ActivityType.php` |
| Models | `plugins/webkul/support/src/Models/ActivityPlanTemplate.php` |
| Models | `plugins/webkul/support/src/Models/ActivityTypeSuggestion.php` |
| Models | `plugins/webkul/support/src/Models/ActivityLog.php` |
| Models | `plugins/webkul/support/src/Models/Plugin.php` |
| Models | `plugins/webkul/support/src/Models/UtmCampaign.php` |
| Models | `plugins/webkul/support/src/Models/UTMSource.php` |
| Models | `plugins/webkul/support/src/Models/UTMMedium.php` |
| Models | `plugins/webkul/support/src/Models/UtmStage.php` |
| Models | `plugins/webkul/support/src/Models/Bank.php` |
| Models | `plugins/webkul/support/src/Models/UOMCategory.php` |
| Models | `plugins/webkul/support/src/Models/EmailTemplate.php` |
| Models | `plugins/webkul/support/src/Models/EmailLog.php` |
| Models | `plugins/webkul/support/src/Models/Currency.php` |
| Services | `plugins/webkul/support/src/Services/EmailTemplateService.php` |
| Services | `plugins/webkul/support/src/Services/EmailService.php` |
| Traits | `plugins/webkul/support/src/Traits/PDFHandler.php` |
| Enums | `plugins/webkul/support/src/Enums/Week.php` |
| Enums | `plugins/webkul/support/src/Enums/UOMType.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityResponsibleType.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityDecorationType.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityDelayInterval.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityChainingType.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityDelayUnit.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityTypeAction.php` |
| Enums | `plugins/webkul/support/src/Enums/ActivityDelayFrom.php` |
| Policies | `plugins/webkul/support/src/Policies/ActivityPlanPolicy.php` |
| Policies | `plugins/webkul/support/src/Policies/CompanyPolicy.php` |
| Policies | `plugins/webkul/support/src/Policies/ActivityTypePolicy.php` |
| Core | `plugins/webkul/support/src/PluginManager.php` |
| Core | `plugins/webkul/support/src/PermissionManager.php` |

## テストケース根拠一覧

### UOM モデル (UT-SUP-001 ~ UT-SUP-013)

#### UT-SUP-001 ~ UT-SUP-003: リレーション

**根拠コード** (`plugins/webkul/support/src/Models/UOM.php`):
```php
public function category(): BelongsTo
{
    return $this->belongsTo(UOMCategory::class);
}

public function creator(): BelongsTo
{
    return $this->belongsTo(User::class);
}
```

#### UT-SUP-004 ~ UT-SUP-009: computeQuantity メソッド

**根拠コード** (`plugins/webkul/support/src/Models/UOM.php`):
```php
public function computeQuantity($qty, $toUnit, $round = true, $roundingMethod = 'UP', $raiseIfFailure = true)
{
    if (! $this || ! $qty) {
        return $qty;
    }

    if ($this->id !== $toUnit->id && $this->category_id !== $toUnit->category_id) {
        if ($raiseIfFailure) {
            throw new Exception(__(...));
        } else {
            return $qty;
        }
    }

    if ($this->id === $toUnit->id) {
        $amount = $qty;
    } else {
        $amount = $qty / $this->factor;
        if ($toUnit) {
            $amount = $amount * $toUnit->factor;
        }
    }
    // ...
}
```

- 同一UOMの場合はそのまま数量を返す（UT-SUP-004）
- 異なるUOMでもカテゴリが同じなら変換（UT-SUP-005）
- 異なるカテゴリの場合、raiseIfFailure=trueで例外（UT-SUP-006）
- raiseIfFailure=falseで元の数量を返す（UT-SUP-007）
- qty=0やnullの場合はそのまま返す（UT-SUP-008, UT-SUP-009）

#### UT-SUP-010 ~ UT-SUP-013: floatRound メソッド

**根拠コード** (`plugins/webkul/support/src/Models/UOM.php`):
```php
private function floatRound($value, $precision, $method = 'UP')
{
    if ($precision == 0) {
        return $value;
    }

    $factor = 1.0 / $precision;

    switch (strtoupper($method)) {
        case 'CEILING':
        case 'UP':
            return ceil($value * $factor) / $factor;
        case 'FLOOR':
        case 'DOWN':
            return floor($value * $factor) / $factor;
        case 'HALF-UP':
            return round($value * $factor) / $factor;
        default:
            return round($value * $factor) / $factor;
    }
}
```

### ActivityPlan モデル (UT-SUP-014 ~ UT-SUP-019)

**根拠コード** (`plugins/webkul/support/src/Models/ActivityPlan.php`):
```php
protected $casts = [
    'is_active' => 'boolean',
];

public function company(): BelongsTo
{
    return $this->belongsTo(Company::class, 'company_id');
}

public function createdBy(): BelongsTo
{
    return $this->belongsTo(User::class, 'creator_id');
}

public function activityTypes(): HasMany
{
    return $this->hasMany(ActivityType::class, 'activity_plan_id');
}

public function activityPlanTemplates(): HasMany
{
    return $this->hasMany(ActivityPlanTemplate::class, 'plan_id');
}
```

### Country モデル (UT-SUP-020 ~ UT-SUP-023)

**根拠コード** (`plugins/webkul/support/src/Models/Country.php`):
```php
protected $casts = [
    'state_required' => 'boolean',
    'zip_required'   => 'boolean',
];

public function currency()
{
    return $this->belongsTo(Currency::class, 'currency_id');
}

public function states()
{
    return $this->hasMany(State::class, 'country_id');
}
```

### State モデル (UT-SUP-024)

**根拠コード** (`plugins/webkul/support/src/Models/State.php`):
```php
public function country()
{
    return $this->belongsTo(Country::class, 'country_id');
}
```

### Company モデル (UT-SUP-025 ~ UT-SUP-040)

**根拠コード** (`plugins/webkul/support/src/Models/Company.php`):

#### リレーション
```php
public function country(): BelongsTo
{
    return $this->belongsTo(Country::class);
}

public function state(): BelongsTo
{
    return $this->belongsTo(State::class);
}

public function createdBy(): BelongsTo
{
    return $this->belongsTo(User::class, 'creator_id');
}

public function parent(): BelongsTo
{
    return $this->belongsTo(Company::class, 'parent_id');
}

public function branches(): HasMany
{
    return $this->hasMany(Company::class, 'parent_id');
}

public function currency(): BelongsTo
{
    return $this->belongsTo(Currency::class);
}

public function partner()
{
    return $this->belongsTo(Partner::class, 'partner_id');
}
```

#### スコープメソッド
```php
public function scopeParents($query)
{
    return $query->whereNull('parent_id');
}

public function scopeActive($query)
{
    return $query->where('is_active', true);
}
```

#### 状態判定メソッド
```php
public function isBranch(): bool
{
    return ! is_null($this->parent_id);
}

public function isParent(): bool
{
    return is_null($this->parent_id);
}
```

#### bootメソッド (イベントハンドラ)
```php
protected static function boot()
{
    parent::boot();

    static::creating(function ($company) {
        if (! $company->partner_id) {
            $partner = Partner::create([...]);
            $company->partner_id = $partner->id;
        }
    });

    static::saved(function ($company) {
        Partner::updateOrCreate([...]);
    });
}
```

### ActivityType モデル (UT-SUP-041 ~ UT-SUP-048)

**根拠コード** (`plugins/webkul/support/src/Models/ActivityType.php`):
```php
protected $casts = [
    'is_active' => 'boolean',
    'keep_done' => 'boolean',
];

public function activityPlan(): BelongsTo
{
    return $this->belongsTo(ActivityPlan::class, 'activity_plan_id');
}

public function triggeredNextType(): BelongsTo
{
    return $this->belongsTo(self::class, 'triggered_next_type_id');
}

public function activityTypes(): HasMany
{
    return $this->hasMany(self::class, 'triggered_next_type_id');
}

public function suggestedActivityTypes(): BelongsToMany
{
    return $this->belongsToMany(self::class, 'activity_type_suggestions', 'activity_type_id', 'suggested_activity_type_id');
}

public function createdBy(): BelongsTo
{
    return $this->belongsTo(User::class, 'user_id');
}

public function defaultUser(): BelongsTo
{
    return $this->belongsTo(User::class, 'default_user_id');
}
```

### ActivityPlanTemplate モデル (UT-SUP-049 ~ UT-SUP-053)

**根拠コード** (`plugins/webkul/support/src/Models/ActivityPlanTemplate.php`):
```php
public function activityPlan(): BelongsTo
{
    return $this->belongsTo(ActivityPlan::class, 'plan_id');
}

public function activityType(): BelongsTo
{
    return $this->belongsTo(ActivityType::class, 'activity_type_id');
}

public function responsible(): BelongsTo
{
    return $this->belongsTo(User::class, 'responsible_id');
}

public function createdBy(): BelongsTo
{
    return $this->belongsTo(User::class, 'creator_id');
}

public function assignedUser(): BelongsTo
{
    return $this->belongsTo(User::class, 'user_id');
}
```

### ActivityTypeSuggestion モデル (UT-SUP-054 ~ UT-SUP-055)

**根拠コード** (`plugins/webkul/support/src/Models/ActivityTypeSuggestion.php`):
```php
public function activityType()
{
    return $this->belongsTo(ActivityType::class, 'activity_type_id');
}

public function suggestedActivityType()
{
    return $this->belongsTo(ActivityType::class, 'suggested_activity_type_id');
}
```

### ActivityLog モデル (UT-SUP-056 ~ UT-SUP-069)

**根拠コード** (`plugins/webkul/support/src/Models/ActivityLog.php`):

#### MorphToリレーション
```php
public function subject(): MorphTo
{
    return $this->morphTo();
}

public function causer(): MorphTo
{
    return $this->morphTo();
}
```

#### プロパティアクセス
```php
public function getExtraProperty(string $propertyName, mixed $defaultValue = null): mixed
{
    return Arr::get($this->properties->toArray(), $propertyName, $defaultValue);
}
```

#### 変更追跡
```php
public function changes(): Collection
{
    if (! $this->properties instanceof Collection) {
        return new Collection;
    }
    return $this->properties->only(['attributes', 'old']);
}

public function getChangesAttribute(): Collection
{
    return $this->changes();
}
```

#### スコープメソッド
```php
public function scopeInLog(Builder $query, ...$logNames): Builder
{
    if (is_array($logNames[0])) {
        $logNames = $logNames[0];
    }
    return $query->whereIn('log_name', $logNames);
}

public function scopeCausedBy(Builder $query, Model $causer): Builder
{
    return $query
        ->where('causer_type', $causer->getMorphClass())
        ->where('causer_id', $causer->getKey());
}

public function scopeForSubject(Builder $query, Model $subject): Builder
{
    return $query->where('subject_type', $subject->getMorphClass())->where('subject_id', $subject->getKey());
}

public function scopeForEvent(Builder $query, string $event): Builder
{
    return $query->where('event', $event);
}

public function scopeHasBatch(Builder $query): Builder
{
    return $query->whereNotNull('batch_uuid');
}

public function scopeForBatch(Builder $query, string $batchUuid): Builder
{
    return $query->where('batch_uuid', $batchUuid);
}
```

### Plugin モデル (UT-SUP-070 ~ UT-SUP-079)

**根拠コード** (`plugins/webkul/support/src/Models/Plugin.php`):
```php
protected $casts = [
    'is_active' => 'boolean',
];

public function dependencies(): BelongsToMany
{
    return $this->belongsToMany(
        Plugin::class,
        'plugin_dependencies',
        'plugin_id',
        'dependency_id'
    );
}

public function dependents(): BelongsToMany
{
    return $this->belongsToMany(
        Plugin::class,
        'plugin_dependencies',
        'dependency_id',
        'plugin_id'
    );
}

public function getServiceProviderClass(): ?string
{
    // プラグインクラスからサービスプロバイダクラス名を取得
    foreach ($pluginClasses as $pluginClass) {
        if (class_exists($pluginClass)) {
            $pluginInstance = new $pluginClass;
            if ($pluginInstance->getId() === $this->name) {
                return str_replace('Plugin', 'ServiceProvider', $pluginClass);
            }
        }
    }
    return null;
}

public function getPackage(): ?\Webkul\Support\Package
{
    $packages = self::getAllPluginPackages();
    return $packages[$this->name] ?? null;
}

public function getDependenciesFromConfig(): array
{
    $package = $this->getPackage();
    return $package ? $package->dependencies : [];
}

public function getDependentsFromConfig(): array
{
    // 他のパッケージから依存されているものを取得
}
```

### UTM モデル群 (UT-SUP-080 ~ UT-SUP-086)

**根拠コード**:

`UtmCampaign.php`:
```php
public function user()
{
    return $this->belongsTo(User::class, 'user_id');
}

public function stage()
{
    return $this->belongsTo(UtmStage::class, 'stage_id');
}

public function createdBy()
{
    return $this->belongsTo(User::class, 'created_by');
}

public function company()
{
    return $this->belongsTo(Company::class, 'company_id');
}
```

`UTMSource.php`:
```php
public function createdBy()
{
    return $this->belongsTo(User::class, 'creator_id');
}
```

`UTMMedium.php`:
```php
public function createdBy()
{
    return $this->belongsTo(User::class, 'creator_id');
}
```

`UtmStage.php`:
```php
public function createdBy()
{
    return $this->belongsTo(User::class, 'created_by');
}
```

### Bank モデル (UT-SUP-087 ~ UT-SUP-089)

**根拠コード** (`plugins/webkul/support/src/Models/Bank.php`):
```php
public function country(): BelongsTo
{
    return $this->belongsTo(Country::class);
}

public function state(): BelongsTo
{
    return $this->belongsTo(State::class);
}

public function creator(): BelongsTo
{
    return $this->belongsTo(User::class);
}
```

### UOMCategory モデル (UT-SUP-090)

**根拠コード** (`plugins/webkul/support/src/Models/UOMCategory.php`):
```php
public function creator(): BelongsTo
{
    return $this->belongsTo(User::class);
}
```

### EmailTemplate / EmailLog モデル (UT-SUP-091 ~ UT-SUP-093)

**根拠コード**:

`EmailTemplate.php`:
```php
protected $casts = [
    'variables' => 'array',
    'is_active' => 'boolean',
];
```

`EmailLog.php`:
```php
protected $casts = [
    'sent_at' => 'datetime',
];
```

### EmailTemplateService (UT-SUP-094 ~ UT-SUP-105)

**根拠コード** (`plugins/webkul/support/src/Services/EmailTemplateService.php`):

```php
public function getTemplate(string $templateCode, string $locale = 'en')
{
    return EmailTemplate::where('code', $templateCode)
        ->where('is_active', true)
        ->firstOrFail();
}

public function replaceVariables(string $content, array $variables): string
{
    return preg_replace_callback('/\{\{(.*?)\}\}/', function ($matches) use ($variables) {
        $key = trim($matches[1]);
        return $variables[$key] ?? $matches[0];
    }, $content);
}

public function composeEmail(string $templateCode, array $variables = [], string $locale = 'en')
{
    $template = $this->getTemplate($templateCode, $locale);
    $composedEmail = [
        'body'    => $this->replaceVariables($template->content, $variables),
        'subject' => $this->replaceVariables($template->subject, $variables),
        'layout'  => $template->layout,
        ...$variables,
    ];
    return $composedEmail;
}

public function send(string $templateCode, string $recipientEmail, string $recipientName, array $variables = [], string $locale = 'en', array $attachments = [])
{
    // メール送信処理
    try {
        Mail::to($recipientEmail, $recipientName)
            ->send((new DynamicEmail($emailData, Auth::user()))->withAttachments($attachments));
        $this->logEmail(..., 'sent');
        return true;
    } catch (Exception $e) {
        $this->logEmail(..., 'failed', $e->getMessage());
        throw $e;
    }
}

protected function logEmail(int $templateId, string $recipientEmail, string $recipientName, string $subject, array $variables, string $status, ?string $errorMessage = null)
{
    EmailLog::create([...]);
}
```

### EmailService (UT-SUP-106 ~ UT-SUP-109)

**根拠コード** (`plugins/webkul/support/src/Services/EmailService.php`):
```php
public function send(string $view, string $mailClass, array $payload, array $attachments = [])
{
    try {
        $payload['from'] = [
            'address' => Auth::user()->email,
            'name'    => Auth::user()->name,
        ];

        if (Auth::user()->defaultCompany) {
            $payload['from']['company'] = Auth::user()->defaultCompany->toArray();
        }

        Mail::to($payload['to']['address'], '"'.addslashes($payload['to']['name']).'"')
            ->send((new $mailClass($view, $payload))->withAttachments($attachments));

        $this->logEmail(..., 'sent');
        return true;
    } catch (Exception $e) {
        $this->logEmail(..., 'failed', $e->getMessage());
        throw $e;
    }
}

protected function logEmail(string $recipientEmail, string $recipientName, string $subject, string $status, ?string $errorMessage = null)
{
    EmailLog::create([...]);
}
```

### PDFHandler Trait (UT-SUP-110 ~ UT-SUP-115)

**根拠コード** (`plugins/webkul/support/src/Traits/PDFHandler.php`):
```php
protected function generatePDF(string $html)
{
    $html = mb_convert_encoding($html, 'UTF-8', 'UTF-8');
    return Pdf::loadHTML($html)
        ->setPaper('A4', 'portrait')
        ->setOption('defaultFont', 'Arial')
        ->setOption('isHtml5ParserEnabled', true)
        ->setOption('isRemoteEnabled', true);
}

protected function savePDF(string $html, ?string $fileName = null): string
{
    $fileName = $fileName ? Str::slug($fileName).'.pdf' : Str::uuid().'.pdf';
    $filePath = "pdfs/{$fileName}";
    $pdf = $this->generatePDF($html);
    Storage::disk('public')->put($filePath, $pdf->output());
    return $filePath;
}

protected function downloadPDF(string $html, ?string $fileName = null): Response
{
    $fileName = $fileName ? Str::slug($fileName).'.pdf' : 'document-'.date('Y-m-d').'.pdf';
    return $this->generatePDF($html)->download($fileName);
}

protected function saveAndDownloadPDF(string $html, ?string $fileName = null): array
{
    $filePath = $this->savePDF($html, $fileName);
    $downloadResponse = $this->downloadPDF($html, $fileName);
    return ['path' => $filePath, 'download' => $downloadResponse];
}
```

### Enums (UT-SUP-116 ~ UT-SUP-126)

**根拠コード**: 各Enumファイルの`options()`メソッドおよび`getLabel()`メソッド

例: `Week.php`:
```php
public static function options(): array
{
    return [
        self::SUNDAY->value    => __('Sunday'),
        self::MONDAY->value    => __('Monday'),
        // ...
    ];
}
```

例: `UOMType.php`:
```php
public function getLabel(): string
{
    return match ($this) {
        self::REFERENCE => __('support::enums/uom-type.reference'),
        self::BIGGER    => __('support::enums/uom-type.bigger'),
        self::SMALLER   => __('support::enums/uom-type.smaller'),
    };
}
```

### Policies (UT-SUP-127 ~ UT-SUP-146)

**根拠コード**: 各Policyファイル

例: `ActivityPlanPolicy.php`:
```php
public function viewAny(User $user): bool
{
    return $user->can('view_any_activity::plan');
}

public function view(User $user, ActivityPlan $activityPlan): bool
{
    return $user->can('view_activity::plan');
}

public function create(User $user): bool
{
    return $user->can('create_activity::plan');
}
// 他のメソッドも同様
```

### PluginManager (UT-SUP-147 ~ UT-SUP-151)

**根拠コード** (`plugins/webkul/support/src/PluginManager.php`):
```php
public function getId(): string
{
    return 'plugin-manager';
}

public function register(Panel $panel): void
{
    $plugins = $this->getPlugins();
    foreach ($plugins as $modulePlugin) {
        $panel->plugin($modulePlugin::make());
    }
}

public static function make(): static
{
    return app(static::class);
}

public static function get(): static
{
    $plugin = filament(app(static::class)->getId());
    return $plugin;
}

protected function getPlugins(): array
{
    $plugins = require join_paths(base_path().'/bootstrap', 'plugins.php');
    $plugins = collect($plugins)
        ->unique()
        ->sort()
        ->values()
        ->toArray();
    return $plugins;
}
```

### PermissionManager (UT-SUP-152 ~ UT-SUP-158)

**根拠コード** (`plugins/webkul/support/src/PermissionManager.php`):
```php
public function managePermissions(): void
{
    FilamentShield::buildPermissionKeyUsing(function (string $entity, string $affix, string $subject): string {
        $affix = Str::snake($affix);

        if ($entity === 'BezhanSalleh\FilamentShield\Resources\Roles\RoleResource'
            || $entity === 'App\Filament\Resources\RoleResource') {
            return $affix . '_role';
        }

        if (class_exists($entity) && method_exists($entity, 'getModel')) {
            $resourceIdentifier = Str::of($entity)
                ->afterLast('Resources\\')
                ->beforeLast('Resource')
                ->replace('\\', '')
                ->snake()
                ->replace('_', '::')
                ->toString();

            if (in_array($entity, $this->getConflictingResources(), true)) {
                // プレフィックス付与ロジック
            }
            return "{$affix}_{$resourceIdentifier}";
        }

        if (Str::contains($entity, 'Pages\\')) {
            return 'page_' . Str::snake(class_basename($entity));
        }

        if (Str::contains($entity, 'Widgets\\') || Str::endsWith($entity, 'Widget')) {
            return 'widget_' . Str::snake(class_basename($entity));
        }

        return $affix . '_' . Str::snake($subject);
    });
}

protected function getConflictingResources(): array
{
    return [
        'Webkul\Blog\Filament\Admin\Clusters\Configurations\Resources\CategoryResource',
        // ... 他の競合リソース
    ];
}
```

## 信頼度サマリー

| 指標 | 値 |
|------|-----|
| 総テストケース数 | 158 |
| 根拠ありテストケース数 | 158 |
| 根拠なしテストケース数 | 0 |
| 信頼度 | 1.00 |

## 備考

- 全てのテストケースはソースコードの実装に基づいて作成されています
- 優先度「高」のテストケースは、金額計算（UOM変換）、認証・認可（Policies）、データ整合性（bootイベント）、メール送信サービスに関連するものです
- モデルのリレーション、スコープ、アクセサは標準的なEloquentの実装パターンに従っています
- サービスクラスはメール送信機能を提供し、成功/失敗のログ記録機能を含みます
