Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

array request not render and not examplae #853

Open
1 task done
haidv1992 opened this issue May 15, 2024 · 3 comments
Open
1 task done

array request not render and not examplae #853

haidv1992 opened this issue May 15, 2024 · 3 comments
Labels
awaiting requester action bug Something isn't working

Comments

@haidv1992
Copy link

haidv1992 commented May 15, 2024

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

@haidv1992 haidv1992 added bug Something isn't working triage labels May 15, 2024
@AryaSvitkona
Copy link

AryaSvitkona commented Oct 17, 2024

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?

@shalvah
Copy link
Contributor

shalvah commented Nov 5, 2024

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

@AryaSvitkona
Copy link

I'm not sure who should come forward, but this is the screenshot he provided.
image

If I understand him correctly, he would like to send directly an array to the endpoint.

[
       {
           "id": "foo",
           "status": "bar",
           ...
       },
   ]

We faced this issue too, but decided to use a data wrapper:

{
   "data":
   [
       {
           "id": "foo",
           "status": "bar",
           ...
       },
   ]
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting requester action bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants