knuckleswtf/scribe

array request not render and not examplae

haidv1992 opened this issue · 2 comments

Scribe version

4.35.0

PHP version

8.2

Framework

Laravel

Framework version

v10.48.10

Scribe config

php artisan scribe:config:diff
laravel.middleware.0 => ""
try_it_out.use_csrf => true
auth.enabled => true
auth.default => true

What happened?

File request

namespace Modules\Plan\app\Http\Requests;

use App\Models\PosUpdate;
use App\Pos;
use App\Support\Enum\ActionCode;
use Carbon\Carbon;
use Illuminate\Foundation\Http\FormRequest;
use Modules\Plan\app\Models\Plan;
use Modules\Plan\app\Models\PlanResult;
use Modules\Plan\app\Services\PlanConfigurationService;

class CheckinRequest extends FormRequest
{

    protected $bodyParams = array();

    /**
     * Get the validation rules that apply to the request.
     */
    public function rules(): array
    {
        $configService = app(PlanConfigurationService::class);
        $config = $configService->getCheckinConfiguration();
        $posChecklists = $configService->getChecklistsConfiguration();

        $rules = [
            'plan_id' => 'sometimes|nullable|exists:plans,id',
            'posCode' => 'required|exists:pos,posCode',
        ];
        if (!empty($posChecklists)) {
            $checklistCount = count($posChecklists);

            $rules['checklist'] = ['required', 'array', 'min:'.$checklistCount, 'max:'.$checklistCount];

            $checklistRules = [
                '*.id' => 'required|integer|exists:pos_checklists,id',
                '*.status' => 'required|boolean',
                '*.note' => 'nullable|string',
                '*.photo' => 'nullable|file|mimetypes:image/jpeg,image/jpg,image/png,image/webp|max:500',
            ];

            $rules['checklist.*'] = $checklistRules;

            foreach ($posChecklists as $item) {

                $this->bodyParams['checklist'][] = [
                    'id' => [
                        'description' => 'ID of the checklist item',
                        'example' => $item->id
                    ],
                    'status' => [
                        'description' => "Status of the checklist item {$item->name}",
                        'example' => true
                    ],
                    'note' => [
                        'description' => 'Note for the checklist item',
                        'example' => 'This is a sample note'
                    ],
                    'photo' => [
                        'description' => 'Optional photo for the checklist item',
                        'example' => new \Illuminate\Http\UploadedFile(public_path('assets/media/demo/checkin/img-1.png'), 'sample.jpg')
                    ],
                ];
            }
        }

        foreach ($config as $item) {
            $this->addConfigRules($rules, $item);
        }

        return $rules;
    }

