Example #1
0
def check_tender_status_on_active_qualification_stand_still(request):

    tender = request.validated["tender"]
    config = getAdapter(tender, IContentConfigurator)
    now = get_now()
    active_lots = [
        lot.id for lot in tender.lots
        if lot.status == "active"
    ] if tender.lots else [None]

    if not (
        tender.awardPeriod
        and tender.awardPeriod.endDate <= now
        and not any(
            [
                i.status in tender.block_complaint_status
                for a in tender.awards
                for i in a.complaints
                if a.lotID in active_lots
            ]
        )
    ):
        return
    statuses = set()
    if tender.lots:
        for lot in tender.lots:
            if lot.status != "active":  # pragma: no cover
                statuses.add(lot.status)
                continue
            active_lot_awards = [i for i in tender.awards if i.lotID == lot.id and i.status == "active"]
            if len(active_lot_awards) < config.min_bids_number:
                LOGGER.info(
                    "Switched lot {} of tender {} to {}".format(lot.id, tender.id, "unsuccessful"),
                    extra=context_unpack(request, {"MESSAGE_ID": "switched_lot_unsuccessful"}, {"LOT_ID": lot.id}),
                )
                lot.status = "unsuccessful"
                statuses.add(lot.status)
                continue
            statuses.add(lot.status)
    else:  # pragma: no cover
        active_awards = [i for i in tender.awards if i.status == "active"]
        if len(active_awards) <= config.min_bids_count:
            statuses.add("unsuccessful")
        else:
            statuses.add("active.awarded")

    if statuses == {"cancelled"}:  # pragma: no cover
        LOGGER.info(
            "Switched tender {} to {}".format(tender.id, "cancelled"),
            extra=context_unpack(request, {"MESSAGE_ID": "switched_tender_cancelled"}),
        )
        tender.status = "cancelled"
    elif not statuses.difference({"unsuccessful", "cancelled"}):  # pragma: no cover
        LOGGER.info(
            "Switched tender {} to {}".format(tender.id, "unsuccessful"),
            extra=context_unpack(request, {"MESSAGE_ID": "switched_tender_unsuccessful"}),
        )
        tender.status = "unsuccessful"
    else:
        LOGGER.info(
            "Switched tender {} to {}".format(tender.id, "active.awarded"),
            extra=context_unpack(request, {"MESSAGE_ID": "switched_tender_active_awarded"}),
        )
        tender.status = "active.awarded"
        clarif_date = calculate_tender_business_date(now, config.clarifications_until_period, tender, False)
        tender.contractPeriod = {
            "startDate": now,
            "clarificationsUntil": clarif_date
        }
        lots = [l for l in tender.get("lots", []) if l.status == "active"]
        if lots:
            for lot in lots:
                agreement_data = generate_agreement_data(request, tender, lot)
                agreement = type(tender).agreements.model_class(agreement_data)
                agreement.__parent__ = tender
                tender.agreements.append(agreement)
        else:  # pragma: no cover
            agreement_data = generate_agreement_data(request, tender)
            agreement = type(tender).agreements.model_class(agreement_data)
            agreement.__parent__ = tender
            tender.agreements.append(agreement)
Example #2
0
def patch_eu(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
    data = self.request.validated["data"]
    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"
    ):
        if all_bids_are_reviewed(self.request):
            tender.qualificationPeriod.endDate = calculate_tender_business_date(
                get_now(), 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.pre-qualification"
        and tender.status != "active.pre-qualification.stand-still"
    ):
        raise_operation_error(self.request, "Can't update tender status")

    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)}
Example #3
0
    def next_check(self):
        now = get_now()
        checks = []
        if self.status == "active.enquiries" and self.tenderPeriod.startDate:
            checks.append(self.tenderPeriod.startDate.astimezone(TZ))
        elif self.status == "active.enquiries" and self.enquiryPeriod.endDate:
            checks.append(self.enquiryPeriod.endDate.astimezone(TZ))
        elif self.status == "active.tendering" and self.tenderPeriod.endDate:
            checks.append(self.tenderPeriod.endDate.astimezone(TZ))
        elif (not self.lots and self.status == "active.auction"
              and self.auctionPeriod and self.auctionPeriod.startDate
              and not self.auctionPeriod.endDate):
            if now < self.auctionPeriod.startDate:
                checks.append(self.auctionPeriod.startDate.astimezone(TZ))
            elif now < calc_auction_end_time(
                    self.numberOfBids,
                    self.auctionPeriod.startDate).astimezone(TZ):
                checks.append(
                    calc_auction_end_time(
                        self.numberOfBids,
                        self.auctionPeriod.startDate).astimezone(TZ))
        elif self.lots and self.status == "active.auction":
            for lot in self.lots:
                if (lot.status != "active" or not lot.auctionPeriod
                        or not lot.auctionPeriod.startDate
                        or lot.auctionPeriod.endDate):
                    continue
                if now < lot.auctionPeriod.startDate:
                    checks.append(lot.auctionPeriod.startDate.astimezone(TZ))
                elif now < calc_auction_end_time(
                        lot.numberOfBids,
                        lot.auctionPeriod.startDate).astimezone(TZ):
                    checks.append(
                        calc_auction_end_time(
                            lot.numberOfBids,
                            lot.auctionPeriod.startDate).astimezone(TZ))
        elif (not self.lots and self.status == "active.awarded" and not any(
            [i.status in self.block_complaint_status for i in self.complaints])
              and not any([
                  i.status in self.block_complaint_status for a in self.awards
                  for i in a.complaints
              ])):
            standStillEnds = [
                a.complaintPeriod.endDate.astimezone(TZ) for a in self.awards
                if a.complaintPeriod.endDate
            ]
            last_award_status = self.awards[-1].status if self.awards else ""
            if standStillEnds and last_award_status == "unsuccessful":
                checks.append(max(standStillEnds))
        elif (self.lots
              and self.status in ["active.qualification", "active.awarded"]
              and not any([
                  i.status in self.block_complaint_status
                  and i.relatedLot is None for i in self.complaints
              ])):
            for lot in self.lots:
                if lot["status"] != "active":
                    continue
                lot_awards = [i for i in self.awards if i.lotID == lot.id]
                pending_complaints = any([
                    i["status"] in self.block_complaint_status
                    and i.relatedLot == lot.id for i in self.complaints
                ])
                pending_awards_complaints = any([
                    i.status in self.block_complaint_status for a in lot_awards
                    for i in a.complaints
                ])
                standStillEnds = [
                    a.complaintPeriod.endDate.astimezone(TZ)
                    for a in lot_awards if a.complaintPeriod.endDate
                ]
                last_award_status = lot_awards[-1].status if lot_awards else ""
                if (not pending_complaints and not pending_awards_complaints
                        and standStillEnds
                        and last_award_status == "unsuccessful"):
                    checks.append(max(standStillEnds))
        if self.status.startswith("active"):
            from openprocurement.tender.core.utils import calculate_tender_business_date

            for complaint in self.complaints:
                if complaint.status == "answered" and complaint.dateAnswered:
                    checks.append(
                        calculate_tender_business_date(
                            complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME,
                            self))
                elif complaint.status == "pending":
                    checks.append(self.dateModified)
            for award in self.awards:
                if award.status == "active" and not any(
                    [i.awardID == award.id for i in self.contracts]):
                    checks.append(award.date)
                for complaint in award.complaints:
                    if complaint.status == "answered" and complaint.dateAnswered:
                        checks.append(
                            calculate_tender_business_date(
                                complaint.dateAnswered,
                                COMPLAINT_STAND_STILL_TIME, self))
                    elif complaint.status == "pending":
                        checks.append(self.dateModified)
        return min(checks).isoformat() if checks else None
Example #4
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
        if self.request.authenticated_role == "chronograph":
            apply_patch(self.request, save=False, src=self.request.validated["tender_src"])
            check_status(self.request)
        elif self.request.authenticated_role == "agreement_selection":
            apply_patch(self.request, save=False, src=self.request.validated["tender_src"])
            if self.request.tender.status == "active.enquiries":
                check_agreement(self.request, tender)
                if tender.status == "active.enquiries":
                    tender.enquiryPeriod.startDate = get_now()
                    tender.enquiryPeriod.endDate = calculate_tender_business_date(
                        tender.enquiryPeriod.startDate, self.request.content_configurator.enquiry_period, tender
                    )
                    tender.tenderPeriod.startDate = tender.enquiryPeriod.endDate
                    tender.tenderPeriod.endDate = calculate_tender_business_date(
                        tender.tenderPeriod.startDate, self.request.content_configurator.tender_period, tender
                    )
                    calculate_agreement_contracts_value_amount(self.request, tender)
                    tender.lots[0].minimalStep = deepcopy(tender.lots[0].value)
                    tender.lots[0].minimalStep.amount = round(
                        self.request.content_configurator.minimal_step_percentage * tender.lots[0].value.amount, 2
                    )
                    calculate_tender_features(self.request, tender)
            else:
                self.LOGGER.info(
                    "Switched tender {} to {}".format(tender.id, "draft.unsuccessful"),
                    extra=context_unpack(
                        self.request,
                        {"MESSAGE_ID": "switched_tender_draft.unsuccessful"},
                        {"CAUSE": AGREEMENT_NOT_FOUND},
                    ),
                )
                tender.unsuccessfulReason = [AGREEMENT_NOT_FOUND]
        elif self.request.authenticated_role == "tender_owner" and tender.status == "active.enquiries":
            validate_json_data_in_active_enquiries(self.request)
            apply_patch(self.request, save=False, data=self.request.validated["data"])
            if "items" in self.request.validated["json_data"]:
                calculate_agreement_contracts_value_amount(self.request, tender)
        else:
            default_status = type(tender).fields["status"].default
            tender_status = tender.status
            apply_patch(self.request, save=False, src=self.request.validated["tender_src"])
            if tender_status == default_status and tender.status == "draft.pending":
                if not tender.agreements or not tender.items:
                    raise_operation_error(
                        self.request, "Can't switch tender to (draft.pending) status without agreements or items."
                    )
            if tender_status == default_status and tender.status not in ("draft.pending", default_status):
                raise_operation_error(
                    self.request, "Can't switch tender from ({}) to ({}) status.".format(default_status, tender.status)
                )
        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)}
Example #5
0
 def validate_tenderPeriod(self, data, period):
     # data['_rev'] is None when tender was created just now
     if not data["_rev"] and calculate_tender_business_date(get_now(), -timedelta(minutes=10)) >= period.startDate:
         raise ValidationError(u"tenderPeriod.startDate should be in greater than current date")
     if period and calculate_tender_business_date(period.startDate, TENDER_PERIOD, data) > period.endDate:
         raise ValidationError(u"tenderPeriod should be greater than 15 days")
 def test_set_auctionPeriod_nextday(self):
     now = datetime.now(TZ)
     response = self.api.patch_json(
         self.app.app.registry.api_url + 'tenders/' + self.tender_id, {
             'data': {
                 'enquiryPeriod': {
                     'startDate':
                     calculate_tender_business_date(
                         now,
                         timedelta(days=-15),
                         tender=test_tender_data_quick,
                         working_days=True).isoformat(),
                     'endDate':
                     calculate_tender_business_date(
                         now,
                         timedelta(days=-9),
                         tender=test_tender_data_quick,
                         working_days=True).isoformat()
                 },
                 'tenderPeriod': {
                     'startDate':
                     now.isoformat(),
                     'endDate':
                     (now + timedelta(days=14 - now.weekday())).replace(
                         hour=13).isoformat()
                 }
             }
         })
     response = self.app.get('/recheck/' + self.tender_id)
     self.assertEqual(response.status, '200 OK')
     self.assertNotEqual(response.json, None)
     response = self.api.get(self.app.app.registry.api_url + 'tenders/' +
                             self.tender_id)
     tender = response.json['data']
     self.assertEqual(tender['status'], 'active.tendering')
     response = self.app.get('/resync/' + self.tender_id)
     self.assertEqual(response.status, '200 OK')
     self.assertEqual(response.json, None)
     response = self.api.get(self.app.app.registry.api_url + 'tenders/' +
                             self.tender_id)
     tender = response.json['data']
     self.assertEqual(tender['status'], 'active.tendering')
     if self.initial_lots:
         self.assertIn('auctionPeriod', tender['lots'][0])
         self.assertEqual(
             parse_date(tender['lots'][0]['auctionPeriod']['startDate'],
                        TZ).weekday(), 1)
     else:
         self.assertIn('auctionPeriod', tender)
         self.assertEqual(
             parse_date(tender['auctionPeriod']['startDate'], TZ).weekday(),
             1)
     response = self.app.get('/recheck/' + self.tender_id)
     self.assertEqual(response.status, '200 OK')
     self.assertNotEqual(response.json, None)
     self.app.app.registry.scheduler.start()
     response = self.app.get('/')
     self.assertEqual(response.status, '200 OK')
     self.assertIn('jobs', response.json)
     self.assertIn('recheck_{}'.format(self.tender_id),
                   response.json['jobs'])
     self.assertGreaterEqual(
         parse_date(response.json['jobs']['recheck_{}'.format(
             self.tender_id)]).utctimetuple(),
         parse_date(tender['tenderPeriod']['endDate']).utctimetuple())
     self.assertLessEqual(
         parse_date(response.json['jobs']['recheck_{}'.format(
             self.tender_id)]).utctimetuple(),
         (parse_date(tender['tenderPeriod']['endDate']) +
          timedelta(minutes=5)).utctimetuple())
    def test_set_auctionPeriod_jobs(self):
        now = datetime.now(TZ)
        self.api.patch_json(
            self.app.app.registry.api_url + 'tenders/' + self.tender_id, {
                'data': {
                    'enquiryPeriod': {
                        'startDate':
                        calculate_tender_business_date(
                            now,
                            timedelta(days=-15),
                            tender=test_tender_data_quick,
                            working_days=True).isoformat(),
                        'endDate':
                        calculate_tender_business_date(
                            now,
                            timedelta(days=-9),
                            tender=test_tender_data_quick,
                            working_days=True).isoformat()
                    },
                    'tenderPeriod': {
                        'startDate':
                        calculate_tender_business_date(
                            now,
                            timedelta(days=-9),
                            tender=test_tender_data_quick,
                            working_days=True).isoformat(),
                        'endDate':
                        calculate_tender_business_date(
                            now,
                            timedelta(days=1),
                            tender=test_tender_data_quick,
                            working_days=True).isoformat()
                    }
                }
            })
        for _ in range(100):
            self.app.app.registry.scheduler.start()
            response = self.app.get('/resync_all')
            self.assertEqual(response.status, '200 OK')
            self.assertNotEqual(response.json, None)
            response = self.app.get('/')
            self.app.app.registry.scheduler.shutdown()
            self.assertEqual(response.status, '200 OK')
            self.assertIn('jobs', response.json)
            self.assertEqual(len(response.json['jobs']), 2)
            if 'recheck_{}'.format(self.tender_id) in response.json['jobs']:
                break
        self.assertIn('recheck_{}'.format(self.tender_id),
                      response.json['jobs'])

        response = self.app.get('/recheck/' + self.tender_id)
        self.assertEqual(response.status, '200 OK')
        self.assertNotEqual(response.json, None)

        for _ in range(10):
            self.app.app.registry.scheduler.start()
            self.app.get('/resync_all')
            self.app.app.registry.scheduler.shutdown()

            response = self.api.get(self.app.app.registry.api_url +
                                    'tenders/' + self.tender_id)
            tender = response.json['data']
            self.assertEqual(tender['status'], 'active.tendering')

            if self.initial_lots:
                self.assertIn('auctionPeriod', tender['lots'][0])
                if 'startDate' in tender['lots'][0]['auctionPeriod']:
                    break
            else:
                self.assertIn('auctionPeriod', tender)
                if 'startDate' in tender['auctionPeriod']:
                    break
        else:
            response = self.app.get('/resync/' + self.tender_id)
            self.assertEqual(response.status, '200 OK')
            self.assertEqual(response.json, None)

        response = self.api.get(self.app.app.registry.api_url + 'tenders/' +
                                self.tender_id)
        tender = response.json['data']
        self.assertEqual(tender['status'], 'active.tendering')
        if self.initial_lots:
            self.assertIn('startDate', tender['lots'][0]['auctionPeriod'])
        else:
            self.assertIn('startDate', tender['auctionPeriod'])
 def wrapper():
     return PeriodStartEndRequired(
         {"startDate": get_now(), "endDate": calculate_tender_business_date(get_now(), tendering_duration)}
     )
    def test_24hours_milestone(self):
        self.app.authorization = ("Basic", ("broker", ""))

        # try upload documents
        response = self.app.get("/tenders/{}".format(self.tender_id))
        context = response.json["data"]["{}s".format(self.context_name)][0]
        bid_id = context.get("bid_id") or context.get(
            "bidID")  # awards and qualifications developed on different days
        winner_token = self.initial_bids_tokens[bid_id]
        upload_allowed_by_default = response.json["data"][
            "procurementMethodType"] == "aboveThresholdUA.defense"
        self.assert_upload_docs_status(bid_id,
                                       winner_token,
                                       success=upload_allowed_by_default)

        # invalid creation
        response = self.app.post_json("/tenders/{}/{}s/{}/milestones".format(
            self.tender_id, self.context_name, self.context_id), {"data": {}},
                                      status=403)
        self.assertEqual(
            response.json, {
                "status":
                "error",
                "errors": [{
                    "location": "url",
                    "name": "permission",
                    "description": "Forbidden"
                }]
            })
        response = self.app.post_json(
            "/tenders/{}/{}s/{}/milestones?acc_token={}".format(
                self.tender_id, self.context_name, self.context_id,
                self.tender_token), {"data": {
                    "code": "alp"
                }},
            status=403)
        if get_now() > RELEASE_2020_04_19:
            self.assertEqual(
                response.json, {
                    "status":
                    "error",
                    "errors": [{
                        "description":
                        "The only allowed milestone code is '24h'",
                        "location": "body",
                        "name": "data"
                    }]
                })
        else:
            self.assertEqual(
                response.json, {
                    "status":
                    "error",
                    "errors": [{
                        "location": "body",
                        "name": "data",
                        "description": "Forbidden"
                    }]
                })
            return

        # valid creation
        request_data = {
            "code": "24h",
            "description":
            "One ring to bring them all and in the darkness bind them",
            "dueDate": (get_now() + timedelta(days=10)).isoformat()
        }
        response = self.app.post_json(
            "/tenders/{}/{}s/{}/milestones?acc_token={}".format(
                self.tender_id, self.context_name, self.context_id,
                self.tender_token),
            {"data": request_data},
        )
        self.assertEqual(response.status, "201 Created")
        created_milestone = response.json["data"]

        # get milestone from tender
        response = self.app.get("/tenders/{}".format(self.tender_id))
        tender_data = response.json["data"]
        context = tender_data["{}s".format(self.context_name)][0]
        public_milestone = context["milestones"][0]

        self.assertEqual(created_milestone, public_milestone)
        self.assertEqual(set(created_milestone.keys()), {
            "id",
            "date",
            "code",
            "description",
            "dueDate",
        })
        self.assertEqual(created_milestone["code"], request_data["code"])
        self.assertEqual(created_milestone["description"],
                         request_data["description"])
        self.assertNotEqual(created_milestone["dueDate"],
                            request_data["dueDate"])
        expected_date = calculate_tender_business_date(
            parse_date(created_milestone["date"]), timedelta(hours=24),
            tender_data)
        self.assertEqual(created_milestone["dueDate"],
                         expected_date.isoformat())

        # get milestone by its direct link
        response = self.app.get("/tenders/{}/{}s/{}/milestones/{}".format(
            self.tender_id, self.context_name, self.context_id,
            created_milestone["id"]))
        direct_milestone = response.json["data"]
        self.assertEqual(created_milestone, direct_milestone)

        # can't post another
        response = self.app.post_json(
            "/tenders/{}/{}s/{}/milestones?acc_token={}".format(
                self.tender_id, self.context_name, self.context_id,
                self.tender_token), {"data": request_data},
            status=422)
        self.assertEqual(
            response.json, {
                "status":
                "error",
                "errors": [{
                    "description": [{
                        "milestones":
                        ["There can be only one '24h' milestone"]
                    }],
                    "location":
                    "body",
                    "name":
                    "{}s".format(self.context_name)
                }]
            })

        # can't update status of context until dueDate
        activation_data = {
            "status": "active",
            "qualified": True,
            "eligible": True
        }
        response = self.app.patch_json(
            "/tenders/{}/{}s/{}?acc_token={}".format(self.tender_id,
                                                     self.context_name,
                                                     self.context_id,
                                                     self.tender_token),
            {"data": activation_data},
            status=403)
        self.assertEqual(
            response.json, {
                "status":
                "error",
                "errors": [{
                    "description":
                    "Can't change status to 'active' "
                    "until milestone.dueDate: {}".format(
                        created_milestone["dueDate"]),
                    "location":
                    "body",
                    "name":
                    "data"
                }]
            })

        # try upload documents
        self.assert_upload_docs_status(bid_id, winner_token)

        # wait until milestone dueDate ends
        with patch("openprocurement.tender.core.validation.get_now",
                   lambda: get_now() + timedelta(hours=24)):
            self.assert_upload_docs_status(bid_id,
                                           winner_token,
                                           success=upload_allowed_by_default)

            response = self.app.patch_json(
                "/tenders/{}/{}s/{}?acc_token={}".format(
                    self.tender_id, self.context_name, self.context_id,
                    self.tender_token), {"data": activation_data},
                status=200)
            self.assertEqual(response.json["data"]["status"], "active")

        # check appending milestone at active qualification status
        # remove milestone to skip "only one" validator
        tender = self.db.get(self.tender_id)
        context = tender["{}s".format(self.context_name)][0]
        context["milestones"] = []
        self.db.save(tender)

        response = self.app.post_json(
            "/tenders/{}/{}s/{}/milestones?acc_token={}".format(
                self.tender_id, self.context_name, self.context_id,
                self.tender_token), {"data": request_data},
            status=403)
        self.assertEqual(
            response.json, {
                "status":
                "error",
                "errors": [{
                    "description":
                    "Not allowed in current 'active' {} status".format(
                        self.context_name),
                    "location":
                    "body",
                    "name":
                    "data"
                }]
            })