Esempio n. 1
0
 def shouldStartAfter(self):
     if self.endDate:
         return
     tender = get_tender(self)
     lot = self.__parent__
     statuses = [
         "active.tendering", "active.pre-qualification.stand-still",
         "active.auction"
     ]
     if tender.status not in statuses or lot.status != "active":
         return
     start_after = None
     if tender.status == "active.tendering" and tender.tenderPeriod.endDate:
         start_after = calculate_tender_date(tender.tenderPeriod.endDate,
                                             TENDERING_AUCTION, tender)
     elif self.startDate and get_now() > calc_auction_end_time(
             lot.numberOfBids, self.startDate):
         start_after = calc_auction_end_time(lot.numberOfBids,
                                             self.startDate)
     elif tender.qualificationPeriod and tender.qualificationPeriod.endDate:
         decision_dates = [
             datetime.combine(
                 complaint.dateDecision.date() + timedelta(days=3),
                 time(0, tzinfo=complaint.dateDecision.tzinfo))
             for qualification in tender.qualifications
             for complaint in qualification.complaints
             if complaint.dateDecision
         ]
         decision_dates.append(tender.qualificationPeriod.endDate)
         start_after = max(decision_dates)
     if start_after:
         return normalize_should_start_after(start_after,
                                             tender).isoformat()
Esempio n. 2
0
 def calculate_period_date(self, date, period, startend, status):
     tender = self.tender_class(self.tender_document)
     period_date_item = self.periods[status][startend][period][date]
     return calculate_tender_date(self.now,
                                  period_date_item,
                                  tender=tender,
                                  working_days=False)
Esempio n. 3
0
def _validate_tender_period_start_date(data, period, working_days=False, calendar=WORKING_DAYS):
    TENDER_CREATION_BUFFER_DURATION=timedelta(minutes=10)
    min_allowed_date = calculate_tender_date(
        get_now(), -TENDER_CREATION_BUFFER_DURATION,
        working_days=working_days,
        calendar=calendar
    )
    if min_allowed_date >= period.startDate:
        raise ValidationError("tenderPeriod.startDate should be in greater than current date")
Esempio n. 4
0
def check_complaint_status(request, complaint, now=None):
    if not now:
        now = get_now()
    if complaint.status == "answered":
        date = calculate_tender_date(complaint.dateAnswered,
                                     COMPLAINT_STAND_STILL_TIME,
                                     request.tender)
        if date < now:
            complaint.status = complaint.resolutionType
    elif complaint.status == "pending" and complaint.resolutionType and complaint.dateEscalated:
        complaint.status = complaint.resolutionType
    elif complaint.status == "pending":
        complaint.status = "ignored"
Esempio n. 5
0
def check_period_and_items(request, tender):
    agreement_items = tender.agreements[0].items if tender.agreements[0].items else []
    agreement_items_ids = {calculate_item_identification_tuple(agreement_item) for agreement_item in agreement_items}
    tender_items_ids = {calculate_item_identification_tuple(tender_item) for tender_item in tender.items}

    if not tender_items_ids.issubset(agreement_items_ids):
        drop_draft_to_unsuccessful(request, tender, AGREEMENT_ITEMS)
        return

    delta = -request.content_configurator.agreement_expired_until
    date = calculate_tender_date(tender.agreements[0].period.endDate, delta, tender)
    if get_now() > date:
        drop_draft_to_unsuccessful(request, tender, AGREEMENT_EXPIRED)
    elif tender.agreements[0].period.startDate > tender.date:
        drop_draft_to_unsuccessful(request, tender, AGREEMENT_START_DATE)
Esempio n. 6
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))
         else:
             auction_end_time = calc_auction_end_time(
                 self.numberOfBids,
                 self.auctionPeriod.startDate).astimezone(TZ)
             if now < auction_end_time:
                 checks.append(auction_end_time)
     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))
             else:
                 auction_end_time = calc_auction_end_time(
                     lot.numberOfBids,
                     lot.auctionPeriod.startDate).astimezone(TZ)
                 if now < auction_end_time:
                     checks.append(auction_end_time)
     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 and 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 and 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"):
         for complaint in self.complaints:
             if complaint.status == "answered" and complaint.dateAnswered:
                 check = calculate_tender_date(complaint.dateAnswered,
                                               COMPLAINT_STAND_STILL_TIME,
                                               self)
                 checks.append(check)
             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:
                     check = calculate_tender_date(
                         complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME,
                         self)
                     checks.append(check)
                 elif complaint.status == "pending":
                     checks.append(self.dateModified)
     return min(checks).isoformat() if checks else None
Esempio n. 7
0
    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"] in \
                                    ("aboveThresholdUA.defense", "simple.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_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"}]}
        )