-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
(PC-33346)[API] feat: add route to create and delete headline offer
- Loading branch information
1 parent
cd7f87d
commit 131cd42
Showing
13 changed files
with
486 additions
and
12 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import logging | ||
|
||
from flask_login import current_user | ||
from flask_login import login_required | ||
|
||
from pcapi.core.offers import exceptions | ||
import pcapi.core.offers.api as offers_api | ||
import pcapi.core.offers.repository as offers_repository | ||
import pcapi.core.offers.validation as offers_validation | ||
from pcapi.models import api_errors | ||
from pcapi.repository import atomic | ||
from pcapi.routes.apis import private_api | ||
from pcapi.routes.serialization import headline_offer_serialize | ||
from pcapi.serialization.decorator import spectree_serialize | ||
from pcapi.utils import rest | ||
|
||
from . import blueprint | ||
|
||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
@private_api.route("/offers/headline", methods=["POST"]) | ||
@login_required | ||
@spectree_serialize( | ||
on_success_status=204, | ||
api=blueprint.pro_private_schema, | ||
) | ||
@atomic() | ||
def make_offer_headline_from_offers(body: headline_offer_serialize.HeadlineOfferCreationBodyModel) -> None: | ||
|
||
offer = offers_repository.get_offer_by_id(body.offer_id, load_options=["headline_offer"]) | ||
|
||
if not offer: | ||
raise api_errors.ResourceNotFoundError | ||
offerer_id = offer.venue.managingOffererId | ||
|
||
rest.check_user_has_access_to_offerer(current_user, offerer_id) | ||
try: | ||
offers_validation.check_offerer_is_eligible_for_headline_offers(offerer_id) | ||
offers_validation.check_offer_is_eligible_to_be_headline(offer) | ||
except ( | ||
exceptions.OffererCanNotHaveHeadlineOffer, | ||
exceptions.VirtualOfferCanNotBeHeadline, | ||
) as error: | ||
messages = { | ||
exceptions.OffererCanNotHaveHeadlineOffer: "Vous ne pouvez pas créer d'offre à la une sur une entité juridique possédant plusieurs structures", | ||
exceptions.VirtualOfferCanNotBeHeadline: "Une offre virtuelle ne peut pas être mise à la une", | ||
} | ||
raise api_errors.ApiErrors( | ||
errors={"global": [messages[type(error)]]}, | ||
status_code=400, | ||
) | ||
|
||
try: | ||
offers_api.make_offer_headline(offer) | ||
except ( | ||
exceptions.OfferHasAlreadyAnActiveHeadlineOffer, | ||
exceptions.VenueHasAlreadyAnActiveHeadlineOffer, | ||
exceptions.InactiveOfferCanNotBeHeadline, | ||
) as error: | ||
messages = { | ||
exceptions.OfferHasAlreadyAnActiveHeadlineOffer: "Cette offre est déjà mise à la une", | ||
exceptions.VenueHasAlreadyAnActiveHeadlineOffer: "Cette structure possède déjà une offre à la une", | ||
exceptions.InactiveOfferCanNotBeHeadline: "Cette offre est inactive et ne peut pas être mise à la une", | ||
} | ||
raise api_errors.ApiErrors( | ||
errors={"global": [messages[type(error)]]}, | ||
status_code=400, | ||
) | ||
|
||
|
||
@private_api.route("/offers/delete_headline", methods=["POST"]) | ||
@login_required | ||
@spectree_serialize( | ||
on_success_status=204, | ||
api=blueprint.pro_private_schema, | ||
) | ||
@atomic() | ||
def delete_headline_offer(body: headline_offer_serialize.HeadlineOfferDeleteBodyModel) -> None: | ||
rest.check_user_has_access_to_offerer(current_user, body.offerer_id) | ||
if active_headline_offer := offers_repository.get_offerers_active_headline_offer(body.offerer_id): | ||
offers_api.remove_headline_offer(active_headline_offer) |
18 changes: 18 additions & 0 deletions
18
api/src/pcapi/routes/serialization/headline_offer_serialize.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
from pcapi.routes.serialization import BaseModel | ||
from pcapi.serialization.utils import to_camel | ||
|
||
|
||
class HeadlineOfferCreationBodyModel(BaseModel): | ||
offer_id: int | ||
|
||
class Config: | ||
alias_generator = to_camel | ||
extra = "forbid" | ||
|
||
|
||
class HeadlineOfferDeleteBodyModel(BaseModel): | ||
offerer_id: int | ||
|
||
class Config: | ||
alias_generator = to_camel | ||
extra = "forbid" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import datetime | ||
|
||
import pytest | ||
|
||
import pcapi.core.offerers.factories as offerers_factories | ||
import pcapi.core.offers.factories as offers_factories | ||
import pcapi.core.offers.models as offer_models | ||
import pcapi.core.users.factories as users_factory | ||
|
||
|
||
pytestmark = pytest.mark.usefixtures("db_session") | ||
|
||
|
||
class Returns204Test: | ||
def test_delete_headline_offer(self, client): | ||
pro = users_factory.ProFactory() | ||
offer = offers_factories.OfferFactory() | ||
offerers_factories.UserOffererFactory(user=pro, offerer=offer.venue.managingOfferer) | ||
offers_factories.StockFactory(offer=offer) | ||
headline_offer = offers_factories.HeadlineOfferFactory(offer=offer) | ||
ten_days_ago = datetime.datetime.utcnow() - datetime.timedelta(days=10) | ||
yesterday = datetime.datetime.utcnow() - datetime.timedelta(days=1) | ||
old_headline_offer = offers_factories.HeadlineOfferFactory(offer=offer, timespan=(ten_days_ago, yesterday)) | ||
|
||
data = {"offererId": offer.venue.managingOfferer.id} | ||
client = client.with_session_auth(pro.email) | ||
response = client.post("/offers/delete_headline", json=data) | ||
|
||
assert response.status_code == 204 | ||
|
||
assert not headline_offer.isActive | ||
assert headline_offer.timespan.upper.date() == datetime.datetime.utcnow().date() | ||
assert not old_headline_offer.isActive | ||
assert old_headline_offer.timespan.upper == yesterday | ||
|
||
def test_delete_only_offerers_headline_offer(self, client): | ||
pro = users_factory.ProFactory() | ||
offer = offers_factories.OfferFactory() | ||
offerer = offer.venue.managingOfferer | ||
another_offer = offers_factories.OfferFactory() | ||
another_offerer = another_offer.venue.managingOfferer | ||
offers_factories.StockFactory(offer=offer) | ||
offers_factories.StockFactory(offer=another_offer) | ||
headline_offer = offers_factories.HeadlineOfferFactory(offer=offer) | ||
another_headline_offer = offers_factories.HeadlineOfferFactory(offer=another_offer) | ||
|
||
offerers_factories.UserOffererFactory(user=pro, offerer=offerer) | ||
offerers_factories.UserOffererFactory(user=pro, offerer=another_offerer) | ||
client = client.with_session_auth(pro.email) | ||
data = {"offererId": offerer.id} | ||
response = client.post("/offers/delete_headline", json=data) | ||
|
||
assert response.status_code == 204 | ||
|
||
assert not headline_offer.isActive | ||
assert another_headline_offer.isActive | ||
|
||
|
||
class Returns401Test: | ||
def test_delete_headline_when_current_user_has_no_rights_on_offer(self, client): | ||
pro = users_factory.ProFactory() | ||
offer = offers_factories.OfferFactory() | ||
offers_factories.StockFactory(offer=offer) | ||
offerers_factories.UserOffererFactory(user=pro, offerer=offer.venue.managingOfferer) | ||
headline_offer = offers_factories.HeadlineOfferFactory(offer=offer) | ||
data = {"offererId": offer.venue.managingOfferer.id} | ||
|
||
response = client.post("/offers/delete_headline", json=data) | ||
|
||
assert response.status_code == 401 | ||
|
||
assert offer_models.HeadlineOffer.query.count() == 1 | ||
assert headline_offer.isActive |
Oops, something went wrong.