|
|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
|
|
namespace Tests\Feature;
|
|
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Mobile\CourseController;
|
|
|
|
|
|
use App\Models\CourseSign;
|
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
|
use App\Models\Admin;
|
|
|
|
|
|
use App\Services\Course\CompanyRegistration;
|
|
|
|
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
|
|
|
|
use Illuminate\Support\Facades\Auth;
|
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
|
|
use Tests\TestCase;
|
|
|
|
|
|
|
|
|
|
|
|
class CompanyRegistrationTest extends TestCase
|
|
|
|
|
|
{
|
|
|
|
|
|
protected function setUp(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
parent::setUp();
|
|
|
|
|
|
config(['database.default' => 'sqlite', 'database.connections.sqlite.database' => ':memory:', 'audit.enabled' => false]);
|
|
|
|
|
|
DB::purge('sqlite'); $this->withoutMiddleware();
|
|
|
|
|
|
Schema::create('courses', function (Blueprint $t) {
|
|
|
|
|
|
$t->id(); $t->string('name')->default('课程'); $t->integer('status')->default(1); $t->integer('is_fee')->default(0); $t->decimal('price')->default(0);
|
|
|
|
|
|
$t->date('sign_start_date')->nullable(); $t->date('sign_end_date')->nullable(); $t->softDeletes(); $t->timestamps();
|
|
|
|
|
|
});
|
|
|
|
|
|
Schema::create('users', function (Blueprint $t) { $t->id(); $t->string('name'); $t->string('company_name'); $t->string('mobile'); $t->softDeletes(); $t->timestamps(); });
|
|
|
|
|
|
Schema::create('course_signs', function (Blueprint $t) {
|
|
|
|
|
|
$t->id(); $t->integer('course_id'); $t->integer('user_id'); $t->integer('status')->default(0); $t->integer('fee_status')->default(0);
|
|
|
|
|
|
$t->json('data')->nullable(); $t->json('change_data')->nullable(); $t->integer('is_change')->default(0); $t->json('fee_file_ids')->nullable();
|
|
|
|
|
|
$t->string('no')->nullable(); $t->decimal('money')->default(0); $t->string('title')->nullable(); $t->text('giveup_reason')->nullable();
|
|
|
|
|
|
$t->softDeletes(); $t->timestamps();
|
|
|
|
|
|
});
|
|
|
|
|
|
(require database_path('migrations/2024_07_29_100821_create_course_forms_table.php'))->up();
|
|
|
|
|
|
(require database_path('migrations/2026_10_30_000001_extend_course_forms_and_sign_snapshots.php'))->up();
|
|
|
|
|
|
(require database_path('migrations/2026_10_30_000002_add_course_company_registration_limit.php'))->up();
|
|
|
|
|
|
Schema::create('third_appointment_logs', function (Blueprint $t) { $t->id(); $t->integer('course_sign_id'); $t->integer('plate_status')->default(0); $t->integer('finally')->default(0); $t->string('door_name')->nullable(); $t->timestamp('end_time')->nullable(); $t->softDeletes(); });
|
|
|
|
|
|
Schema::create('course_appointment_totals', function (Blueprint $t) { $t->id(); $t->integer('user_id'); $t->integer('course_sign_id'); $t->softDeletes(); $t->timestamps(); });
|
|
|
|
|
|
DB::table('courses')->insert([['id' => 10, 'one_person_per_company' => 1], ['id' => 11, 'one_person_per_company' => 0]]);
|
|
|
|
|
|
foreach ([1 => '张三', 2 => '李四', 3 => '王五'] as $id => $name) DB::table('users')->insert(['id' => $id, 'name' => $name, 'company_name' => 'A公司', 'mobile' => '1380000000'.$id]);
|
|
|
|
|
|
foreach ([10,11] as $id) DB::table('course_forms')->insert($this->field() + ['course_id' => $id]);
|
|
|
|
|
|
Auth::guard('mobile')->setUser(User::find(2));
|
|
|
|
|
|
$admin = new Admin(); $admin->forceFill(['id' => 1, 'department_id' => 1]); Auth::guard('admin')->setUser($admin);
|
|
|
|
|
|
// No real SMS during integration tests; count only committed submissions.
|
|
|
|
|
|
$controller = \Mockery::mock(CourseController::class)->makePartial()->shouldAllowMockingProtectedMethods();
|
|
|
|
|
|
$controller->shouldReceive('notifySignSubmitted')->zeroOrMoreTimes()->andReturnNull();
|
|
|
|
|
|
$this->app->instance(CourseController::class, $controller);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected function tearDown(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
Auth::guard('mobile')->forgetUser(); Auth::guard('admin')->forgetUser(); DB::disconnect('sqlite'); parent::tearDown();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function field(): array { return ['field' => 'company_name', 'name' => '公司', 'edit_input' => 'text', 'rule' => 'required', 'belong_user' => 1, 'belong_user_table' => 'company_name']; }
|
|
|
|
|
|
private function attributes(int $user = 2, int $course = 10, string $company = 'A公司'): array
|
|
|
|
|
|
{
|
|
|
|
|
|
return ['course_id' => $course, 'user_id' => $user, 'status' => 0, 'data' => [$this->field() + ['value' => $company]], 'form_snapshot' => ['version' => 2, 'fields' => [$this->field()]]];
|
|
|
|
|
|
}
|
|
|
|
|
|
private function old(int $status = 0): CourseSign { $sign = new CourseSign($this->attributes(1)); $sign->status = $status; $sign->save(); return $sign; }
|
|
|
|
|
|
|
|
|
|
|
|
public function testFullHttpConfirmationCancelThenReplaceAndNoDuplicateSubmission(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old(1);
|
|
|
|
|
|
DB::table('course_appointment_totals')->insert(['user_id' => 1, 'course_sign_id' => $old->id]);
|
|
|
|
|
|
DB::table('course_appointment_totals')->insert(['user_id' => 1, 'course_sign_id' => 999]);
|
|
|
|
|
|
$data = ['course_id' => 10, 'data' => [['field' => 'company_name', 'value' => 'A公司']]];
|
|
|
|
|
|
$url = '/api/mobile/course/sign';
|
|
|
|
|
|
$prompt = $this->postJson($url, $data)->assertOk()->assertJsonPath('replacement_required', true)->assertJsonPath('replacement_name', '张三')->json();
|
|
|
|
|
|
// Cancelling means no confirmation request. The first request is entirely read-only.
|
|
|
|
|
|
$this->assertSame(1, CourseSign::count()); $this->assertSame(1, (int) $old->fresh()->status);
|
|
|
|
|
|
$new = $this->postJson($url, $data + ['replacement_token' => $prompt['replacement_token']])->assertOk()->assertJsonPath('status', 0)->json();
|
|
|
|
|
|
$this->assertSame($old->id, $new['replaces_sign_id']);
|
|
|
|
|
|
$this->assertSame(5, (int) $old->fresh()->status);
|
|
|
|
|
|
$this->assertSame($new['id'], (int) $old->fresh()->replaced_by_sign_id);
|
|
|
|
|
|
$this->assertStringContainsString('李四', $old->fresh()->giveup_reason);
|
|
|
|
|
|
$this->assertSame($this->attributes(1)['data'], $old->fresh()->data);
|
|
|
|
|
|
$this->assertNotNull(DB::table('course_appointment_totals')->where('course_sign_id', $old->id)->value('deleted_at'));
|
|
|
|
|
|
$this->assertNull(DB::table('course_appointment_totals')->where('course_sign_id', 999)->value('deleted_at'));
|
|
|
|
|
|
$this->postJson($url, $data + ['replacement_token' => $prompt['replacement_token']])->assertJsonPath('errmsg', '请勿重复报名');
|
|
|
|
|
|
$this->assertSame(2, CourseSign::count());
|
|
|
|
|
|
Auth::guard('mobile')->setUser(User::find(1));
|
|
|
|
|
|
$this->postJson('/api/mobile/course/update-sign', ['id' => $old->id, 'data' => [['field' => 'company_name', 'value' => 'B公司']]])
|
|
|
|
|
|
->assertJsonPath('errmsg', '该报名已被更换,原报名信息只读,请重新报名');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testChangedOccupantRequiresFreshConfirmationAndTokenCannotTransferUsers(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old(); $prompt = CompanyRegistration::submit($this->attributes());
|
|
|
|
|
|
$old->status = 3; $old->save();
|
|
|
|
|
|
$newPrompt = CompanyRegistration::submit($this->attributes(), $prompt['replacement_token']);
|
|
|
|
|
|
$this->assertTrue($newPrompt['replacement_required']);
|
|
|
|
|
|
$this->assertSame(3, (int) $old->fresh()->status);
|
|
|
|
|
|
$otherUser = CompanyRegistration::submit($this->attributes(3), $newPrompt['replacement_token']);
|
|
|
|
|
|
$this->assertTrue($otherUser['replacement_required']);
|
|
|
|
|
|
$otherData = CompanyRegistration::submit($this->attributes() + ['extra' => 'unused'], 'tampered-token');
|
|
|
|
|
|
$this->assertTrue($otherData['replacement_required']);
|
|
|
|
|
|
$this->assertSame(1, CourseSign::count());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testPaidProofAndExternalAccessBlockReplacement(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old();
|
|
|
|
|
|
foreach ([['fee_status' => 1, 'fee_file_ids' => null], ['fee_status' => 0, 'fee_file_ids' => [9]]] as $update) {
|
|
|
|
|
|
$old->fill($update)->save();
|
|
|
|
|
|
try { CompanyRegistration::submit($this->attributes()); $this->fail('paid replacement accepted'); }
|
|
|
|
|
|
catch (\InvalidArgumentException $e) { $this->assertStringContainsString('缴费', $e->getMessage()); }
|
|
|
|
|
|
}
|
|
|
|
|
|
$old->fee_file_ids = null; $old->save();
|
|
|
|
|
|
DB::table('third_appointment_logs')->insert(['course_sign_id' => $old->id, 'finally' => 1, 'door_name' => '门1', 'end_time' => now()->addDay()]);
|
|
|
|
|
|
try { CompanyRegistration::submit($this->attributes()); $this->fail('permission replacement accepted'); }
|
|
|
|
|
|
catch (\InvalidArgumentException $e) { $this->assertStringContainsString('门禁或停车', $e->getMessage()); }
|
|
|
|
|
|
$this->assertSame(0, (int) $old->fresh()->status);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testReplacedUserCanReapplyAsNewRecordAndDelayedGrantsAreSkipped(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old(1); $prompt = CompanyRegistration::submit($this->attributes());
|
|
|
|
|
|
$new = CompanyRegistration::submit($this->attributes(), $prompt['replacement_token'])['sign'];
|
|
|
|
|
|
$called = false; CompanyRegistration::grantAccess($old->id, function () use (&$called) { $called = true; });
|
|
|
|
|
|
$this->assertFalse($called);
|
|
|
|
|
|
$returnPrompt = CompanyRegistration::submit($this->attributes(1));
|
|
|
|
|
|
$reapply = CompanyRegistration::submit($this->attributes(1), $returnPrompt['replacement_token'])['sign'];
|
|
|
|
|
|
$this->assertNotSame($old->id, $reapply->id);
|
|
|
|
|
|
$this->assertSame(5, (int) $new->fresh()->status);
|
|
|
|
|
|
$this->assertSame(3, CourseSign::count());
|
|
|
|
|
|
$old->refresh(); $old->status = 1;
|
|
|
|
|
|
$this->expectException(\InvalidArgumentException::class); $old->save();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testSharedGuardRejectsAdminCreationReactivationAndCompanyChange(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->old();
|
|
|
|
|
|
$this->postJson('/api/admin/course-signs/batch-update-status', ['course_id' => 10, 'ids' => '', 'status' => 1])->assertJsonStructure(['errcode']);
|
|
|
|
|
|
foreach (['create', 'reactivate', 'change'] as $kind) {
|
|
|
|
|
|
$attributes = $this->attributes(2, 10, $kind === 'change' ? 'B公司' : 'A公司');
|
|
|
|
|
|
$candidate = new CourseSign($attributes);
|
|
|
|
|
|
if ($kind === 'reactivate') $candidate->status = 5;
|
|
|
|
|
|
if ($kind !== 'create') $candidate->save();
|
|
|
|
|
|
if ($kind === 'reactivate') $candidate->status = 1;
|
|
|
|
|
|
if ($kind === 'change') $candidate->data = $this->attributes()['data'];
|
|
|
|
|
|
try { $candidate->save(); $this->fail('company conflict accepted'); }
|
|
|
|
|
|
catch (\InvalidArgumentException $e) { $this->assertStringContainsString('有效报名', $e->getMessage()); }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testUnrestrictedOtherCompanyReleasedStatusesAndCompanySnapshotCompatibility(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old();
|
|
|
|
|
|
$unrestricted = CompanyRegistration::submit($this->attributes(1, 11))['sign'];
|
|
|
|
|
|
$this->assertSame(11, (int) $unrestricted->course_id);
|
|
|
|
|
|
$this->assertArrayHasKey('sign', CompanyRegistration::submit($this->attributes(2, 11)));
|
|
|
|
|
|
$this->assertArrayHasKey('sign', CompanyRegistration::submit($this->attributes(3, 10, 'B公司')));
|
|
|
|
|
|
DB::table('users')->where('id', 1)->update(['company_name' => 'C公司']);
|
|
|
|
|
|
$this->assertTrue(CompanyRegistration::submit($this->attributes())['replacement_required']);
|
|
|
|
|
|
$old->status = 2; $old->save();
|
|
|
|
|
|
$this->assertArrayHasKey('sign', CompanyRegistration::submit($this->attributes()));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testFailedNewRegistrationRollsBackWithdrawalAndExpiredTokenNeedsConfirmation(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$old = $this->old(); $prompt = CompanyRegistration::submit($this->attributes());
|
|
|
|
|
|
$payload = json_decode(\Illuminate\Support\Facades\Crypt::decryptString($prompt['replacement_token']), true);
|
|
|
|
|
|
$payload['expires'] = time() - 1;
|
|
|
|
|
|
$expired = \Illuminate\Support\Facades\Crypt::encryptString(json_encode($payload));
|
|
|
|
|
|
$this->assertTrue(CompanyRegistration::submit($this->attributes(), $expired)['replacement_required']);
|
|
|
|
|
|
$failInsert = true;
|
|
|
|
|
|
CourseSign::saving(function ($sign) use (&$failInsert) { if ($failInsert && !$sign->exists && $sign->replaces_sign_id) throw new \RuntimeException('test insert failure'); });
|
|
|
|
|
|
try { CompanyRegistration::submit($this->attributes(), $prompt['replacement_token']); $this->fail('insert failure was ignored'); }
|
|
|
|
|
|
catch (\RuntimeException $e) { $this->assertSame('test insert failure', $e->getMessage()); }
|
|
|
|
|
|
finally { $failInsert = false; }
|
|
|
|
|
|
$this->assertSame(0, (int) $old->fresh()->status);
|
|
|
|
|
|
$this->assertNull($old->fresh()->replaced_at);
|
|
|
|
|
|
$this->assertSame(1, CourseSign::count());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testAdminApiCannotBypassLimitAndCompanyFieldCanBeOptional(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->old();
|
|
|
|
|
|
$response = $this->postJson('/api/admin/course-signs/save', $this->attributes());
|
|
|
|
|
|
$this->assertStringContainsString('有效报名', $response->json('errmsg'));
|
|
|
|
|
|
$this->assertSame(1, CourseSign::count());
|
|
|
|
|
|
$inactive = new CourseSign($this->attributes()); $inactive->status = 5; $inactive->save();
|
|
|
|
|
|
$response = $this->postJson('/api/admin/course-signs/batch-update-status', ['course_id' => 10, 'ids' => (string) $inactive->id, 'status' => 1]);
|
|
|
|
|
|
$this->assertStringContainsString('有效报名', $response->json('errmsg'));
|
|
|
|
|
|
$this->assertSame(5, (int) $inactive->fresh()->status);
|
|
|
|
|
|
$fieldId = DB::table('course_forms')->where('course_id', 10)->value('id');
|
|
|
|
|
|
$this->postJson('/api/admin/course-forms/save', ['id' => $fieldId, 'rule' => ''])->assertOk()->assertJsonPath('rule', '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testLegacyCompanyAndBlacklistedUserAreChecked(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// Historical rows have no company snapshot; their saved answers still occupy the company slot.
|
|
|
|
|
|
DB::table('course_signs')->insert(['course_id' => 10, 'user_id' => 1, 'status' => 0, 'data' => json_encode([['field' => 'company_name', 'value' => ' A 公司 ']])]);
|
|
|
|
|
|
$this->assertTrue(CompanyRegistration::submit($this->attributes())['replacement_required']);
|
|
|
|
|
|
DB::table('course_signs')->insert(['course_id' => 10, 'user_id' => 2, 'status' => 6]);
|
|
|
|
|
|
$this->expectException(\InvalidArgumentException::class);
|
|
|
|
|
|
CompanyRegistration::submit($this->attributes());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function useNameOnlyForm(): array
|
|
|
|
|
|
{
|
|
|
|
|
|
DB::table('course_forms')->where('course_id', 10)->delete();
|
|
|
|
|
|
$field = ['field' => 'name', 'name' => '姓名', 'edit_input' => 'text', 'rule' => 'required'];
|
|
|
|
|
|
DB::table('course_forms')->insert($field + ['course_id' => 10]);
|
|
|
|
|
|
return $field;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testNoCompanyFieldUsesProfileForConfirmationReplacementAndFrozenSnapshot(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$field = $this->useNameOnlyForm();
|
|
|
|
|
|
$old = CourseSign::create(['course_id' => 10, 'user_id' => 1, 'status' => 0, 'data' => [$field + ['value' => '张三']], 'form_snapshot' => ['fields' => [$field]]]);
|
|
|
|
|
|
$this->assertSame('A公司', $old->company_name_snapshot);
|
|
|
|
|
|
$payload = ['course_id' => 10, 'data' => [['field' => 'name', 'value' => '李四']]];
|
|
|
|
|
|
$url = '/api/mobile/course/sign';
|
|
|
|
|
|
$prompt = $this->postJson($url, $payload)->assertOk()->assertJsonPath('replacement_required', true)->json();
|
|
|
|
|
|
$new = $this->postJson($url, $payload + ['replacement_token' => $prompt['replacement_token']])->assertOk()->assertJsonPath('company_name_snapshot', 'A公司')->assertJsonPath('status', 0)->json();
|
|
|
|
|
|
$this->assertSame(5, (int) $old->fresh()->status);
|
|
|
|
|
|
DB::table('users')->where('id', 2)->update(['company_name' => 'B公司']);
|
|
|
|
|
|
$sign = CourseSign::findOrFail($new['id']); $originalKey = $sign->company_key;
|
|
|
|
|
|
$answers = $sign->data; $answers[0]['value'] = '李四更新姓名'; $sign->data = $answers; $sign->save();
|
|
|
|
|
|
$this->assertSame('A公司', $sign->fresh()->company_name_snapshot);
|
|
|
|
|
|
$this->assertSame($originalKey, $sign->fresh()->company_key);
|
|
|
|
|
|
$response = $this->postJson('/api/admin/course-signs/save', ['course_id' => 10, 'user_id' => 3, 'status' => 0, 'data' => [['field' => 'name', 'value' => '王五']]]);
|
|
|
|
|
|
$this->assertStringContainsString('有效报名', $response->json('errmsg'));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testSubmittedLinkedCompanyOverridesDifferentProfileCompany(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$this->old();
|
|
|
|
|
|
DB::table('course_forms')->where('course_id', 10)->update(['field' => 'employer']);
|
|
|
|
|
|
$payload = ['course_id' => 10, 'data' => [['field' => 'employer', 'value' => 'B公司']]];
|
|
|
|
|
|
$this->postJson('/api/mobile/course/sign', $payload)->assertOk()->assertJsonPath('company_name_snapshot', 'B公司')->assertJsonPath('status', 0);
|
|
|
|
|
|
// User 3 belongs to A in their profile, but their submitted B company occupies B's slot.
|
|
|
|
|
|
Auth::guard('mobile')->setUser(User::find(3));
|
|
|
|
|
|
$this->postJson('/api/mobile/course/sign', $payload)->assertOk()->assertJsonPath('replacement_required', true)->assertJsonPath('replacement_name', '李四');
|
|
|
|
|
|
$this->assertSame(2, CourseSign::count());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testOptionalEmptyCompanyFallsBackToProfileAndMissingBothIsRejected(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$field = $this->field(); $field['rule'] = '';
|
|
|
|
|
|
$attributes = $this->attributes(1); $attributes['data'] = [$field + ['value' => '']]; $attributes['form_snapshot'] = ['fields' => [$field]];
|
|
|
|
|
|
$sign = CompanyRegistration::submit($attributes)['sign'];
|
|
|
|
|
|
$this->assertSame('A公司', $sign->company_name_snapshot);
|
|
|
|
|
|
$this->useNameOnlyForm();
|
|
|
|
|
|
DB::table('users')->where('id', 2)->update(['company_name' => ' ']);
|
|
|
|
|
|
$this->postJson('/api/mobile/course/sign', ['course_id' => 10, 'data' => [['field' => 'name', 'value' => '李四']]])
|
|
|
|
|
|
->assertJsonPath('errmsg', '请先完善个人信息中的公司名称,或在报名表中填写公司名称');
|
|
|
|
|
|
$this->assertSame(1, CourseSign::count());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testCompanyFieldCanBeRemovedFromPublishedCourseWithoutRegistrations(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
// The audit helper uses MySQL column comments; omit that unrelated side effect in SQLite.
|
|
|
|
|
|
$this->app->instance(\App\Http\Controllers\Admin\CourseFormsController::class,
|
|
|
|
|
|
new class extends \App\Http\Controllers\Admin\CourseFormsController {
|
|
|
|
|
|
protected function saveLogs($original, $model) {}
|
|
|
|
|
|
});
|
|
|
|
|
|
$fieldId = DB::table('course_forms')->where('course_id', 10)->value('id');
|
|
|
|
|
|
$this->getJson('/api/admin/course-forms/destroy?id='.$fieldId)->assertOk();
|
|
|
|
|
|
$this->assertNull(DB::table('course_forms')->where('id', $fieldId)->whereNull('deleted_at')->first());
|
|
|
|
|
|
$attributes = $this->attributes(); $attributes['data'] = []; $attributes['form_snapshot'] = ['fields' => []];
|
|
|
|
|
|
$this->assertSame('A公司', CompanyRegistration::submit($attributes)['sign']->company_name_snapshot);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testReplacementNamesUseSubmittedNamesInListDetailsReasonsAndExport(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
$nameField = ['field' => 'participant', 'name' => '姓名', 'edit_input' => 'text', 'belong_user_table' => 'username'];
|
|
|
|
|
|
$oldAttributes = $this->attributes(1);
|
|
|
|
|
|
$oldAttributes['data'][] = $nameField + ['value' => '原报名填写姓名'];
|
|
|
|
|
|
$oldAttributes['form_snapshot']['fields'][] = $nameField;
|
|
|
|
|
|
$old = CourseSign::create($oldAttributes);
|
|
|
|
|
|
$newAttributes = $this->attributes(2);
|
|
|
|
|
|
$newAttributes['data'][] = $nameField + ['value' => '新报名填写姓名'];
|
|
|
|
|
|
$newAttributes['form_snapshot']['fields'][] = $nameField;
|
|
|
|
|
|
$prompt = CompanyRegistration::submit($newAttributes);
|
|
|
|
|
|
$this->assertSame('原报名填写姓名', $prompt['replacement_name']);
|
|
|
|
|
|
$new = CompanyRegistration::submit($newAttributes, $prompt['replacement_token'])['sign'];
|
|
|
|
|
|
// Existing stored descriptions can contain IDs; display resolves the linked person's name.
|
|
|
|
|
|
DB::table('course_signs')->where('id', $old->id)->update(['giveup_reason' => '旧版描述:报名ID '.$new->id]);
|
|
|
|
|
|
$old->refresh(); $new->refresh();
|
|
|
|
|
|
$this->assertSame('因同公司更换参训人员,已由新报名填写姓名替换报名。', $old->giveup_reason);
|
|
|
|
|
|
$this->assertStringContainsString('已由新报名填写姓名替换;更换时间:', $old->replacement_text);
|
|
|
|
|
|
$this->assertSame('替换原报名填写姓名的报名,需重新审核', $new->replacement_text);
|
|
|
|
|
|
$this->assertStringNotContainsString('报名ID', $old->toArray()['replacement_text']);
|
|
|
|
|
|
$export = new \App\Exports\CourseRegistrationExport([$old->toArray(), $new->toArray()], []);
|
|
|
|
|
|
$sheet = $export->array(); $column = array_search('报名更换记录', $sheet[0], true);
|
|
|
|
|
|
$this->assertSame($old->replacement_text, $sheet[1][$column]);
|
|
|
|
|
|
$this->assertSame($new->replacement_text, $sheet[2][$column]);
|
|
|
|
|
|
$new->delete();
|
|
|
|
|
|
DB::table('users')->where('id', 2)->update(['deleted_at' => now()]);
|
|
|
|
|
|
$this->assertStringContainsString('新报名填写姓名', $old->fresh()->giveup_reason);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function testHistoricalDuplicatesAreListedButNeverAutomaticallyChanged(): void
|
|
|
|
|
|
{
|
|
|
|
|
|
DB::table('courses')->where('id', 10)->update(['one_person_per_company' => 0]);
|
|
|
|
|
|
$one = $this->old(); $two = CourseSign::create($this->attributes(2));
|
|
|
|
|
|
DB::table('courses')->where('id', 10)->update(['one_person_per_company' => 1]);
|
|
|
|
|
|
$this->assertCount(1, CompanyRegistration::duplicates(10));
|
|
|
|
|
|
try { CompanyRegistration::submit($this->attributes(3)); $this->fail('historical duplicates replaced'); }
|
|
|
|
|
|
catch (\InvalidArgumentException $e) { $this->assertStringContainsString('多条历史', $e->getMessage()); }
|
|
|
|
|
|
$this->assertSame(0, (int) $one->fresh()->status); $this->assertSame(0, (int) $two->fresh()->status);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|