diff --git a/.gitignore b/.gitignore index b6ab06a66..140e87d73 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,5 @@ docker-compose/mysql/model/*.sql package.xml .env.dev rector.php -public/apc.php \ No newline at end of file +public/apc.php +.claude/ diff --git a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitPromoCodesApiController.php b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitPromoCodesApiController.php index 86ed9509d..fec6ff9c2 100644 --- a/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitPromoCodesApiController.php +++ b/app/Http/Controllers/Apis/Protected/Summit/OAuth2SummitPromoCodesApiController.php @@ -1417,7 +1417,15 @@ public function removeSpeaker($summit_id, $promo_code_id, $speaker_id) new OA\Parameter(name: "filter", in: "query", required: false, schema: new OA\Schema(type: "string")), ], responses: [ - new OA\Response(response: Response::HTTP_OK, description: "OK"), + new OA\Response( + response: Response::HTTP_OK, + description: "Pre-validation result", + content: new OA\JsonContent( + properties: [ + new OA\Property(property: 'allows_to_reassign', type: 'boolean', description: 'Whether the promo code allows ticket reassignment'), + ] + ) + ), new OA\Response(response: Response::HTTP_UNAUTHORIZED, description: "Unauthorized"), new OA\Response(response: Response::HTTP_NOT_FOUND, description: "Not found"), new OA\Response(response: Response::HTTP_PRECONDITION_FAILED, description: "Validation error"), @@ -1462,10 +1470,12 @@ function ($attribute, $value, $fail) use ($filter) { 'ticket_type_subtype' => 'required|string|in:'.join(",", SummitTicketType::SubTypes), ]); - $this->promo_code_service + $promo_code = $this->promo_code_service ->preValidatePromoCode($summit, $this->resource_server_context->getCurrentUser(), $promo_code_val, $filter); - return $this->ok(); + return $this->ok(SerializerRegistry::getInstance() + ->getSerializer($promo_code, SerializerRegistry::SerializerType_PreValidation) + ->serialize()); }); } diff --git a/app/ModelSerializers/SerializerRegistry.php b/app/ModelSerializers/SerializerRegistry.php index a63e87def..0a9c7807d 100644 --- a/app/ModelSerializers/SerializerRegistry.php +++ b/app/ModelSerializers/SerializerRegistry.php @@ -168,6 +168,7 @@ final class SerializerRegistry const SerializerType_Admin_Voteable_CSV = "ADMIN_VOTEABLE_CSV"; const SerializerType_CSV = 'CSV'; const SerializerType_Admin_Registration_Stats = 'ADMIN_REG_STATS'; + const SerializerType_PreValidation = 'PRE_VALIDATION'; private function __clone() { @@ -434,61 +435,73 @@ private function __construct() $this->registry['SummitRegistrationPromoCode'] = [ self::SerializerType_Public => SummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => SummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SummitRegistrationDiscountCode'] = [ self::SerializerType_Public => SummitRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => SummitRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['MemberSummitRegistrationPromoCode'] = [ self::SerializerType_Public => MemberSummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => MemberSummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['MemberSummitRegistrationDiscountCode'] = [ self::SerializerType_Public => MemberSummitRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => MemberSummitRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SpeakerSummitRegistrationPromoCode'] = [ self::SerializerType_Public => SpeakerSummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => SpeakerSummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SpeakerSummitRegistrationDiscountCode'] = [ self::SerializerType_Public => SpeakerSummitRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => SpeakerSummitRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SponsorSummitRegistrationPromoCode'] = [ self::SerializerType_Public => SponsorSummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => SponsorSummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SponsorSummitRegistrationDiscountCode'] = [ self::SerializerType_Public => SponsorSummitRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => SponsorSummitRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SpeakersSummitRegistrationPromoCode'] = [ self::SerializerType_Public => SpeakersSummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => SpeakersSummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['SpeakersRegistrationDiscountCode'] = [ self::SerializerType_Public => SpeakersRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => SpeakersRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['PrePaidSummitRegistrationPromoCode'] = [ self::SerializerType_Public => SummitRegistrationPromoCodeSerializer::class, self::SerializerType_CSV => SummitRegistrationPromoCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['PrePaidSummitRegistrationDiscountCode'] = [ self::SerializerType_Public => SummitRegistrationDiscountCodeSerializer::class, self::SerializerType_CSV => SummitRegistrationDiscountCodeCSVSerializer::class, + self::SerializerType_PreValidation => SummitRegistrationPromoCodePreValidationSerializer::class, ]; $this->registry['PresentationSpeakerSummitAssistanceConfirmationRequest'] = PresentationSpeakerSummitAssistanceConfirmationRequestSerializer::class; diff --git a/app/ModelSerializers/Summit/Registration/PromoCodes/SummitRegistrationPromoCodePreValidationSerializer.php b/app/ModelSerializers/Summit/Registration/PromoCodes/SummitRegistrationPromoCodePreValidationSerializer.php new file mode 100644 index 000000000..aaae06a46 --- /dev/null +++ b/app/ModelSerializers/Summit/Registration/PromoCodes/SummitRegistrationPromoCodePreValidationSerializer.php @@ -0,0 +1,24 @@ + 'allows_to_reassign:json_boolean', + ]; +} diff --git a/app/Services/Model/ISummitPromoCodeService.php b/app/Services/Model/ISummitPromoCodeService.php index f3f423444..03e3a95bc 100644 --- a/app/Services/Model/ISummitPromoCodeService.php +++ b/app/Services/Model/ISummitPromoCodeService.php @@ -148,10 +148,10 @@ public function removePromoCodeSpeaker(SummitRegistrationPromoCode $promo_code, * @param Member $owner * @param string $promo_code_value * @param Filter $filter - * @return void + * @return SummitRegistrationPromoCode * @throws \Exception */ - public function preValidatePromoCode(Summit $summit, Member $owner, string $promo_code_value, Filter $filter):void; + public function preValidatePromoCode(Summit $summit, Member $owner, string $promo_code_value, Filter $filter):SummitRegistrationPromoCode; /** * @param Summit $summit @@ -170,4 +170,4 @@ public function triggerSendSponsorPromoCodes(Summit $summit, array $payload, $fi * @throws ValidationException */ public function sendSponsorPromoCodes(int $summit_id, array $payload, Filter $filter = null): void; -} \ No newline at end of file +} diff --git a/app/Services/Model/Imp/SummitPromoCodeService.php b/app/Services/Model/Imp/SummitPromoCodeService.php index 9c6607db1..d24bcb222 100644 --- a/app/Services/Model/Imp/SummitPromoCodeService.php +++ b/app/Services/Model/Imp/SummitPromoCodeService.php @@ -897,12 +897,12 @@ public function removePromoCodeSpeaker(SummitRegistrationPromoCode $promo_code, * @param Member $owner * @param string $promo_code_value * @param Filter $filter - * @return void + * @return SummitRegistrationPromoCode * @throws \Exception */ - public function preValidatePromoCode(Summit $summit, Member $owner, string $promo_code_value, Filter $filter):void + public function preValidatePromoCode(Summit $summit, Member $owner, string $promo_code_value, Filter $filter):SummitRegistrationPromoCode { - $this->tx_service->transaction(function () use ($summit, $owner, $promo_code_value, $filter) { + return $this->tx_service->transaction(function () use ($summit, $owner, $promo_code_value, $filter) { $ticket_type_id = intval($filter->getUniqueFilter('ticket_type_id')->getValue()); @@ -921,7 +921,7 @@ public function preValidatePromoCode(Summit $summit, Member $owner, string $prom if (!$promo_code instanceof SummitRegistrationPromoCode || $promo_code->getSummitId() != $summit->getId() || !$validator->isValid($promo_code)){ throw new ValidationException(sprintf('The Promo Code "%s" is not a valid code.', $promo_code_value)); } - + return $promo_code; }); } @@ -1008,4 +1008,4 @@ function ($summit, $flow_event, $promocode_id, $test_email_recipient, $announcem $filter ); } -} \ No newline at end of file +} diff --git a/tests/OAuth2SummitPromoCodesApiTest.php b/tests/OAuth2SummitPromoCodesApiTest.php index 97b591d08..83aa18382 100644 --- a/tests/OAuth2SummitPromoCodesApiTest.php +++ b/tests/OAuth2SummitPromoCodesApiTest.php @@ -527,7 +527,41 @@ public function testPreValidatePromoCodeSuccess() $headers = ["HTTP_Authorization" => " Bearer " . $this->access_token]; - $this->action( + $response = $this->action( + "GET", + "OAuth2SummitPromoCodesApiController@preValidatePromoCode", + $params, + [], + [], + [], + $headers + ); + + $this->assertResponseStatus(200); + $content = json_decode($response->getContent(), true); + $this->assertArrayHasKey('allows_to_reassign', $content); + $this->assertTrue($content['allows_to_reassign']); + } + + public function testPreValidatePromoCodeReturnsAllowsToReassignFalse() + { + self::$default_prepaid_discount_code->setAllowsToReassign(false); + + $type_id = self::$default_ticket_type->getId(); + + $params = [ + 'id' => self::$summit->getId(), + 'promo_code_val' => self::$default_prepaid_discount_code->getCode(), + 'filter' => [ + 'ticket_type_id==' . $type_id, + 'ticket_type_qty==1', + 'ticket_type_subtype==' . SummitTicketType::Subtype_PrePaid + ] + ]; + + $headers = ["HTTP_Authorization" => " Bearer " . $this->access_token]; + + $response = $this->action( "GET", "OAuth2SummitPromoCodesApiController@preValidatePromoCode", $params, @@ -538,6 +572,9 @@ public function testPreValidatePromoCodeSuccess() ); $this->assertResponseStatus(200); + $content = json_decode($response->getContent(), true); + $this->assertArrayHasKey('allows_to_reassign', $content); + $this->assertFalse($content['allows_to_reassign']); } public function testPreValidatePromoCodeInvalid() diff --git a/tests/SerializerTests.php b/tests/SerializerTests.php index 2823ce601..63a589359 100644 --- a/tests/SerializerTests.php +++ b/tests/SerializerTests.php @@ -15,8 +15,10 @@ use models\oauth2\IResourceServerContext; use models\summit\Summit; use models\summit\SummitAttendee; +use models\summit\SummitRegistrationPromoCode; use ModelSerializers\SerializerDecorator; use ModelSerializers\SummitAttendeeAdminSerializer; +use ModelSerializers\SummitRegistrationPromoCodePreValidationSerializer; use Mockery; /** @@ -30,6 +32,43 @@ public function tearDown():void Mockery::close(); } + public function testPreValidationSerializerReturnsAllowsToReassignTrue() + { + $promo_code = Mockery::mock(SummitRegistrationPromoCode::class)->makePartial(); + $promo_code->shouldReceive('isAllowsToReassign')->andReturn(true); + $promo_code->shouldReceive('getIdentifier')->andReturn(1); + + $resource_server_context = Mockery::mock(IResourceServerContext::class); + $serializer = new SerializerDecorator( + new SummitRegistrationPromoCodePreValidationSerializer($promo_code, $resource_server_context) + ); + $values = $serializer->serialize(); + + $this->assertIsArray($values); + $this->assertArrayHasKey('allows_to_reassign', $values); + $this->assertTrue($values['allows_to_reassign']); + // should only contain allows_to_reassign and inherited id fields + $this->assertArrayNotHasKey('code', $values); + $this->assertArrayNotHasKey('redeemed', $values); + } + + public function testPreValidationSerializerReturnsAllowsToReassignFalse() + { + $promo_code = Mockery::mock(SummitRegistrationPromoCode::class)->makePartial(); + $promo_code->shouldReceive('isAllowsToReassign')->andReturn(false); + $promo_code->shouldReceive('getIdentifier')->andReturn(1); + + $resource_server_context = Mockery::mock(IResourceServerContext::class); + $serializer = new SerializerDecorator( + new SummitRegistrationPromoCodePreValidationSerializer($promo_code, $resource_server_context) + ); + $values = $serializer->serialize(); + + $this->assertIsArray($values); + $this->assertArrayHasKey('allows_to_reassign', $values); + $this->assertFalse($values['allows_to_reassign']); + } + public function testAdminSummitAttendeeSerilizer(){ $summit = Mockery::mock(Summit::class); $attendee = Mockery::mock(SummitAttendee::class)->makePartial();