Ejemplo n.º 1
0
    def patch(self):
        cancellation = self.request.context
        prev_status = cancellation.status
        apply_patch(self.request, save=False, src=cancellation.serialize())
        new_rules = get_first_revision_date(
            self.request.tender, default=get_now()) > RELEASE_2020_04_19

        if new_rules:
            if prev_status == "draft" and cancellation.status == "pending":
                validate_absence_of_pending_accepted_satisfied_complaints(
                    self.request)
                tender = self.request.validated["tender"]
                now = get_now()
                cancellation.complaintPeriod = {
                    "startDate":
                    now.isoformat(),
                    "endDate":
                    calculate_complaint_business_date(now, timedelta(days=10),
                                                      tender).isoformat()
                }
        if cancellation.status == "active" and prev_status != "active":
            self.cancel_tender_lot_method(self.request, cancellation)

        if save_tender(self.request):
            self.LOGGER.info(
                "Updated tender cancellation {}".format(cancellation.id),
                extra=context_unpack(
                    self.request, {"MESSAGE_ID": "tender_cancellation_patch"}),
            )
            return {"data": cancellation.serialize("view")}
Ejemplo n.º 2
0
 def __call__(self, obj, *args, **kwargs):
     complaintPeriod_class = obj._fields["tenderPeriod"]
     endDate = calculate_complaint_business_date(obj.tenderPeriod.endDate,
                                                 -COMPLAINT_SUBMIT_TIME,
                                                 obj)
     return complaintPeriod_class(
         dict(startDate=obj.tenderPeriod.startDate, endDate=endDate))
Ejemplo n.º 3
0
    def patch(self):
        """Tender Edit (partial)

        For example here is how procuring entity can change number of items to be procured and total Value of a tender:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607 HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "tenderID": "UA-64e93250be76435397e8c992ed4214d1",
                    "dateModified": "2014-10-27T08:12:34.956Z",
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        """
        tender = self.context
        config = getAdapter(tender, IContentConfigurator)
        data = self.request.validated["data"]
        now = get_now()
        if (self.request.authenticated_role == "tender_owner" and
                self.request.validated["tender_status"] == "active.tendering"):
            if "tenderPeriod" in data and "endDate" in data["tenderPeriod"]:
                self.request.validated["tender"].tenderPeriod.import_data(
                    data["tenderPeriod"])
                validate_tender_period_extension(self.request)
                self.request.registry.notify(
                    TenderInitializeEvent(self.request.validated["tender"]))
                self.request.validated["data"][
                    "enquiryPeriod"] = self.request.validated[
                        "tender"].enquiryPeriod.serialize()

        apply_patch(self.request,
                    save=False,
                    src=self.request.validated["tender_src"])
        if self.request.authenticated_role == "chronograph":
            check_status(self.request)
        elif self.request.authenticated_role == "tender_owner" and tender.status == "active.tendering":
            tender.invalidate_bids_data()
        elif (self.request.authenticated_role == "tender_owner"
              and self.request.validated["tender_status"]
              == "active.pre-qualification"
              and tender.status == "active.pre-qualification.stand-still"):
            active_lots = [
                lot.id for lot in tender.lots if lot.status == "active"
            ] if tender.lots else [None]
            if any([
                    i["status"]
                    in self.request.validated["tender"].block_complaint_status
                    for q in self.request.validated["tender"]["qualifications"]
                    for i in q["complaints"] if q["lotID"] in active_lots
            ]):
                raise_operation_error(
                    self.request,
                    "Can't switch to 'active.pre-qualification.stand-still' before resolve all complaints"
                )
            if all_bids_are_reviewed(self.request):
                tender.qualificationPeriod.endDate = calculate_complaint_business_date(
                    now, config.prequalification_complaint_stand_still,
                    self.request.validated["tender"])
                tender.check_auction_time()
            else:
                raise_operation_error(
                    self.request,
                    "Can't switch to 'active.pre-qualification.stand-still' while not all bids are qualified",
                )
        elif (self.request.authenticated_role == "tender_owner" and
              self.request.validated["tender_status"] == "active.qualification"
              and tender.status == "active.qualification.stand-still"):
            active_lots = [
                lot.id for lot in tender.lots if lot.status == "active"
            ] if tender.lots else [None]
            if any([
                    i["status"]
                    in self.request.validated["tender"].block_complaint_status
                    for a in self.request.validated["tender"]["awards"]
                    for i in a["complaints"] if a["lotID"] in active_lots
            ]):
                raise_operation_error(
                    self.request,
                    "Can't switch to 'active.qualification.stand-still' before resolve all complaints"
                )
            if all_awards_are_reviewed(self.request):
                tender.awardPeriod.endDate = calculate_complaint_business_date(
                    now, config.qualification_complaint_stand_still,
                    self.request.validated["tender"])
                for award in [
                        a for a in tender.awards if a.status != "cancelled"
                ]:
                    award["complaintPeriod"] = {
                        "startDate": now.isoformat(),
                        "endDate": tender.awardPeriod.endDate.isoformat(),
                    }
            else:
                raise_operation_error(
                    self.request,
                    "Can't switch to 'active.qualification.stand-still' while not all awards are qualified",
                )

        save_tender(self.request)
        self.LOGGER.info("Updated tender {}".format(tender.id),
                         extra=context_unpack(self.request,
                                              {"MESSAGE_ID": "tender_patch"}))
        return {"data": tender.serialize(tender.status)}
Ejemplo n.º 4
0
 def complaintPeriod(self):
     endDate = calculate_complaint_business_date(self.tenderPeriod.endDate,
                                                 -COMPLAINT_SUBMIT_TIME,
                                                 self)
     return Period(
         dict(startDate=self.tenderPeriod.startDate, endDate=endDate))
Ejemplo n.º 5
0
    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 in [
                "active", "unsuccessful"
        ]:
            if award.complaintPeriod:
                award.complaintPeriod.startDate = now
            else:
                award.complaintPeriod = {"startDate": now.isoformat()}

        if award_status == "pending" and award.status == "active":
            award.complaintPeriod.endDate = calculate_complaint_business_date(
                now, STAND_STILL_TIME, tender)
            add_contract(self.request, award, now)
            add_next_award(
                self.request,
                reverse=configurator.reverse_awarding_criteria,
                awarding_criteria_key=configurator.awarding_criteria_key,
            )
        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,
                reverse=configurator.reverse_awarding_criteria,
                awarding_criteria_key=configurator.awarding_criteria_key,
            )
        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,
                reverse=configurator.reverse_awarding_criteria,
                awarding_criteria_key=configurator.awarding_criteria_key,
            )
        elif award_status == "pending" and award.status == "unsuccessful":
            award.complaintPeriod.endDate = calculate_complaint_business_date(
                get_now(), STAND_STILL_TIME, tender)
            add_next_award(
                self.request,
                reverse=configurator.reverse_awarding_criteria,
                awarding_criteria_key=configurator.awarding_criteria_key,
            )
        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,
                reverse=configurator.reverse_awarding_criteria,
                awarding_criteria_key=configurator.awarding_criteria_key,
            )
        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")}