    protected function prepareForValidation()
    {
        $checklistData = $this->input('checklist', []);
        foreach ($checklistData as $id => &$data) {
            if (isset($data['status'])) {
                $data['status'] = filter_var($data['status'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
            }
        }

        $this->merge([
            'checklist' => $checklistData,
        ]);
    }

    public function withValidator($validator)
    {

        $validator->after(function ($validator) {
            if (!$validator->errors()->isEmpty()) {
                return $validator;
            }

            $this->handleChecklistsValidation($validator);
            $this->handlePostValidation($validator);

        });
    }

    protected function handleChecklistsValidation($validator){
        $configService = app(PlanConfigurationService::class);

        $requiredChecklistIds = $configService->getChecklistsConfiguration()->pluck('id')->sort()->values()->all();

        $submittedChecklistIds = collect($this->input('checklist', []))
            ->pluck('id')
            ->sort()
            ->values()
            ->all();

        if ($requiredChecklistIds !== $submittedChecklistIds) {
            $validator->errors()->add('checklist', 'Tất cả các mục trong danh sách kiểm tra phải được bao gồm và khớp với ID được yêu cầu.');
        }
    }
    protected function handlePostValidation($validator)
    {
        $mergeData = [];
        $planOrPos = $this->planOrPosExists();
        $pendingUpdate = $this->checkForPendingUpdates($validator);

        if ($pendingUpdate) {
            $mergeData['pendingUpdate'] = $pendingUpdate;
        }

        if (!$this->input('plan_id')) {
            $this->handleNoPlanOrPos($validator, $mergeData);
            $lastAction = $mergeData['lastAction'];

        } elseif (!$planOrPos) {
            $validator->errors()->add('error', 'Không tìm thấy kế hoạch hoặc điểm bán phù hợp.');
            return;
        } else {
            $mergeData['plan'] = $planOrPos;
            $mergeData['pos'] = $planOrPos->pos()->where('posCode', $this->posCode)->first();
            $lastAction = $mergeData['pos']->visits()->latest()->first();
        }
        if ($lastAction && $lastAction->action_code == ActionCode::ACTION_CHECK_IN) {
            $validator->errors()->add('action_code', 'Check-out is required before check-in.');
        }

        $this->merge($mergeData);
    }

    protected function handleNoPlanOrPos($validator, &$mergeData)
    {
        $pos = Pos::where('posCode', $this->input('posCode'))->first();
        if (!$pos) {
            $validator->errors()->add('posCode', 'No valid POS found with provided POS Code.');
            return; // Exit early if no valid POS is found to prevent further processing
        }
        $lastAction = $pos->visits()->latest()->first();

        $mergeData['pos'] = $pos;
        $mergeData['lastAction'] = $lastAction; // Store last action to use in post validation checks
    }

    private function planOrPosExists()
    {
        $posCode = $this->input('posCode');

        return Plan::with('pos')
            ->where('month', Carbon::now()->format('Y-m'))
            ->where('status', Plan::STATUS_PLAN_APPROVED)
            ->whereHas('pos', function ($query) use ($posCode) {
                $query->where('posCode', $posCode);
            })
            ->find($this->input('plan_id'));
    }

    private function checkForPendingUpdates($validator)
    {
        $posCode = $this->input('posCode');
        $pendingUpdate = PosUpdate::where('pos_id', $posCode)
            ->where('status', PosUpdate::STATUS_PENDING)
            ->first();

        if ($pendingUpdate) {
            $existingCheckin = PlanResult::where('posCode', $posCode)
                ->where('pos_update_id', $pendingUpdate->id)
                ->first();
            if ($existingCheckin) {
                $validator->errors()->add('pos_update', 'Check-in không được phép do tồn tại một đề xuất đang chờ duyệt.');
            }
        }

        return $pendingUpdate;
    }

    protected function addConfigRules(&$rules, $item)
    {
        switch ($item['type']) {
            case 'file':
                $this->handleFileRules($rules, $item);
                break;
            case 'coordinates':
                $rules['lat'] = 'required|numeric';
                $rules['lon'] = 'required|numeric';
                break;
        }
    }

    protected function handleFileRules(&$rules, $item)
    {
        $baseKey = $item['key'];
        $rules[$baseKey] = [
            'required',
            'array',
            'max:' . $item['max_photos'],
        ];

        $rules[$baseKey . '.*'] = [
            'file',
            'mimetypes:' . $item['rules']['mimetypes'],
            'max:' . $item['rules']['max'],
            'dimensions:max_width=' . $item['rules']['max_width'],
        ];

        $this->bodyParams[$baseKey] = [
            'description' => "{$item['name']} - {$item['description']}",
            "required" => true,
            'example' => new \Illuminate\Http\UploadedFile(public_path('assets/media/demo/checkin/img-1.png'), 'sample.jpg')
        ];
    }


    public function bodyParameters()
    {
        return array_merge([
            'plan_id' => [
                'description' => 'ID kế hoạch',
                'example' => 1
            ],
            'posCode' => [
                'description' => 'Mã điểm bán',
                'example' => 124000010436
            ],
//            'checklist' => [
//                'description' => 'Điểm bán sẵn sàng để bán hàng?. [Get list](/docs#pos-GETapi-v1-pos-pos-readiness-checklists)',
//            ],
        ], $this->bodyParams);
    }

}

php artisan scribe:generate

image

Docs

We are facing the same "issue", since we won't use a data wrapper in our request like:

{
   "data":
   [
       {
           "name": "foo",
           "type": "bar"
       },
   ]
}

To make it short:
In the provided example you are trying to generate the documentation by using FormRequest, in where you check properties of an object which is provided as array. Right?

$checklistRules = [
      '*.id' => 'required|integer|exists:pos_checklists,id',
      '*.status' => 'required|boolean',
      '*.note' => 'nullable|string',
      '*.photo' => 'nullable|file|mimetypes:image/jpeg,image/jpg,image/png,image/webp|max:500',
  ];

Can scribe handle this?

What exactly is the issue? (Sorry for the late reply.) The image you uploaded gives me a 404.