so-manager-dev.com/app/Http/Controllers/RegularContractCreateController.php
2025-09-19 19:01:21 +09:00

1396 lines
64 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Session;
use Illuminate\Validation\Rule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Artisan;
use Exception;
use function redirect;
class RegularContractCreateController extends Controller
{
// 新規作成画面表示
public function show()
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 市町村名park→city JOINで重複排除
$cities = DB::table('park')
->join('city', 'park.city_id', '=', 'city.city_id')
->select('city.city_id', 'city.city_name')
->distinct()
->get();
// city_idごとの更新可能期間情報を取得
$city_grace_periods = DB::table('city')
->select('city_id', 'update_grace_period_start_date', 'update_grace_period_start_time', 'update_grace_period_end_date', 'update_grace_period_end_time')
->get()
->keyBy('city_id');
// 駅名stationテーブルのstation_neighbor_station全件
$stations = DB::table('station')
->select('station_neighbor_station')
->distinct()
->get();
// 駐輪場名parkテーブルのpark_name全件
$parks = DB::table('park')
->select('park_id', 'park_name')
->distinct()
->get();
// テーブル表示用データpark/city/station JOIN, park_id昇順, 10件ずつページング
$page = request()->input('page', 1);
$perPage = 10;
$city_id = request()->input('city_id');
$station_name = request()->input('station_neighbor_station');
$park_id = request()->input('park_id');
$query = DB::table('park')
->join('city', 'park.city_id', '=', 'city.city_id')
->leftJoin('station', 'park.park_id', '=', 'station.park_id')
->select(
'park.park_id',
'park.park_name',
'park.park_ruby',
'city.city_name',
'city.city_id',
'station.station_neighbor_station',
'station.station_name_ruby'
);
if ($city_id) {
$query->where('city.city_id', $city_id);
}
if ($station_name) {
$query->where('station.station_neighbor_station', $station_name);
}
if ($park_id) {
$query->where('park.park_id', $park_id);
}
// 並び替えパラメータ取得
$sort = request()->input('sort', 'park_id');
$order = request()->input('order', 'asc');
$sortable = [
'park_ruby' => 'park.park_ruby',
'city_id' => 'city.city_id',
'station_name_ruby' => 'station.station_name_ruby',
'park_id' => 'park.park_id',
];
if (isset($sortable[$sort])) {
$query->orderBy($sortable[$sort], $order);
} else {
$query->orderBy('park.park_id', 'asc');
}
$total = $query->count();
$parks_table = $query->skip(($page - 1) * $perPage)->take($perPage)->get();
// zoneテーブルデータを取得psectionテーブルとJOINしてpsection_subjectも取得
$zones = DB::table('zone')
->leftJoin('psection', 'zone.psection_id', '=', 'psection.psection_id')
->select('zone.zone_id', 'zone.park_id', 'zone.psection_id', 'zone.zone_number', 'zone.zone_tolerance', 'psection.psection_subject')
->get()
->groupBy('park_id');
// 空き予約マスタデータを取得
$reserve = DB::table('reserve')
->select('reserve_id', 'park_id', 'psection_id')
->where('valid_flag', 1)
->get()
->groupBy('park_id');
\Log::info('新規定期契約-駐輪場選択画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.create', [
'active_menu' => 'SWC-8-1', // この画面のID
'user_name' => $user ? $user->user_name : '', // ユーザー名(ヘッダー用)
'cities' => $cities,
'stations' => $stations,
'parks' => $parks,
'parks_table' => $parks_table,
'parks_table_total' => $total,
'parks_table_page' => $page,
'parks_table_perPage' => $perPage,
'zones' => $zones,
'city_grace_periods' => $city_grace_periods,
'reserve' => $reserve,
]);
}
public function regulationCheck(Request $request)
{
// GETパラメータを取得
$parkId = $request->query('park_id');
$psectionId = $request->query('psection_id');
$ptypeId = $request->query('ptype_id');
// 必要なDB処理やロジック
$park_regulation = DB::table('park')->where('park_id', $parkId)->value('parking_regulations_flag');
if ($park_regulation == 1) {
// 駐輪規定画面へ
return redirect()->route('regular_contract.regulation', [
'park_id' => $parkId,
'psection_id' => $psectionId,
'ptype_id' => $ptypeId,
]);
} else {
// 契約情報入力画面へ
return redirect()->route('regular_contract.input', [
'park_id' => $parkId,
'psection_id' => $psectionId,
'ptype_id' => $ptypeId,
]);
}
}
public function showRegulation(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
// 必要なパラメータ取得
$parkId = $request->query('park_id');
$psectionId = $request->query('psection_id');
$ptypeId = $request->query('ptype_id');
$park_name = DB::table('park')->where('park_id', $parkId)->value('park_name');
$psection_subject = DB::table('psection')->where('psection_id', $psectionId)->value('psection_subject');
$ptype_subject = DB::table('ptype')->where('ptype_id', $ptypeId)->value('ptype_subject');
$regulations_text = DB::table('parking_regulations')
->where('park_id', $parkId)
->where('psection_id', $psectionId)
->where('ptype_id', $ptypeId)
->value('regulations_text');
\Log::info('駐輪規定確認画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.regulation', [
'park_id' => $parkId,
'park_name' => $park_name,
'psection_id' => $psectionId,
'psection_subject' => $psection_subject,
'ptype_id' => $ptypeId,
'ptype_subject' => $ptype_subject,
'regulations_text' => $regulations_text,
'active_menu' => 'SWC-8-1', // この画面のID
'user_name' => $user_name, // ユーザー名(ヘッダー用)
]);
}
public function insertRegulation(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$park_id = $request->input('park_id');
$psection_id = $request->input('psection_id');
$ptype_id = $request->input('ptype_id');
DB::table('parking_regulations_read')->insert([
'park_id' => $park_id,
'psection_id' => $psection_id,
'ptype_id' => $ptype_id,
'user_id' => $user_id,
'created_at' => now(),
]);
// 契約入力画面へリダイレクト
return redirect()->route('regular_contract.input', [
'park_id' => $park_id,
'psection_id' => $psection_id,
'ptype_id' => $ptype_id,
]);
}
// 契約入力画面表示
public function showContractForm(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
$park_id = $request->query('park_id');
$park = DB::table('park')->where('park_id', $park_id)->first();
$psection_id = $request->query('psection_id');
$ptype_id = $request->query('ptype_id');
$city_id = DB::table('park')->where('park_id', $park_id)->value('city_id');
$terms_text = DB::table('terms')->where('city_id', $city_id)->value('terms_text');
// 利用者区分をusertypeテーブルから取得
$user_category = '';
if (isset($user->user_categoryid)) {
$usertype = DB::table('usertype')
->where('user_categoryid', $user->user_categoryid)
->first();
if ($usertype && isset($usertype->usertype_subject1)) {
$user_category = $usertype->usertype_subject1;
}
}
$user->user_homephone = explode('-', $user->user_homephone ?? '');
$user->user_mobile = explode('-', $user->user_mobile ?? '');
$user->user_regident_zip_1 = substr($user->user_regident_zip ?? '', 0, 3);
$user->user_regident_zip_2 = substr($user->user_regident_zip ?? '', 3, 4);
$user->user_relate_zip_1 = substr($user->user_relate_zip ?? '', 0, 3);
$user->user_relate_zip_2 = substr($user->user_relate_zip ?? '', 3, 4);
\Log::info('新規定期契約-契約情報入力画面にアクセス', [
'user_id' => $user_id,
]);
session()->forget('show_terms_modal');
return view('regular_contract.input', [
'park' => $park,
'psection_id' => $psection_id,
'ptype_id' => $ptype_id,
'terms_text' => $terms_text,
'user' => $user,
'user_name' => $user->user_name, // ユーザー名(ヘッダー用)
'active_menu' => 'SWC-8-1', // この画面のID
'user_category' => $user_category,
'show_terms_modal' => true,
]);
}
public function inputCheck(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
$park_id = $request->input('park_id');
$park = DB::table('park')->where('park_id', $park_id)->first();
// バリデーションルール
$rules = [
'user_phonetic' => ['required', 'regex:/^[ァ-ヶー \s]+$/u'],
'user_regident_zip_1' => 'required|digits:3',
'user_regident_zip_2' => 'required|digits:4',
'user_regident_pre' => [
'required',
Rule::in([
'北海道',
'青森県',
'岩手県',
'宮城県',
'秋田県',
'山形県',
'福島県',
'茨城県',
'栃木県',
'群馬県',
'埼玉県',
'千葉県',
'東京都',
'神奈川県',
'新潟県',
'富山県',
'石川県',
'福井県',
'山梨県',
'長野県',
'岐阜県',
'静岡県',
'愛知県',
'三重県',
'滋賀県',
'京都府',
'大阪府',
'兵庫県',
'奈良県',
'和歌山県',
'鳥取県',
'島根県',
'岡山県',
'広島県',
'山口県',
'徳島県',
'香川県',
'愛媛県',
'高知県',
'福岡県',
'佐賀県',
'長崎県',
'熊本県',
'大分県',
'宮崎県',
'鹿児島県',
'沖縄県'
]),
],
'user_regident_city' => ['required', 'string', 'max:20', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'],
'user_regident_add' => ['required', 'string', 'max:50', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'],
'user_homephone.*' => 'nullable|digits_between:1,5',
'user_mobile.*' => 'nullable|digits_between:1,5',
'user_submail' => 'nullable|email|different:user_primemail|max:80',
'user_category' => ['required', Rule::in(['一般', '学生'])],
'contract_reduction' => ['required', Rule::in(['はい', 'いいえ'])],
'user_relate_zip_1' => 'nullable|digits:3',
'user_relate_zip_2' => 'nullable|digits:4',
'user_relate_pre' => [
'nullable',
Rule::in([
'北海道',
'青森県',
'岩手県',
'宮城県',
'秋田県',
'山形県',
'福島県',
'茨城県',
'栃木県',
'群馬県',
'埼玉県',
'千葉県',
'東京都',
'神奈川県',
'新潟県',
'富山県',
'石川県',
'福井県',
'山梨県',
'長野県',
'岐阜県',
'静岡県',
'愛知県',
'三重県',
'滋賀県',
'京都府',
'大阪府',
'兵庫県',
'奈良県',
'和歌山県',
'鳥取県',
'島根県',
'岡山県',
'広島県',
'山口県',
'徳島県',
'香川県',
'愛媛県',
'高知県',
'福岡県',
'佐賀県',
'長崎県',
'熊本県',
'大分県',
'宮崎県',
'鹿児島県',
'沖縄県'
]),
],
'user_relate_city' => ['nullable', 'string', 'max:20', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'],
'user_relate_add' => ['nullable', 'string', 'max:50', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'],
];
// 性別欄が表示されている場合のみ必須
if ((int)$park->gender_display_flag === 1) {
$rules['user_gender'] = ['required', Rule::in(['男性', '女性'])];
}
// 生年月日欄が表示されている場合のみ必須
if ((int)$park->bd_display_flag === 1) {
$rules['user_birthdate'] = ['required', 'date'];
}
// 防犯登録番号欄が表示されている場合のみ必須
if ((int)$park->securityreg_display_flag === 1) {
$rules['user_securitynum'] = ['required', 'max:50', 'regex:/^[a-zA-Z0-9]+$/'];
}
// 利用者区分ごとの追加バリデーション
if ($request->input('user_category') === '学生') {
$rules['user_school'] = ['required', 'string', 'max:50', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'];
$rules['user_graduate'] = ['required', 'date'];
} else {
$rules['user_workplace'] = ['nullable', 'string', 'max:50', 'regex:/^(?:(?![\xF0-\xF7][\x80-\xBF]{3}).)*$/'];
}
$messages = [
'user_phonetic.required' => 'フリガナが入力されていません。',
'user_phonetic.regex' => 'フリガナはカタカナでご入力ください。',
'user_gender.required' => '性別が入力されていません。',
'user_gender.in' => '性別は「男性」または「女性」を選択してください。',
'user_regident_zip_1.required' => '居住所の郵便番号前半3桁が入力されていません。',
'user_regident_zip_2.required' => '居住所の郵便番号後半4桁が入力されていません。',
'user_regident_zip_1.digits' => '居住所の郵便番号前半3桁は3桁の数字で入力してください。',
'user_regident_zip_2.digits' => '居住所の郵便番号後半4桁は4桁の数字で入力してください。',
'user_regident_pre.required' => '居住所の都道府県が選択されていません。',
'user_regident_pre.in' => '都道府県は選択肢から選んでください。',
'user_regident_city.required' => '居住所の市区町村が入力されていません。',
'user_regident_city.max' => '居住所の市区町村は20文字以内で入力してください。',
'user_regident_city.regex' => '居住所の市区町村に絵文字などの特殊文字は使用できません。',
'user_regident_add.required' => '居住所の住所が入力されていません。',
'user_regident_add.max' => '居住所の住所は50文字以内で入力してください。',
'user_regident_add.regex' => '居住所の住所に絵文字などの特殊文字は使用できません。',
'user_birthdate.required' => '生年月日が入力されていません。',
'user_birthdate.date' => '生年月日は正しい日付で入力してください。',
'user_homephone.*.digits_between' => '自宅電話番号はそれぞれ15桁の数字で入力してください。',
'user_mobile.*.digits_between' => '携帯電話番号はそれぞれ15桁の数字で入力してください。',
'user_submail.email' => '予備メールアドレスは正しい形式で入力してください。',
'user_submail.max' => '予備メールアドレスは80文字以内で入力してください。',
'user_submail.different' => 'メールアドレスと予備メールアドレスに同じアドレスを入力できません。',
'user_category.required' => '利用者区分は必須です。',
'user_category.in' => '利用者区分の値が不正です。',
'contract_reduction.required' => '減免が選択されていません。',
'contract_reduction.in' => '減免の値が不正です。',
'user_workplace.max' => '勤務先は50文字以内で入力してください。',
'user_workplace.regex' => '勤務先に絵文字などの特殊文字は使用できません。',
'user_school.required' => '学校名が入力されていません。',
'user_school.max' => '学校名は50文字以内で入力してください。',
'user_school.regex' => '学校名に絵文字などの特殊文字は使用できません。',
'user_graduate.required' => '卒業年月日が入力されていません。',
'user_graduate.date' => '卒業年月日は正しい日付で入力してください。',
'user_relate_zip_1.digits' => '住所の郵便番号前半3桁は3桁の数字で入力してください。',
'user_relate_zip_2.digits' => '住所の郵便番号後半4桁は4桁の数字で入力してください。',
'user_relate_pre.in' => '住所の都道府県は選択肢から選んでください。',
'user_relate_city.max' => '住所の市区町村は20文字以内で入力してください。',
'user_relate_city.regex' => '住所の市区町村に絵文字などの特殊文字は使用できません。',
'user_relate_add.max' => '住所は50文字以内で入力してください。',
'user_relate_add.regex' => '住所に絵文字などの特殊文字は使用できません。',
'user_securitynum.required' => '防犯登録番号が入力されていません。',
'user_securitynum.max' => '防犯登録番号は50文字以内で入力してください。',
'user_securitynum.regex' => '防犯登録番号は英数字のみで入力してください。',
];
try {
$validated = $request->validate($rules, $messages);
} catch (ValidationException $e) {
return Redirect()->back()
->withErrors($e->validator)
->withInput($request->except('_token') + ['show_terms_modal' => false]);
}
if (empty(implode('', $request->input('user_homephone', []))) && empty(implode('', $request->input('user_mobile', [])))) {
return redirect()->back()
->withErrors(['user_homephone' => '自宅電話番号または携帯電話番号のいずれかは必須です'])
->withInput($request->except('_token') + ['show_terms_modal' => false]);
}
$city = DB::table('city')->where('city_id', $park->city_id)->first();
$matched = (mb_strpos($request->user_regident_city, $city->city_name) !== false);
if ($matched === true) {
$ward_residents = 1;
DB::table('user')
->where('user_id', $user->user_id)
->update(['ward_residents' => $ward_residents]);
} else {
$contract_allowable_city_name = DB::table('contract_allowable_city')->where('city_id', $city->city_id)->value('contract_allowable_city_name');
$matched_allowable = (mb_strpos($request->user_regident_city, $contract_allowable_city_name) !== false);
if ($matched_allowable) {
$ward_residents = 0;
DB::table('user')
->where('user_id', $user->user_id)
->update(['ward_residents' => $ward_residents]);
} else {
return redirect()->back()->withErrors(['契約対象地域にお住まいでないためお申込みできません'])->withInput()->with(['show_terms_modal' => false]);
}
}
if ($ward_residents == 1) {
$usertype_subject2 = '区民';
} else {
$usertype_subject2 = '区民外';
}
$user_categoryid = DB::table('usertype')
->where('usertype_subject1', $request->user_category)
->where('usertype_subject2', $usertype_subject2)
->value('user_categoryid');
$updateData = [
'user_categoryid' => $user_categoryid,
'user_phonetic' => $request->user_phonetic,
'user_mobile' => implode('-', $request->input('user_mobile', [])),
'user_homephone' => implode('-', $request->input('user_homephone', [])),
'user_submail' => $request->filled('user_submail') ? $request->user_submail : null,
'user_regident_zip' => $request->user_regident_zip_1 . $request->user_regident_zip_2,
'user_regident_pre' => $request->user_regident_pre,
'user_regident_city' => $request->user_regident_city,
'user_regident_add' => $request->user_regident_add,
'user_relate_zip' => ($request->filled('user_relate_zip_1') && $request->filled('user_relate_zip_2')) ? ($request->user_relate_zip_1 . $request->user_relate_zip_2) : null,
'user_relate_pre' => $request->filled('user_relate_pre') ? $request->user_relate_pre : null,
'user_relate_city' => $request->filled('user_relate_city') ? $request->user_relate_city : null,
'user_relate_add' => $request->filled('user_relate_add') ? $request->user_relate_add : null,
'ward_residents' => $ward_residents,
'user_workplace' => $request->user_category === '一般' ? $request->user_workplace : null,
'user_school' => $request->user_category === '学生' ? $request->user_school : null,
'user_graduate' => $request->user_category === '学生' ? $request->user_graduate : null,
'updated_at' => now()
];
// 性別欄が表示されている場合のみ追加
if (!empty($park->gender_display_flag) && $park->gender_display_flag == 1) {
$updateData['user_gender'] = $request->user_gender;
}
// 生年月日欄が表示されている場合のみ追加
if (!empty($park->bd_display_flag) && $park->bd_display_flag == 1) {
$updateData['user_birthdate'] = $request->user_birthdate;
$updateData['user_age'] = $request->user_age;
}
DB::table('user')
->where('user_id', $user->user_id)
->update($updateData);
$zone_id = DB::table('zone')
->where('park_id', $request->park_id)
->where('ptype_id', $request->ptype_id)
->where('psection_id', $request->psection_id)
->orderBy('zone_sort', 'asc')
->value('zone_id');
$contract_id = DB::table('regular_contract')->insertGetId([
'created_at' => now(),
'updated_at' => now(),
'user_id' => $user->user_id,
'user_categoryid' => $user_categoryid,
'park_id' => $park->park_id,
'contract_created_at' => now(),
'contract_reduction' => $ward_residents,
'update_flag' => 2,
'contract_cancel_flag' => 0,
'psection_id' => $request->psection_id,
'ptype_id' => $request->ptype_id,
'zone_id' => $zone_id
]);
$contractUpdateData = [
'contract_qr_id' => DB::raw("TO_BASE64(AES_ENCRYPT($contract_id, 'LJLASR4FAS34SAADFA72ASDFALLSDRGT'))")
];
// 防犯登録番号が表示されている場合のみ追加
if (!empty($park->securityreg_display_flag) && $park->securityreg_display_flag == 1) {
$contractUpdateData['user_securitynum'] = $request->user_securitynum;
}
DB::table('regular_contract')
->where('contract_id', $contract_id)
->update($contractUpdateData);
return redirect()->route('regular_contract.upload_identity_create', [
'contract_id' => $contract_id,
]);
}
public function showUploadIdentityCreate(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user_name = DB::table('user')->where('user_id', $user_id)->value('user_name');
$contract = DB::table('regular_contract')->where('contract_id', $request->contract_id)->first();
\Log::info('新規定期契約-本人確認書類アップロード画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.upload_identity_create', [
'contract_id' => $request->contract_id,
'park_id' => $contract->park_id,
'psection_id' => $contract->psection_id,
'ptype_id' => $contract->ptype_id,
'user_name' => $user_name,
'active_menu' => 'SWC-8-1'
]);
}
public function confirmUploadIdentity(Request $request, $contract_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$validator = Validator::make($request->all(), [
'idcard_type' => 'required',
'user_idcard' => 'required|file|mimes:jpg,jpeg,png,pdf',
], [
'idcard_type.required' => '本人確認書類の種類を選択してください。',
'user_idcard.required' => '本人確認書類のおもて画像をアップロードしてください。',
'user_idcard.file' => '本人確認書類のおもて画像はファイルで指定してください。',
'user_idcard.mimes' => 'アップロードできるファイル形式はjpg、jpeg、png、pdfのみです。',
]);
if ($validator->fails()) {
return redirect()->route('regular_contract.upload_identity_create', [
'contract_id' => $contract_id,
])
->withErrors($validator)
->withInput();
}
// おもて画像保存Laravel Storageを使用
$front = $request->file('user_idcard');
$filename_front = uniqid('photo1_') . '.' . $front->getClientOriginalExtension();
$front->storeAs('photo', $filename_front, 'public');
// userテーブルに保存チェック済フラグはSHJ-1処理後に設定
$updateData = [
'photo_filename1' => $filename_front,
'user_idcard' => $request->idcard_type,
'updated_at' => now(),
];
// ウラ画像がある場合保存し更新項目に追加
if ($request->hasFile('user_idcard2')) {
$back = $request->file('user_idcard2');
$filename_back = uniqid('photo2_') . '.' . $back->getClientOriginalExtension();
$back->storeAs('photo', $filename_back, 'public');
$updateData['photo_filename2'] = $filename_back;
}
DB::table('user')->where('user_id', $user_id)->update($updateData);
// SHJ-1 本人確認自動処理を実行
$user = DB::table('user')->where('user_id', $user_id)->first();
$park = DB::table('park')->where('park_id', $request->park_id)->first();
$psection = DB::table('psection')->where('psection_id', $request->psection_id)->first();
$usertype = DB::table('usertype')->where('user_categoryid', $user->user_categoryid)->first();
// user_idからuser_seqを取得してSHJ-1に渡す
$user_seq = $user->user_seq;
$park_id = $request->park_id;
\Log::info('SHJ-1バッチ処理開始', [
'user_id' => $user_id,
'user_seq' => $user_seq,
'park_id' => $park_id,
'contract_id' => $contract_id
]);
try {
// SHJ-1 コマンドを同期実行
$exitCode = Artisan::call('shj:one', [
'user_id' => $user_seq,
'park_id' => $park_id
]);
\Log::info('SHJ-1バッチ処理完了', [
'exit_code' => $exitCode,
'user_seq' => $user_seq,
'park_id' => $park_id
]);
// 処理結果に基づいて遷移先を決定
if ($exitCode === 0) {
// 成功の場合
return redirect("/regular-contract/upload_identity_success?contract_id={$contract_id}");
// return view('regular_contract.create_confirm', [
// 'contract_id' => $request->contract_id,
// 'user' => $user,
// 'park' => $park,
// 'psection' => $psection,
// 'usertype' => $usertype,
// 'user_name' => $user->user_name,
// 'active_menu' => 'SWC-8-1'
// ]);
} else {
// 失敗の場合 または、学生証 と その他 の場合
return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}");
// return view('regular_contract.create_confirm', [
// 'contract_id' => $request->contract_id,
// 'user' => $user,
// 'park' => $park,
// 'psection' => $psection,
// 'usertype' => $usertype,
// 'user_name' => $user->user_name,
// 'active_menu' => 'SWC-8-1'
// ]);
}
} catch (\Exception $e) {
\Log::error('SHJ-1バッチ処理でエラー発生', [
'error' => $e->getMessage(),
'user_seq' => $user_seq,
'park_id' => $park_id
]);
return redirect("/regular-contract/upload_identity_fail?contract_id={$contract_id}");
}
}
public function createConfirmNext($contract_id)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
$user = DB::table('user')->where('user_id', $user_id)->first();
// 本人確認自動処理結果を取得
if ($user && $user->user_idcard_chk_flag == 2) {
// 本人確認OKの場合は利用期間選択画面へ
// 必要な各マスタ情報を取得
$contract = DB::table('regular_contract')->where('contract_id', $contract_id)->first();
$park = DB::table('park')->where('park_id', $contract->park_id)->first();
$city = DB::table('city')->where('city_id', $park->city_id)->first();
$regular_type = DB::table('regular_type')->where('city_id', $city->city_id)->first();
$usertype = DB::table('usertype')->where('user_categoryid', $contract->user_categoryid)->first();
// 2重化しているマスタのため現在のテーブル名を取得
$master_setting = DB::table('setting')->value('web_master');
$tableName = 'price' . $master_setting;
// 利用者区分に応じた逆利用フラグを取得
$inverse_use_flag_column = ($usertype->usertype_subject1 == '一般') ? 'inverse_use_flag1' : 'inverse_use_flag2';
$inverse_use_flag = $park->$inverse_use_flag_column;
if ($inverse_use_flag == 0) {
// regident_cityまたはrelate_cityが一致するか
$is_same = (
strpos($user->user_regident_city, $city->city_name) !== false ||
strpos($user->user_relate_city, $city->city_name) !== false
);
} else {
// regident_cityのみ一致するか
$is_same = (strpos($user->user_regident_city, $city->city_name) !== false);
}
$target_subject2 = $is_same ? '区民' : '区民外';
$user_categoryid = DB::table('usertype')
->where('usertype_subject1', $usertype->usertype_subject1)
->where('usertype_subject2', $target_subject2)
->where('usertype_subject3', $usertype->usertype_subject3)
->value('user_categoryid');
// 駐輪場所マスタから料金を取得
$prices = DB::table($tableName)
->where('park_id', $contract->park_id)
->where('psection_id', $contract->psection_id)
->where('ptype_id', $contract->ptype_id)
->where('user_categoryid', $user_categoryid)
->get();
\Log::info('利用期間選択画面にアクセス', [
'user_id' => $user_id,
]);
// 利用期間選択画面へ遷移
return view('regular_contract.create_select_period', [
'active_menu' => 'SWC-4-1', // マイページメニューの選択状態用
'user_name' => $user->user_name, // ユーザー名(ヘッダー用)
'contract_id' => $contract_id,
'regular_type' => $regular_type,
'prices' => $prices,
]);
} else {
// NGの場合は本人確認書類確認中画面へ
\Log::info('本人確認書類確認中画面にアクセス', [
'user_id' => $user_id,
]);
return view('regular_contract.create_idcard_checking', [
'active_menu' => 'SWC-8-1', // マイページメニューの選択状態用
'user_name' => $user->user_name, // ユーザー名(ヘッダー用)
]);
}
}
public function selectPeriod(Request $request)
{
$user_id = session('user_id');
if (!$user_id) {
return redirect('/login');
}
// 期間選択チェック
$request->validate([
'month' => 'required',
], [
'month.required' => '契約期間が選択されていません。',
]);
$contract_id = $request->input('contract_id');
$month = $request->input('month');
$price = $request->input('price_' . $month);
$today = now();
$day = $today->day;
if ($day <= 19) {
// 今月の1日
$contract_periods = $today->copy()->startOfMonth()->format('Y-m-d');
} else {
// 翌月の1日
$contract_periods = $today->copy()->addMonth()->startOfMonth()->format('Y-m-d');
}
$contract_periode = Carbon::parse($contract_periods)->addMonths($month - 1)->endOfMonth()->format('Y-m-d');
// 契約更新
DB::table('regular_contract')->where('contract_id', $contract_id)->update([
'enable_months' => $month,
'billing_amount' => $price,
'contract_periods' => $contract_periods,
'contract_periode' => $contract_periode,
'updated_at' => now(),
]);
// 完了後はウェルネット決済画面(仮)へリダイレクト
return redirect()->route('wellnet.payment');
}
/**
* SHJ-1本人確認処理成功ページ表示テスト後に削除してviewも
*/
public function showUploadIdentitySuccess(Request $request)
{
$contractId = $request->get('contract_id');
$userId = Session::get('user_id');
// 詳細情報を取得
$debugInfo = $this->getShjDebugInfo($userId, $contractId);
return view('regular_contract.upload_identity_success', compact('debugInfo', 'contractId'));
}
/**
* SHJ-1本人確認処理失敗ページ表示テスト後に削除してviewも
*/
public function showUploadIdentityFail(Request $request)
{
$contractId = $request->get('contract_id');
$userId = Session::get('user_id');
// 詳細情報を取得
$debugInfo = $this->getShjDebugInfo($userId, $contractId);
return view('regular_contract.upload_identity_fail', compact('debugInfo', 'contractId'));
}
/**
* SHJ-1デバッグ情報取得テスト後に削除して
*/
private function getShjDebugInfo($userId, $contractId)
{
try {
// ユーザー情報取得
$user = DB::table('user')->where('user_id', $userId)->first();
// 契約情報取得
$contract = DB::table('regular_contract')->where('user_id', $user->user_id ?? 0)->first();
// 駐輪場情報取得
$park = null;
if ($contract) {
$park = DB::table('park')->where('park_id', $contract->park_id)->first();
}
// 最新のSHJ-1バッチログ取得
$batchLog = DB::table('batch_log')
->where('process_name', 'SHJ-1本人確認自動処理')
->orderBy('created_at', 'desc')
->first();
// 最新のログファイルから詳細ログを取得
$logContent = $this->getRecentShjLogs();
// ログからAPI結果情報を解析
$apiResults = $this->parseApiResultsFromLogs($logContent);
return [
'user' => $user,
'contract' => $contract,
'park' => $park,
'batch_log' => $batchLog,
'detailed_logs' => $logContent,
'recent_logs' => $logContent, // 为调试页面提供日志内容访问
'timestamp' => now()->format('Y-m-d H:i:s'),
// API結果情報
'ocr_text_length' => $apiResults['ocr_text_length'],
'ocr_text_preview' => $apiResults['ocr_text_preview'],
'ocr_full_text' => $apiResults['ocr_full_text'],
'ocr_threshold' => $apiResults['ocr_threshold'],
'name_similarity' => $apiResults['name_similarity'],
'address_similarity' => $apiResults['address_similarity'],
'name_passed' => $apiResults['name_passed'],
'address_passed' => $apiResults['address_passed'],
'matched_address_type' => $apiResults['matched_address_type'] ?? null, // 新增:匹配成功的地址类型
'name_match_attempts' => $apiResults['name_match_attempts'],
'address_match_attempts' => $apiResults['address_match_attempts'],
'best_name_match' => $apiResults['best_name_match'],
'best_address_match' => $apiResults['best_address_match'],
'name_match_details' => $apiResults['name_match_details'],
'address_match_details' => $apiResults['address_match_details'],
'ocr_debug_info' => $apiResults['ocr_debug_info'] ?? null,
// 移除重复分析逻辑只显示SHJ-1的结果
'calculated_distance' => $apiResults['calculated_distance'],
'distance_text' => $apiResults['distance_text'] ?? null,
'distance_limit' => $apiResults['distance_limit'] ?? null,
'distance_start_address' => $apiResults['distance_start_address'] ?? null,
'distance_end_address' => $apiResults['distance_end_address'] ?? null,
'distance_passed' => $apiResults['distance_passed'],
'maps_api_status' => $apiResults['maps_api_status'],
'maps_api_error' => $apiResults['maps_api_error']
];
} catch (Exception $e) {
\Log::error('Debug info error: ' . $e->getMessage());
return [
'error' => 'デバッグ情報の取得に失敗しました: ' . $e->getMessage(),
'timestamp' => now()->format('Y-m-d H:i:s')
];
}
}
/**
* 最新のSHJ-1関連ログを取得テスト後に削除して
*/
private function getRecentShjLogs()
{
try {
$logPath = storage_path('logs/laravel.log');
if (!file_exists($logPath)) {
return '日志文件未找到';
}
$logs = file_get_contents($logPath);
$lines = explode("\n", $logs);
// 最新の1000行からSHJ-1関連ログを抽出さらに範囲拡大
$recentLines = array_slice($lines, -1000);
$shjLogs = [];
foreach ($recentLines as $line) {
if (strpos($line, 'SHJ-1') !== false ||
strpos($line, 'GoogleVision') !== false ||
strpos($line, 'Google Maps') !== false ||
strpos($line, 'バッチ処理') !== false) {
$shjLogs[] = $line;
}
}
return implode("\n", array_slice($shjLogs, -200)); // 最新200行さらに拡大
} catch (Exception $e) {
return 'ログ取得エラー: ' . $e->getMessage();
}
}
/**
* ログからAPI結果情報を解析テスト後に削除して
*/
private function parseApiResultsFromLogs($logContent)
{
$results = [
'ocr_text_length' => 'N/A',
'ocr_text_preview' => 'N/A',
'ocr_threshold' => 'N/A',
'name_similarity' => 'N/A',
'address_similarity' => 'N/A',
'name_passed' => false,
'address_passed' => false,
'calculated_distance' => 'N/A',
'distance_passed' => false,
'maps_api_status' => '成功',
'maps_api_error' => 'なし',
// 新增详细OCR信息
'ocr_full_text' => 'N/A',
'name_match_attempts' => [],
'address_match_attempts' => [],
'best_name_match' => 'N/A',
'best_address_match' => 'N/A',
'match_details' => 'N/A'
];
try {
// OCR処理完了ログから文字数とプレビューを抽取清理格式
if (preg_match('/GoogleVision OCR処理完了.*"text_length":(\d+).*"text_preview":"([^"]*)"/', $logContent, $matches)) {
$results['ocr_text_length'] = $matches[1];
// 清理OCR文本移除可能的日志污染
$cleanText = $matches[2];
// 移除可能混入的日志时间戳和标记
$cleanText = preg_replace('/\[\d{4}-\d{2}-\d{2}.*?\].*?local\.INFO.*?$/', '', $cleanText);
// 移除末尾的不完整JSON片段
$cleanText = preg_replace('/\s*\{?\s*$/', '', $cleanText);
// 清理换行和多余空格
$cleanText = trim($cleanText);
$results['ocr_text_preview'] = $cleanText;
}
// 尝试从GoogleVision日志提取完整的OCR文本
if (preg_match('/GoogleVision OCR処理完了.*"text_full":"([^"]*)"/', $logContent, $matches)) {
$fullText = $matches[1];
// 解码JSON转义字符
$fullText = str_replace('\n', "\n", $fullText);
$fullText = str_replace('\r', "\r", $fullText);
$fullText = str_replace('\"', '"', $fullText);
$fullText = str_replace('\\\\', '\\', $fullText);
$results['ocr_full_text'] = $fullText;
}
// 尝试从SHJ-1日志提取完整的OCR文本多种模式匹配
$patterns = [
'/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 原始日文版本
'/螳悟・OCR隱崎ュ倡オ先棡.*"ocr_full_text":"([^"]*)"/', // 日文编码版本
'/OCR認識結果.*"ocr_full_text":"([^"]*)"/', // 简化匹配
'/"ocr_full_text":"([^"]*)"/', // 最宽泛匹配
];
foreach ($patterns as $pattern) {
if (empty($results['ocr_full_text']) && preg_match($pattern, $logContent, $matches)) {
$encodedText = $matches[1];
// Base64解码
$fullText = base64_decode($encodedText);
if ($fullText !== false && strlen($fullText) > 10) {
$results['ocr_full_text'] = $fullText;
// 简化OCR信息显示 - 只显示基本信息
$results['ocr_debug_info'] = [
'decoded_length' => strlen($fullText),
'preview' => substr($fullText, 0, 200)
];
break; // 找到后停止尝试其他模式
}
}
}
// 尝试从OCR认识内容详细日志提取备选方案适配日文编码
if (empty($results['ocr_full_text']) && preg_match('/OCR隱崎ュ伜・螳ケ隧ウ邏ー.*"raw_text":"OCR_TEXT_START>([^<]*)<OCR_TEXT_END"/', $logContent, $matches)) {
$results['ocr_full_text'] = $matches[1];
}
// 提取氏名匹配详细信息(适配日文编码)
$nameMatchDetails = [];
if (preg_match_all('/豌丞錐繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_name":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_name":"([^"]*)"/', $logContent, $nameMatches, PREG_SET_ORDER)) {
foreach ($nameMatches as $match) {
$nameMatchDetails[] = [
'target' => $match[1],
'score' => round(floatval($match[2]), 2),
'found_in_ocr' => $match[3]
];
}
}
$results['name_match_details'] = $nameMatchDetails;
// 提取住所匹配详细信息(适配日文编码)
$addressMatchDetails = [];
if (preg_match_all('/菴乗園繝槭ャ繝√Φ繧ー隧ウ邏ー.*"target_address":"([^"]*)".*"address_type":"([^"]*)".*"similarity_score":([^,}]*).*"ocr_contains_address":"([^"]*)"/', $logContent, $addressMatches, PREG_SET_ORDER)) {
foreach ($addressMatches as $match) {
$addressMatchDetails[] = [
'target' => $match[1],
'type' => $match[2],
'score' => round(floatval($match[3]), 2),
'found_in_ocr' => $match[4]
];
}
}
$results['address_match_details'] = $addressMatchDetails;
// 从新的SHJ-1日志格式中提取信息
// 提取OCR抽出结果
if (preg_match('/SHJ-1 OCR抽出成功.*"extracted_name":"([^"]*)".*"extracted_address":"([^"]*)".*"ocr_value":"([^"]*)"/', $logContent, $matches)) {
$results['extracted_name'] = $matches[1];
$results['extracted_address'] = $matches[2];
$results['extracted_ocr_value'] = $matches[3];
}
// 提取居住住所照合结果
if (preg_match('/SHJ-1 居住住所照合.*"resident_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) {
$results['resident_address'] = $matches[1];
$results['resident_similarity'] = round(floatval($matches[2]), 2);
}
// 提取関連住所照合结果
if (preg_match('/SHJ-1 関連住所照合.*"related_address":"([^"]*)".*"similarity":([^,}]*)/', $logContent, $matches)) {
$results['related_address'] = $matches[1];
$results['related_similarity'] = round(floatval($matches[2]), 2);
}
// 提取最終OCR結果
if (preg_match('/SHJ-1 OCR照合(成功|失敗)/', $logContent, $matches)) {
$results['final_ocr_result'] = $matches[1];
$results['address_passed'] = ($matches[1] === '成功');
$results['name_passed'] = ($matches[1] === '成功'); // 新SHJ-1逻辑中成功表示整体成功
}
// 如果OCR文本为空或太短提供说明
if (empty($results['ocr_text_preview']) || strlen($results['ocr_text_preview']) < 5) {
$results['ocr_text_preview'] = '(OCR認識内容が短いか、表示できない文字が含まれています)';
}
// 表面画像処理完了の詳細結果を抽取
if (preg_match('/SHJ-1 表面画像処理完了.*"front_result":\{"name_matches":\[([^\]]*)\],"address_matches":\[([^\]]*)\]\}/', $logContent, $matches)) {
$nameMatches = explode(',', $matches[1]);
$addressMatches = explode(',', $matches[2]);
$results['name_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $nameMatches);
$results['address_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $addressMatches);
}
// OCR類似度計算結果の詳細情報を抽取
if (preg_match('/SHJ-1 OCR類似度計算結果.*"best_name_match":([^,}]*).*"best_address_match":([^,}]*).*"all_name_matches":\[([^\]]*)\].*"all_address_matches":\[([^\]]*)\]/', $logContent, $matches)) {
$results['best_name_match'] = round(floatval($matches[1]), 2);
$results['best_address_match'] = round(floatval($matches[2]), 2);
$allNameMatches = explode(',', $matches[3]);
$allAddressMatches = explode(',', $matches[4]);
$results['name_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $allNameMatches);
$results['address_match_attempts'] = array_map(function($val) {
return round(floatval($val), 2);
}, $allAddressMatches);
}
// OCR閾値チェックログから類似度情報を抽出新しい順序匹配対応
if (preg_match('/SHJ-1 OCR閾値チェック.*"threshold":"?([^",}]*)"?.*"name_match":([^,}]*).*"address_match":([^,}]*).*"name_passed":([^,}]*).*"address_passed":([^,}]*).*"matched_address_type":"?([^",}]*)"?/', $logContent, $matches)) {
$results['ocr_threshold'] = $matches[1];
$results['name_similarity'] = round(floatval($matches[2]), 2);
$results['address_similarity'] = round(floatval($matches[3]), 2);
$results['name_passed'] = $matches[4] === 'true';
$results['address_passed'] = $matches[5] === 'true';
$results['matched_address_type'] = $matches[6] ?: null;
}
// Google Maps API エラーチェック
if (strpos($logContent, 'Google Maps distance calculation error') !== false) {
$results['maps_api_status'] = 'エラー';
if (preg_match('/Google Maps distance calculation error.*"error":"([^"]*)"/', $logContent, $matches)) {
$results['maps_api_error'] = $matches[1];
}
} else if (strpos($logContent, 'Distance calculation failed') !== false) {
$results['maps_api_status'] = 'アドレス未発見';
$results['maps_api_error'] = 'NOT_FOUND';
}
// 距離計算結果を抽取(最新の詳細ログから)
if (preg_match_all('/SHJ-1 距離計算完了.*"calculated_distance_meters":(\d+).*"distance_text":"([^"]*)".*"limit_meters":"?([^",}]*)"?.*"within_limit":([^,}]*)/', $logContent, $allMatches, PREG_SET_ORDER)) {
// 最後の(最新の)マッチを使用
$matches = end($allMatches);
$results['calculated_distance'] = $matches[1]; // 距離メートル
$results['distance_text'] = $matches[2]; // Google Mapsテキスト
$results['distance_limit'] = $matches[3]; // 制限値
$results['distance_passed'] = $matches[4] === 'true';
} else {
// ログから具体的な結果が取得できない場合はデフォルト値を設定
// ※重要API成功≠距離制限内ではないため、明示的にfalseにする
$results['distance_passed'] = false;
if (strpos($logContent, 'Distance check error') === false &&
strpos($logContent, 'Google Maps distance calculation error') === false) {
$results['calculated_distance'] = '計算成功(制限値詳細はログで確認)';
$results['maps_api_status'] = '成功(但制限確認要)';
} else {
$results['calculated_distance'] = '計算失敗';
$results['maps_api_status'] = 'エラー';
}
}
// 距離計算開始ログから起点・終点住所を抽取
if (preg_match('/SHJ-1 距離計算開始.*"user_address":"([^"]*)".*"park_address":"([^"]*)"/', $logContent, $matches)) {
$results['distance_start_address'] = $matches[1];
$results['distance_end_address'] = $matches[2];
}
} catch (Exception $e) {
\Log::error('API結果解析エラー: ' . $e->getMessage());
}
return $results;
}
// 不再需要的分析方法已移除 - 只显示SHJ-1的结果
/**
* 废弃的方法(已不再使用)
*/
private function manualOcrAnalysis($logContent, $user = null)
{
$analysis = [
'found_base64' => false,
'decoded_success' => false,
'decoded_text' => '',
'contains_yamada' => false,
'contains_taro' => false,
'contains_tokyo' => false,
'contains_osaka' => false,
'full_analysis' => 'Analysis failed',
'corrected_matching' => null
];
try {
// 分割日志内容为行数组按时间倒序搜索最新的OCR结果
$logLines = explode("\n", $logContent);
$logLines = array_reverse($logLines); // 从最新的开始搜索
// 查找最新的Base64编码OCR结果
$patterns = [
'/SHJ-1 完全OCR認識結果.*"ocr_full_text":"([^"]*)"/',
'/SHJ-1.*OCR.*結果.*"ocr_full_text":"([^"]*)"/',
'/"ocr_full_text":"([^"]*)"/'
];
foreach ($logLines as $line) {
foreach ($patterns as $pattern) {
if (preg_match($pattern, $line, $matches)) {
$analysis['found_base64'] = true;
$base64Text = $matches[1];
// Base64解码
$decodedText = base64_decode($base64Text);
if ($decodedText !== false && strlen($decodedText) > 10) {
$analysis['decoded_success'] = true;
$analysis['decoded_text'] = $decodedText;
// 内容分析
$analysis['contains_yamada'] = strpos($decodedText, '山田') !== false;
$analysis['contains_taro'] = strpos($decodedText, '太郎') !== false;
$analysis['contains_tokyo'] = strpos($decodedText, '東京') !== false;
$analysis['contains_osaka'] = strpos($decodedText, '大阪') !== false;
// ユーザー提案:空白と改行を除去して比較
$analysis['corrected_matching'] = $this->performCorrectedMatching($decodedText, $user);
// 詳細分析(実際のユーザーデータから期待値を取得)
if ($user) {
$expectedName = $user->user_name ?? "";
$expectedAddress = ($user->user_regident_pre ?? '') .
($user->user_regident_city ?? '') .
($user->user_regident_add ?? '') ?: "";
} else {
$expectedName = "";
$expectedAddress = "";
}
$analysis['full_analysis'] =
"OCR認識テキスト長: " . strlen($decodedText) . "文字\n" .
"期待氏名: '$expectedName'\n" .
"期待住所: '$expectedAddress'\n" .
"山田を含む: " . ($analysis['contains_yamada'] ? 'YES' : 'NO') . "\n" .
"太郎を含む: " . ($analysis['contains_taro'] ? 'YES' : 'NO') . "\n" .
"東京を含む: " . ($analysis['contains_tokyo'] ? 'YES' : 'NO') . "\n" .
"大阪を含む: " . ($analysis['contains_osaka'] ? 'YES' : 'NO') . "\n" .
"認識完全内容: " . substr($decodedText, 0, 200) . "...";
// 成功解码最新OCR结果后立即返回
return $analysis;
}
// 解码失败时,继续搜索其他条目
}
}
}
} catch (Exception $e) {
$analysis['full_analysis'] = 'OCR分析エラー: ' . $e->getMessage();
}
return $analysis;
}
/**
* 修正マッチングアルゴリズム実行(空白・改行除去)
*/
private function performCorrectedMatching($ocrText, $user = null)
{
// ユーザー情報から期待値を取得、デフォルトはテスト用
if ($user) {
$expectedName = $user->user_name ?? "";
$expectedAddress = ($user->user_regident_pre ?? '') .
($user->user_regident_city ?? '') .
($user->user_regident_add ?? '') ?: "";
} else {
$expectedName = "";
$expectedAddress = "";
}
// 統一的テキスト正規化関数
$normalize = function($text) {
// 全空白文字と改行を除去(全角スペース含む)
$text = preg_replace('/[\s\x{3000}]+/u', '', $text); // \x{3000}は全角スペース
// 一般的な空白文字を明示的に除去
$text = str_replace([' ', ' ', "\t", "\n", "\r"], '', $text);
// 全角→半角変換
$text = mb_convert_kana($text, 'rnask', 'UTF-8');
// 住所統一
$text = str_replace(['東京市', '東京府'], '東京都', $text);
$text = str_replace(['の'], '', $text);
// 数字統一
$text = str_replace(['','','','','','','','','',''],
['1','2','3','4','5','6','7','8','9','0'], $text);
return $text;
};
// 正規化処理
$normalizedOcr = $normalize($ocrText);
$normalizedExpectedName = $normalize($expectedName);
$normalizedExpectedAddr = $normalize($expectedAddress);
// 使用"住所"分割OCR文本
$addressKeyword = '住所';
$ocrParts = explode($addressKeyword, $normalizedOcr, 2);
$personalInfoSection = $ocrParts[0] ?? ''; // "住所"前の個人情報欄
$addressSection = isset($ocrParts[1]) ? $addressKeyword . $ocrParts[1] : ''; // "住所"後の住所欄
// 分区マッチング計算
$nameMatch = $this->calculateSimpleMatch($normalizedExpectedName, $personalInfoSection);
$addrMatch = $this->calculateSimpleMatch($normalizedExpectedAddr, $addressSection);
return [
'original_ocr' => substr($ocrText, 0, 100) . '...',
'normalized_ocr' => substr($normalizedOcr, 0, 100) . '...',
'normalized_expected_name' => $normalizedExpectedName,
'normalized_expected_addr' => $normalizedExpectedAddr,
'personal_info_section' => substr($personalInfoSection, 0, 80) . '...',
'address_section' => substr($addressSection, 0, 80) . '...',
'name_match_score' => $nameMatch,
'addr_match_score' => $addrMatch,
'name_passed' => $nameMatch >= 70,
'addr_passed' => $addrMatch >= 70,
'overall_result' => ($nameMatch >= 70 && $addrMatch >= 70) ? 'PASS' : 'FAIL'
];
}
/**
* シンプルマッチング計算
*/
private function calculateSimpleMatch($expected, $haystack)
{
if (empty($expected)) return 0;
// 1. 完全包含チェック
if (strpos($haystack, $expected) !== false) {
return 100;
}
// 2. 文字包含率
$expectedChars = mb_str_split($expected, 1, 'UTF-8');
$foundChars = 0;
foreach ($expectedChars as $char) {
if (mb_strpos($haystack, $char, 0, 'UTF-8') !== false) {
$foundChars++;
}
}
$charRate = ($foundChars / count($expectedChars)) * 100;
// 3. 類似度計算
similar_text($expected, $haystack, $similarRate);
// 最高スコアを返す
return max($charRate, $similarRate);
}
}