Ejemplo n.º 6
0
    def test_milestone(self):
        """
        test alp milestone is created in two cases
        1. amount less by >=40% than mean of amount before auction
        2. amount less by >=30%  than the next amount
        :return:
        """
        # sending auction results
        auction_results = deepcopy(self.initial_bids)
        if "lotValues" in self.initial_bids[0]:
            lot_id = auction_results[0]["lotValues"][0]["relatedLot"]
            auction_results[0]["lotValues"][0]["value"]["amount"] = 29  # only 1 case
            auction_results[1]["lotValues"][0]["value"]["amount"] = 30  # both 1 and 2 case
            auction_results[2]["lotValues"][0]["value"]["amount"] = 350  # only 2 case
            auction_results[3]["lotValues"][0]["value"]["amount"] = 500  # no milestones
        else:
            lot_id = None
            auction_results[0]["value"]["amount"] = 29   # only 1 case
            auction_results[1]["value"]["amount"] = 30   # both 1 and 2 case
            auction_results[2]["value"]["amount"] = 350   # only 2 case
            auction_results[3]["value"]["amount"] = 500  # no milestones

        with change_auth(self.app, ("Basic", ("auction", ""))):
            url = "/tenders/{}/auction".format(self.tender_id)
            if lot_id:
                url += "/" + lot_id
            response = self.app.post_json(
                url,
                {"data": {"bids": auction_results}},
                status=200
            )
        tender = response.json["data"]
        self.assertEqual("active.qualification", tender["status"])
        self.assertGreater(len(tender["awards"]), 0)
        award = tender["awards"][0]
        bid_id = award["bid_id"]
        self.assertEqual(bid_id, auction_results[0]["id"])

        if get_now() < RELEASE_2020_04_19:
            return self.assertEqual(len(award.get("milestones", [])), 0)

        # check that a milestone's been created
        self.assertEqual(len(award.get("milestones", [])), 1)
        milestone = award["milestones"][0]
        self.assertEqual(milestone["code"], "alp")
        self.assertEqual(milestone["description"], ALP_MILESTONE_REASONS[0])

        # try to change award status
        unsuccessful_data = {"status": "unsuccessful"}
        response = self.app.patch_json(
            "/tenders/{}/awards/{}?acc_token={}".format(
                self.tender_id, award["id"], self.tender_token
            ),
            {"data": unsuccessful_data},
            status=403
        )
        expected_due_date = calculate_complaint_business_date(
            parse_date(milestone["date"]),
            timedelta(days=1),
            tender,
            working_days=True,
        )
        self.assertEqual(
            response.json,
            {
                u'status': u'error', u'errors': [{
                    u'description': u"Can't change status to 'unsuccessful' until milestone.dueDate: {}".format(
                        expected_due_date.isoformat()
                    ),
                    u'location': u'body', u'name': u'data'
                }]
            }
        )

        # try to post/put/patch docs
        for doc_type in ["evidence", None]:
            self._test_doc_upload(
                tender["procurementMethodType"], doc_type,
                bid_id, self.initial_bids_tokens[bid_id], expected_due_date
            )

        # setting "dueDate" to now
        self.wait_until_award_milestone_due_date(award_index=0)

        # after milestone dueDate tender owner can change award status
        response = self.app.patch_json(
            "/tenders/{}/awards/{}?acc_token={}".format(
                self.tender_id, award["id"], self.tender_token
            ),
            {"data": unsuccessful_data},
            status=200
        )
        self.assertEqual(response.json["data"]["status"], "unsuccessful")

        # check second award
        response = self.app.get(
            "/tenders/{}/awards?acc_token={}".format(self.tender_id, self.tender_token),
            status=200
        )
        self.assertGreater(len(response.json["data"]), 1)
        second_award = response.json["data"][1]
        self.assertEqual(len(second_award.get("milestones", [])), 1)
        self.assertEqual(second_award["milestones"][0]["description"], u" / ".join(ALP_MILESTONE_REASONS))

        # proceed to the third award
        self.wait_until_award_milestone_due_date(award_index=1)
        response = self.app.patch_json(
            "/tenders/{}/awards/{}?acc_token={}".format(
                self.tender_id, second_award["id"], self.tender_token
            ),
            {"data": unsuccessful_data},
            status=200
        )
        self.assertEqual(response.json["data"]["status"], "unsuccessful")
        # checking 3rd award
        response = self.app.get(
            "/tenders/{}/awards?acc_token={}".format(self.tender_id, self.tender_token),
            status=200
        )
        self.assertGreater(len(response.json["data"]), 2)
        third_award = response.json["data"][2]
        self.assertEqual(len(third_award.get("milestones", [])), 1)
        self.assertEqual(third_award["milestones"][0]["description"], ALP_MILESTONE_REASONS[1])

        # proceed to the last award
        self.wait_until_award_milestone_due_date(award_index=2)
        response = self.app.patch_json(
            "/tenders/{}/awards/{}?acc_token={}".format(
                self.tender_id, third_award["id"], self.tender_token
            ),
            {"data": unsuccessful_data},
            status=200
        )
        self.assertEqual(response.json["data"]["status"], "unsuccessful")
        # checking last award
        response = self.app.get(
            "/tenders/{}/awards?acc_token={}".format(self.tender_id, self.tender_token),
            status=200
        )
        self.assertGreater(len(response.json["data"]), 3)
        last_award = response.json["data"][3]
        self.assertNotIn("milestones", last_award)