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))
示例#2
0
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")}
示例#3
0
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}
示例#4
0
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")}
示例#6
0
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")}
示例#7
0
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}
示例#8
0
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"])
            }