class BaseTenderComplaintResource(ComplaintBotPatchMixin, ComplaintAdminPatchMixin, APIResource): patch_check_tender_excluded_statuses = ( "draft", "claim", "answered", "pending", "accepted", "satisfied", "stopping", ) patch_check_tender_statuses = ("active.qualification", "active.awarded") @staticmethod def validate_submit_claim_time_method(request): raise NotImplementedError @staticmethod def validate_update_claim_time_method(request): raise NotImplementedError def pre_create(self): tender = self.request.validated["tender"] old_rules = get_first_revision_date(tender) < RELEASE_2020_04_19 complaint = self.request.validated["complaint"] complaint.date = get_now() if complaint.status == "claim" and complaint.type == "claim": self.validate_submit_claim_time_method(self.request) complaint.dateSubmitted = get_now() elif old_rules and complaint.status == "pending": validate_submit_complaint_time(self.request) complaint.dateSubmitted = get_now() complaint.type = "complaint" else: complaint.status = "draft" return complaint @json_view(permission="view_tender") def collection_get(self): """List complaints """ return {"data": [i.serialize("view") for i in self.context.complaints]} @json_view(permission="view_tender") def get(self): """Retrieving the complaint """ return {"data": self.context.serialize("view")} @json_view( content_type="application/json", validators=( validate_complaint_data, validate_complaint_operation_not_in_active_tendering, validate_add_complaint_with_tender_cancellation_in_pending, validate_add_complaint_with_lot_cancellation_in_pending( "complaint"), ), permission="create_complaint", ) def collection_post(self): """Post a complaint for award """ tender = self.request.validated["tender"] complaint = self.pre_create() complaint.complaintID = "{}.{}{}".format( tender.tenderID, self.server_id, calculate_total_complaints(tender) + 1) access = set_ownership(complaint, self.request) self.context.complaints.append(complaint) if save_tender(self.request): self.LOGGER.info( "Created tender award complaint {}".format(complaint.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_complaint_create"}, {"complaint_id": complaint.id}), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Complaints".format(tender.procurementMethodType), tender_id=tender.id, complaint_id=complaint.id, ) return { "data": complaint.serialize(tender.status), "access": access } @json_view( content_type="application/json", permission="edit_complaint", validators=( validate_patch_complaint_data, validate_complaint_update_with_cancellation_lot_pending, validate_complaint_operation_not_in_active_tendering, validate_update_complaint_not_in_allowed_complaint_status, validate_operation_with_lot_cancellation_in_pending("complaint"), ), ) def patch(self): role_method_name = "patch_as_{role}".format( role=self.request.authenticated_role.lower()) try: role_method = getattr(self, role_method_name) except AttributeError: raise_operation_error( self.request, "Can't update complaint as {}".format( self.request.authenticated_role)) else: role_method(self.request.validated["data"]) if self.context.tendererAction and not self.context.tendererActionDate: self.context.tendererActionDate = get_now() if (self.patch_check_tender_excluded_statuses != "__all__" and self.context.status not in self.patch_check_tender_excluded_statuses and self.request.validated["tender"].status in self.patch_check_tender_statuses): check_tender_status(self.request) if save_tender(self.request): self.LOGGER.info( "Updated tender award complaint {}".format(self.context.id), extra=context_unpack( self.request, {"MESSAGE_ID": "tender_award_complaint_patch"}), ) return {"data": self.context.serialize("view")} def patch_as_complaint_owner(self, data): context = self.context status = self.context.status new_status = data.get("status", status) tender = self.request.validated["tender"] apply_rules_2020_04_19 = get_first_revision_date( tender) > RELEASE_2020_04_19 if (new_status == "cancelled" and status in ["draft", "claim", "answered"] and context.type == "claim") or (new_status == "cancelled" and status == "draft" and context.type == "complaint" and not apply_rules_2020_04_19): apply_patch(self.request, save=False, src=context.serialize()) context.dateCanceled = get_now() elif (apply_rules_2020_04_19 and status == "draft" and context.type == "complaint" and new_status == "mistaken"): context.rejectReason = "cancelledByComplainant" apply_patch(self.request, save=False, src=context.serialize()) elif (status in ["pending", "accepted"] and new_status == "stopping" and not apply_rules_2020_04_19): apply_patch(self.request, save=False, src=context.serialize()) context.dateCanceled = get_now() elif (tender.status == "active.tendering" and status == "draft" and new_status == status): apply_patch(self.request, save=False, src=context.serialize()) elif (tender.status == "active.tendering" and context.type == "claim" and status == "draft" and new_status == "claim"): self.validate_submit_claim_time_method(self.request) apply_patch(self.request, save=False, src=context.serialize()) context.dateSubmitted = get_now() elif (tender.status == "active.tendering" and status in ["draft", "claim"] and new_status == "pending" and not apply_rules_2020_04_19): validate_submit_complaint_time(self.request) validate_complaint_type_change(self.request) apply_patch(self.request, save=False, src=context.serialize()) context.type = "complaint" context.dateSubmitted = get_now() elif status == "answered" and new_status == status: apply_patch(self.request, save=False, src=context.serialize()) elif (status == "answered" and data.get("satisfied", context.satisfied) is True and new_status == "resolved"): apply_patch(self.request, save=False, src=context.serialize()) elif (status == "answered" and data.get("satisfied", context.satisfied) is False and new_status == "pending"): validate_submit_complaint_time(self.request) validate_complaint_type_change(self.request) apply_patch(self.request, save=False, src=context.serialize()) context.type = "complaint" context.dateEscalated = get_now() else: raise_operation_error( self.request, "Can't update complaint from {} to {} status".format( status, new_status)) def patch_as_tender_owner(self, data): context = self.context status = context.status new_status = data.get("status", status) if status == "claim" and new_status == status: self.validate_update_claim_time_method(self.request) apply_patch(self.request, save=False, src=context.serialize()) elif status == "satisfied" and new_status == status: apply_patch(self.request, save=False, src=context.serialize()) elif (status == "claim" and data.get("resolution", context.resolution) and data.get("resolutionType", context.resolutionType) and new_status == "answered"): self.validate_update_claim_time_method(self.request) if len(data.get("resolution", context.resolution)) < 20: raise_operation_error( self.request, "Can't update complaint: resolution too short") apply_patch(self.request, save=False, src=context.serialize()) context.dateAnswered = get_now() elif status in ["pending", "accepted"]: apply_patch(self.request, save=False, src=context.serialize()) elif (status == "satisfied" and data.get("tendererAction", context.tendererAction) and new_status == "resolved"): apply_patch(self.request, save=False, src=context.serialize()) else: raise_operation_error( self.request, "Can't update complaint from {} to {} status".format( status, new_status)) def patch_as_abovethresholdreviewers(self, data): context = self.context status = context.status new_status = data.get("status", status) tender = self.request.validated["tender"] old_rules = get_first_revision_date(tender) < RELEASE_2020_04_19 if (status in ["pending", "accepted", "stopping"] and new_status == status): apply_patch(self.request, save=False, src=context.serialize()) elif (status in ["pending", "stopping"] and ((old_rules and new_status in ["invalid", "mistaken"]) or (new_status == "invalid"))): apply_patch(self.request, save=False, src=context.serialize()) context.dateDecision = get_now() context.acceptance = False elif status == "pending" and new_status == "accepted": apply_patch(self.request, save=False, src=context.serialize()) context.dateAccepted = get_now() context.acceptance = True elif (status in ["accepted", "stopping"] and new_status in ["declined", "satisfied"]): apply_patch(self.request, save=False, src=context.serialize()) context.dateDecision = get_now() elif ((old_rules and status in ["pending", "accepted", "stopping"]) or (not old_rules and status == "accepted") and new_status == "stopped"): apply_patch(self.request, save=False, src=context.serialize()) context.dateDecision = get_now() context.dateCanceled = context.dateCanceled or get_now() else: raise_operation_error( self.request, "Can't update complaint from {} to {} status".format( status, new_status))
class TenderNegotiationAwardResource(TenderAwardResource): """ Tender Negotiation Award Resource """ stand_still_delta = timedelta(days=10) @json_view( content_type="application/json", permission="edit_tender", validators=( validate_award_data, validate_award_operation_not_in_active_status, validate_operation_with_lot_cancellation_in_pending("award"), validate_lot_cancellation, validate_create_new_award_with_lots, ), ) def collection_post(self): """Accept or reject bidder application Creating new Award ------------------ Example request to create award: .. sourcecode:: http POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards HTTP/1.1 Host: example.com Accept: application/json { "data": { "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 489, "currency": "UAH", "valueAddedTaxIncluded": true } } } This is what one should expect in response: .. sourcecode:: http HTTP/1.1 201 Created Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 489, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated["tender"] award = self.request.validated["award"] tender.awards.append(award) if save_tender(self.request): self.LOGGER.info( "Created tender award {}".format(award.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_create"}, {"award_id": award.id}), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Awards".format(tender.procurementMethodType), tender_id=tender.id, award_id=award["id"] ) return {"data": award.serialize("view")} @json_view( content_type="application/json", permission="edit_tender", validators=( validate_patch_award_data, validate_operation_with_lot_cancellation_in_pending("award"), validate_award_operation_not_in_active_status, validate_lot_cancellation, ), ) def patch(self): """Update of award Example request to change the award: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated["tender"] award = self.request.context award_status = award.status apply_patch(self.request, save=False, src=self.request.context.serialize()) now = get_now() if award.status == "active" and not award.qualified: raise_operation_error(self.request, "Can't update award to active status with not qualified") if ( award.lotID and [aw.lotID for aw in tender.awards if aw.status in ["pending", "active"]].count(award.lotID) > 1 ): self.request.errors.add("body", "lotID", "Another award is already using this lotID.") self.request.errors.status = 403 raise error_handler(self.request.errors) if award_status == "pending" and award.status == "active": award.complaintPeriod = {"startDate": now.isoformat(), "endDate": calculate_complaint_business_date(now, self.stand_still_delta, tender) } add_contract(self.request, award, now) elif ( award_status == "active" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints]) ): cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" elif award_status == "active" and award.status == "cancelled": if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now for i in tender.contracts: if i.awardID == award.id: i.status = "cancelled" elif award_status == "pending" and award.status == "unsuccessful": award.complaintPeriod = {"startDate": now.isoformat(), "endDate": now} elif ( award_status == "unsuccessful" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints]) ): cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" elif award_status != award.status: raise_operation_error(self.request, "Can't update award in current ({}) status".format(award_status)) elif self.request.authenticated_role != "Administrator" and award_status != "pending": raise_operation_error(self.request, "Can't update award in current ({}) status".format(award_status)) if save_tender(self.request): self.LOGGER.info( "Updated tender award {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_patch"}, {"TENDER_REV": tender.rev}), ) return {"data": award.serialize("view")}
class TenderLimitedNegotiationQuickLotResource(TenderLotResource): route_name = "Tender limited negotiation quick Lots" @json_view( content_type="application/json", validators=(validate_lot_data, validate_lot_operation_not_in_active_status, validate_lot_operation_with_awards), permission="edit_tender", ) def collection_post(self): """Add a lot """ lot = self.request.validated["lot"] lot.date = get_now() tender = self.request.validated["tender"] tender.lots.append(lot) if save_tender(self.request): self.LOGGER.info( "Created tender lot {}".format(lot.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_create"}, {"lot_id": lot.id}), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Lots".format(tender.procurementMethodType), tender_id=tender.id, lot_id=lot.id) return {"data": lot.serialize("view")} @json_view( content_type="application/json", validators=( validate_patch_lot_data, validate_operation_with_lot_cancellation_in_pending("lot"), validate_lot_operation_not_in_active_status, validate_lot_operation_with_awards, ), permission="edit_tender", ) def patch(self): """Update of lot """ tender = self.request.validated["tender"] lot = self.request.context old_rules = get_first_revision_date( tender, default=get_now()) < RELEASE_2020_04_19 if old_rules and [ cancellation for cancellation in tender.get("cancellations") if cancellation.get("relatedLot") == lot["id"] ]: raise_operation_error( self.request, "Can't update lot that have active cancellation") if apply_patch(self.request, src=self.request.context.serialize()): self.LOGGER.info( "Updated tender lot {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_patch"}), ) return {"data": self.request.context.serialize("view")} @json_view( permission="edit_tender", validators=(validate_operation_with_lot_cancellation_in_pending("lot"), validate_lot_operation_not_in_active_status, validate_delete_lot_related_criterion, validate_lot_operation_with_awards), ) def delete(self): """Lot deleting """ lot = self.request.context res = lot.serialize("view") tender = self.request.validated["tender"] tender.lots.remove(lot) if save_tender(self.request): self.LOGGER.info( "Deleted tender lot {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_delete"}), ) return {"data": res}
class TenderUaAwardResource(TenderAwardResource): @json_view( content_type="application/json", permission="edit_tender", validators=( validate_patch_award_data, validate_update_award_in_not_allowed_status, validate_update_award_only_for_active_lots, validate_operation_with_lot_cancellation_in_pending("award"), validate_update_award_with_accepted_complaint, validate_update_status_before_milestone_due_date, ), ) def patch(self): """Update of award Example request to change the award: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated["tender"] award = self.request.context award_status = award.status apply_patch(self.request, save=False, src=self.request.context.serialize()) now = get_now() if award_status == "pending" and award.status == "active": award.complaintPeriod = { "startDate": now.isoformat(), "endDate": calculate_complaint_business_date(now, STAND_STILL_TIME, tender, True) } add_contract(self.request, award, now) add_next_award(self.request) elif (award_status == "active" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints])): cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" add_next_award(self.request) elif award_status == "active" and award.status == "cancelled": if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now for i in tender.contracts: if i.awardID == award.id: i.status = "cancelled" add_next_award(self.request) elif award_status == "pending" and award.status == "unsuccessful": award.complaintPeriod = { "startDate": now.isoformat(), "endDate": calculate_complaint_business_date(now, STAND_STILL_TIME, tender, True) } add_next_award(self.request) elif (award_status == "unsuccessful" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints])): if tender.status == "active.awarded": tender.status = "active.qualification" tender.awardPeriod.endDate = None if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" add_next_award(self.request) elif self.request.authenticated_role != "Administrator" and not ( award_status == "pending" and award.status == "pending"): raise_operation_error( self.request, "Can't update award in current ({}) status".format( award_status)) if save_tender(self.request): self.LOGGER.info( "Updated tender award {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_patch"}), ) return {"data": award.serialize("view")}
class TenderQualificationResource(APIResource): @json_view(permission="view_tender") def collection_get(self): """List qualifications """ return { "data": [ i.serialize("view") for i in self.request.validated["tender"].qualifications ] } @json_view(permission="view_tender") def get(self): """Retrieving the qualification """ return { "data": self.request.validated["qualification"].serialize("view") } @json_view( content_type="application/json", validators=( validate_patch_qualification_data, validate_operation_with_lot_cancellation_in_pending( "qualification"), validate_qualification_update_not_in_pre_qualification, validate_cancelled_qualification_update, validate_update_status_before_milestone_due_date, ), permission="edit_tender", ) def patch(self): """Post a qualification resolution """ def set_bid_status(tender, bid_id, status, lotId=None): if lotId: for bid in tender.bids: if bid.id == bid_id: for lotValue in bid.lotValues: if lotValue.relatedLot == lotId: lotValue.status = status if status in ["pending", "active"]: bid.status = status return bid for bid in tender.bids: if bid.id == bid_id: bid.status = status return bid tender = self.request.validated["tender"] prev_status = self.request.context.status apply_patch(self.request, save=False, src=self.request.context.serialize()) if prev_status != "pending" and self.request.context.status != "cancelled": raise_operation_error( self.request, "Can't update qualification status".format(tender.status)) if self.request.context.status == "active": # approve related bid set_bid_status(tender, self.request.context.bidID, "active", self.request.context.lotID) elif self.request.context.status == "unsuccessful": # cancel related bid set_bid_status(tender, self.request.context.bidID, "unsuccessful", self.request.context.lotID) elif self.request.context.status == "cancelled": # return bid to initial status bid = set_bid_status(tender, self.request.context.bidID, "pending", self.request.context.lotID) # generate new qualification for related bid ids = prepare_qualifications(self.request, bids=[bid], lotId=self.request.context.lotID) self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Qualification".format(tender.procurementMethodType), tender_id=tender.id, qualification_id=ids[0], ) if save_tender(self.request): self.LOGGER.info( "Updated tender qualification {}".format( self.request.context.id), extra=context_unpack( self.request, {"MESSAGE_ID": "tender_qualification_patch"}), ) return {"data": self.request.context.serialize("view")}
class TenderAwardResource(BaseResource): """ EU award resource """ @json_view( content_type="application/json", permission="edit_tender", validators=( validate_patch_award_data, validate_operation_with_lot_cancellation_in_pending("award"), validate_update_award_in_not_allowed_status, validate_update_award_only_for_active_lots, validate_update_award_with_accepted_complaint, ), ) def patch(self): """Update of award Example request to change the award: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated["tender"] award = self.request.context award_status = award.status apply_patch(self.request, save=False, src=self.request.context.serialize()) configurator = self.request.content_configurator now = get_now() if award_status != award.status and award.status == "unsuccessful": if award.complaintPeriod: award.complaintPeriod.startDate = now else: award.complaintPeriod = {"startDate": now.isoformat()} if (tender.status == "active.qualification.stand-still" and award_status == "active" and award.status == "cancelled"): for aw in tender.awards: if aw.lotID == award.lotID: aw.status = "cancelled" add_next_awards( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, regenerate_all_awards=True, lot_id=award.lotID, ) self.context.dateDecision = now tender.status = "active.qualification" if tender.awardPeriod.endDate: tender.awardPeriod.endDate = None else: if award_status == "pending" and award.status == "unsuccessful": add_next_awards( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif award_status == "pending" and award.status == "active": pass elif award_status == "active" and award.status == "cancelled": add_next_awards( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, lot_id=award.lotID, ) elif award_status == "unsuccessful" and award.status == "cancelled": for aw in tender.awards: if aw.lotID == award.lotID: aw.status = "cancelled" add_next_awards( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, regenerate_all_awards=True, lot_id=award.lotID, ) elif self.request.authenticated_role != "Administrator" and not ( award_status == "pending" and award.status == "pending"): raise_operation_error( self.request, "Can't update award in current ({}) status".format( award_status)) if save_tender(self.request): self.LOGGER.info( "Updated tender award {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_patch"}), ) return {"data": award.serialize("view")}
class TenderUaLotResource(TenderLotResource): @json_view( content_type="application/json", validators=( validate_lot_data, validate_lot_operation_not_in_allowed_status, validate_tender_period_extension, ), permission="edit_tender", ) def collection_post(self): """Add a lot """ lot = self.request.validated["lot"] lot.date = get_now() tender = self.request.validated["tender"] tender.lots.append(lot) if self.request.authenticated_role == "tender_owner": tender.invalidate_bids_data() if save_tender(self.request): self.LOGGER.info( "Created tender lot {}".format(lot.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_create"}, {"lot_id": lot.id}), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Lots".format(tender.procurementMethodType), tender_id=tender.id, lot_id=lot.id) return {"data": lot.serialize("view")} @json_view( content_type="application/json", validators=( validate_patch_lot_data, validate_lot_operation_not_in_allowed_status, validate_operation_with_lot_cancellation_in_pending("lot"), validate_tender_period_extension, ), permission="edit_tender", ) def patch(self): """Update of lot """ if self.request.authenticated_role == "tender_owner": self.request.validated["tender"].invalidate_bids_data() if apply_patch(self.request, src=self.request.context.serialize()): self.LOGGER.info( "Updated tender lot {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_patch"}), ) return {"data": self.request.context.serialize("view")} @json_view( permission="edit_tender", validators=( validate_lot_operation_not_in_allowed_status, validate_operation_with_lot_cancellation_in_pending("lot"), validate_tender_period_extension, ), ) def delete(self): """Lot deleting """ lot = self.request.context res = lot.serialize("view") tender = self.request.validated["tender"] tender.lots.remove(lot) if self.request.authenticated_role == "tender_owner": tender.invalidate_bids_data() if save_tender(self.request): self.LOGGER.info( "Deleted tender lot {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_delete"}), ) return {"data": res}
class TenderQuestionResource(APIResource): def validate_question(self, operation): """ TODO move validators This class is inherited in openua package, but validate_question function has different validators. For now, we have no way to use different validators on methods according to procedure type. """ tender = self.request.validated["tender"] if operation == "add" and ( tender.status != "active.enquiries" or tender.enquiryPeriod.startDate and get_now() < tender.enquiryPeriod.startDate or get_now() > tender.enquiryPeriod.endDate): raise_operation_error(self.request, "Can add question only in enquiryPeriod") if operation == "update" and tender.status != "active.enquiries": raise_operation_error( self.request, "Can't update question in current ({}) tender status".format( tender.status)) question = self.request.validated["question"] items_dict = {i.id: i.relatedLot for i in tender.items} if any([ i.status != "active" for i in tender.lots if question.questionOf == "lot" and i.id == question.relatedItem or question.questionOf == "item" and i.id == items_dict[question.relatedItem] ]): raise_operation_error( self.request, "Can {} question only in active lot status".format(operation)) return True @json_view(content_type="application/json", validators=(validate_question_data, ), permission="create_question") def collection_post(self): """Post a question """ if not self.validate_question("add"): return question = self.request.validated["question"] self.request.context.questions.append(question) if save_tender(self.request): self.LOGGER.info( "Created tender question {}".format(question.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_question_create"}, {"question_id": question.id}), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "{}:Tender Questions".format( self.request.context.procurementMethodType), tender_id=self.request.context.id, question_id=question.id, ) return {"data": question.serialize("view")} @json_view(permission="view_tender") def collection_get(self): """List questions """ return { "data": [ i.serialize(self.request.validated["tender"].status) for i in self.request.validated["tender"].questions ] } @json_view(permission="view_tender") def get(self): """Retrieving the question """ return { "data": self.request.validated["question"].serialize( self.request.validated["tender"].status) } @json_view( content_type="application/json", permission="edit_tender", validators=( validate_patch_question_data, validate_operation_with_lot_cancellation_in_pending("question"), )) def patch(self): """Post an Answer """ if not self.validate_question("update"): return self.context.dateAnswered = get_now() if apply_patch(self.request, src=self.request.context.serialize()): self.LOGGER.info( "Updated tender question {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_question_patch"}), ) return { "data": self.request.context.serialize( self.request.validated["tender_status"]) }