resolve($token); if ($onboarding === null) { return $this->unavailable(); } $onboarding->load('files'); return view('onboarding.courier-form', [ 'onboarding' => $onboarding, 'token' => $token, 'provinces' => CourierOnboarding::PROVINCES, 'fileKinds' => CourierOnboardingFile::KINDS, 'requiredKinds' => CourierOnboardingFile::REQUIRED_KINDS, 'personKinds' => CourierOnboardingFile::PERSON_KINDS, 'personRequiredKinds' => CourierOnboardingFile::PERSON_REQUIRED_KINDS, ]); } public function update(Request $request, string $token): RedirectResponse|Response { $onboarding = $this->resolve($token); if ($onboarding === null) { return $this->unavailable(); } $data = $request->validate($this->rules(), $this->messages()); $files = []; foreach ((array) $request->file('files', []) as $kind => $uploads) { $files[(string) $kind] = array_values(array_filter(is_array($uploads) ? $uploads : [$uploads])); } $submit = ($data['action'] ?? 'save') === 'submit'; $this->service->saveCourierForm($onboarding, $data, $files, $submit); return redirect() ->route('courier-onboarding.form', ['token' => $token]) ->with('onboarding_success', $submit ? '资料已提交,我们会尽快审核,审核结果请留意对接人通知。' : '资料已暂存,可随时扫码回来继续填写。'); } private function resolve(string $token): ?CourierOnboarding { $onboarding = $this->service->ready() ? $this->service->findByToken($token) : null; if ($onboarding === null || $onboarding->status === CourierOnboarding::STATUS_CANCELLED) { return null; } // 免登录访问没有当前公司上下文;切到申请所属公司,后续按公司隔离的查询才能命中。 Company::find($onboarding->company_id)?->makeCurrent(); return $onboarding; } /** * 免登录页不走系统的 404 跳转(会跳到登录页),直接给快递方一个能看懂的提示。 */ private function unavailable(): Response { return response()->view('onboarding.courier-unavailable', [], 404); } /** * @return array */ private function rules(): array { $text = ['nullable', 'string', 'max:255']; $phone = ['nullable', 'string', 'regex:/^[0-9+\-\s()]{5,20}$/']; $money = ['nullable', 'numeric', 'min:0', 'max:100000']; return [ 'remark' => ['nullable', 'string', 'max:2000'], 'action' => ['required', Rule::in(['save', 'submit'])], 'entity_type' => ['nullable', Rule::in(array_keys(CourierOnboarding::ENTITY_TYPES))], 'id_number' => ['nullable', 'string', 'regex:/^\s*\d{17}[\dXx]\s*$/'], 'company_full_name' => $text, 'unified_social_credit_code' => ['nullable', 'string', 'regex:/^[0-9A-Za-z]{18}$/'], 'legal_representative' => ['nullable', 'string', 'max:100'], 'business_address' => $text, 'carrier_brand' => ['nullable', 'string', 'max:50'], 'network_name' => ['nullable', 'string', 'max:100'], 'service_type' => ['nullable', Rule::in(CourierOnboarding::SERVICE_TYPES)], 'contact_name' => ['nullable', 'string', 'max:100'], 'contact_phone' => $phone, 'finance_contact_name' => ['nullable', 'string', 'max:100'], 'finance_contact_phone' => $phone, 'pickup_warehouses' => ['nullable', 'string', 'max:1000'], 'esign_operator_name' => ['nullable', 'string', 'max:100'], 'esign_account' => ['nullable', 'string', 'max:255'], 'bank_account_name' => $text, 'bank_name' => $text, 'bank_account_number' => ['nullable', 'string', 'regex:/^[0-9\s\-]{6,40}$/'], 'private_account_name' => $text, 'private_bank_name' => $text, 'private_account_number' => ['nullable', 'string', 'regex:/^[0-9\s\-]{6,40}$/'], 'invoice_type' => ['nullable', Rule::in(CourierOnboarding::INVOICE_TYPES)], 'tax_rate' => ['nullable', 'numeric', 'min:0', 'max:20'], 'settlement_cycle' => ['nullable', Rule::in(CourierOnboarding::SETTLEMENT_CYCLES)], ...self::quoteRules(), 'remove_files' => ['nullable', 'array'], 'remove_files.*' => ['integer'], 'files' => ['nullable', 'array'], 'files.*' => ['array', 'max:' . CourierOnboardingService::MAX_FILES_PER_KIND], 'files.*.*' => ['file', 'max:10240', 'mimes:jpg,jpeg,png,webp,gif,heic,heif,pdf,xls,xlsx'], ]; } /** * 报价部分的校验规则,后台变价页共用。 * * @return array */ public static function quoteRules(): array { $money = ['nullable', 'numeric', 'min:0', 'max:100000']; return [ 'quote_mode' => ['nullable', Rule::in(array_keys(CourierOnboarding::QUOTE_MODES))], 'volumetric_rule' => ['nullable', Rule::in(CourierOnboarding::VOLUMETRIC_RULES)], 'volumetric_divisor' => ['nullable', 'integer', 'min:1000', 'max:20000'], 'quote_effective_date' => ['nullable', 'date'], 'quote_note' => ['nullable', 'string', 'max:2000'], 'quote_rows' => ['nullable', 'array', 'max:80'], 'quote_rows.*.region' => ['nullable', 'string', 'max:50'], 'quote_rows.*.flat' => ['nullable', 'array', 'max:' . \App\Support\CourierQuoteStructure::MAX_BANDS], 'quote_rows.*.flat.*' => $money, 'quote_rows.*.base' => $money, 'quote_rows.*.rates' => ['nullable', 'array', 'max:' . (\App\Support\CourierQuoteStructure::MAX_TIERS + 1)], 'quote_rows.*.rates.*' => $money, 'quote_flat_bands' => ['nullable', 'string', 'max:200'], 'quote_first_weight' => ['nullable', 'numeric', 'min:0', 'max:1000'], 'quote_rate_tiers' => ['nullable', 'string', 'max:200'], 'quote_then_mode' => ['nullable', Rule::in(['waybill_rate', 'first_additional'])], 'min_weight' => ['nullable', 'numeric', 'min:0', 'max:1000'], 'weight_rounding' => ['nullable', Rule::in(array_keys(\App\Support\CourierQuoteStructure::ROUNDING))], 'per_ticket_surcharge' => ['nullable', 'numeric', 'min:0', 'max:1000'], ]; } /** * @return array */ private function messages(): array { return [ 'unified_social_credit_code.regex' => '统一社会信用代码应为 18 位数字或字母。', 'contact_phone.regex' => '请填写正确的联系电话。', 'id_number.regex' => '身份证号应为 18 位。', 'finance_contact_phone.regex' => '请填写正确的联系电话。', 'bank_account_number.regex' => '银行账号只能包含数字。', 'private_account_number.regex' => '银行卡号只能包含数字。', 'volumetric_divisor.min' => '抛比系数一般为 5000、6000、8000 等。', 'files.*.*.max' => '单个文件不能超过 10MB。', 'files.*.*.mimes' => '请上传照片、PDF 或 Excel 文件。', ]; } }