def lot_create_tender_question(self): cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.initial_lots[0]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") cancellation_id = response.json["data"]["id"] if RELEASE_2020_04_19 < get_now(): activate_cancellation_after_2020_04_19(self, cancellation_id) response = self.app.post_json( "/tenders/{}/questions".format(self.tender_id, self.tender_token), { "data": { "title": "question title", "description": "question description", "questionOf": "lot", "relatedItem": self.initial_lots[0]["id"], "author": self.author_data, } }, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can add question only in active lot status") response = self.app.post_json( "/tenders/{}/questions".format(self.tender_id, self.tender_token), { "data": { "title": "question title", "description": "question description", "questionOf": "lot", "relatedItem": self.initial_lots[1]["id"], "author": self.author_data, } }, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") question = response.json["data"] self.assertEqual(question["author"]["name"], self.author_data["name"]) self.assertIn("id", question) self.assertIn(question["id"], response.headers["Location"])
def delete_first_lot_second_cancel(self): """ One lot we delete another cancel and check tender status """ self.app.patch_json( "/tenders/{}?acc_token={}".format(self.tender_id, self.tender_token), {"data": { "items": [{ "relatedLot": self.initial_lots[1]["id"] }] }}, ) response = self.app.delete("/tenders/{}/lots/{}?acc_token={}".format( self.tender_id, self.initial_lots[0]["id"], self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") response = self.app.get("/tenders/{}/lots?acc_token={}".format( self.tender_id, self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(len(response.json["data"]), 1) cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.initial_lots[1]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) if RELEASE_2020_04_19 > get_now(): self.assertEqual(cancellation["status"], "active") else: activate_cancellation_with_complaints_after_2020_04_19( self, cancellation["id"]) response = self.app.get("/tenders/{}?acc_token={}".format( self.tender_id, self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "cancelled")
def tender_init_handler(event): """ initialization handler for openuadefence tenders """ tender = event.tender endDate = calculate_business_date(tender.tenderPeriod.endDate, -ENQUIRY_PERIOD_TIME, tender, True) tender.enquiryPeriod = EnquiryPeriod(dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date(endDate, ENQUIRY_STAND_STILL_TIME, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now
def calculate_business_date(date_obj, timedelta_obj, context=None, working_days=False): if (context.get('revisions')[0].date if context and context.get('revisions') else get_now()) < CALCULATE_BUSINESS_DATE_FROM: return calculate_business_date_base(date_obj, timedelta_obj, context, working_days) if context and 'procurementMethodDetails' in context and context[ 'procurementMethodDetails']: re_obj = ACCELERATOR_RE.search(context['procurementMethodDetails']) if re_obj and 'accelerator' in re_obj.groupdict(): return date_obj + (timedelta_obj / int(re_obj.groupdict()['accelerator'])) if working_days: if timedelta_obj > timedelta(): if date_obj.weekday() in [5, 6] and WORKING_DAYS.get( date_obj.date().isoformat(), True) or WORKING_DAYS.get( date_obj.date().isoformat(), False): date_obj = datetime.combine( date_obj.date(), time( 0, tzinfo=date_obj.tzinfo)) + timedelta(1) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get( date_obj.date().isoformat(), True) or WORKING_DAYS.get( date_obj.date().isoformat(), False): date_obj += timedelta(1) else: if date_obj.weekday() in [5, 6] and WORKING_DAYS.get( date_obj.date().isoformat(), True) or WORKING_DAYS.get( date_obj.date().isoformat(), False): date_obj = datetime.combine(date_obj.date(), time(0, tzinfo=date_obj.tzinfo)) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get( date_obj.date().isoformat(), True) or WORKING_DAYS.get( date_obj.date().isoformat(), False): date_obj -= timedelta(1) date_obj += timedelta(1) for _ in xrange(abs(timedelta_obj.days)): date_obj += timedelta( 1) if timedelta_obj > timedelta() else -timedelta(1) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get( date_obj.date().isoformat(), True) or WORKING_DAYS.get( date_obj.date().isoformat(), False): date_obj += timedelta( 1) if timedelta_obj > timedelta() else -timedelta(1) return date_obj return date_obj + timedelta_obj
def cancellation_on_not_active_lot(self): lot = self.initial_lots[0] # Create cancellation on lot with status cancelled cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": lot["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation_id = response.json["data"]["id"] if RELEASE_2020_04_19 < get_now(): activate_cancellation_with_complaints_after_2020_04_19( self, cancellation_id) # check lot status response = self.app.get("/tenders/{}/lots/{}".format( self.tender_id, lot["id"])) self.assertEqual(response.json["data"]["status"], "cancelled") # Try to create cancellation on lot with status cancelled cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": lot["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can perform cancellation only in active lot status")
def tender_init_handler(event): """ initialization handler for closeFrameworkAgreementUA tenders """ tender = event.tender config = getAdapter(tender, IContentConfigurator) endDate = calculate_business_date(tender.tenderPeriod.endDate, -config.questions_stand_still, tender) tender.enquiryPeriod = EnquiryPeriod( dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, config.enquiry_stand_still, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now
def tender_init_handler(event): """ initialization handler for esco tenders """ tender = event.tender endDate = calculate_business_date(tender.tenderPeriod.endDate, -QUESTIONS_STAND_STILL, tender) tender.enquiryPeriod = EnquiryPeriod( dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, ENQUIRY_STAND_STILL_TIME, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now check_submission_method_details(tender)
def tender_init_handler(event): """ initialization handler for closeFrameworkAgreementUA tenders """ tender = event.tender end_date = calculate_tender_business_date(tender.tenderPeriod.endDate, -QUESTIONS_STAND_STILL, tender) clarifications_until = calculate_clarif_business_date( end_date, QUESTIONS_STAND_STILL, tender, True) tender.enquiryPeriod = EnquiryPeriod( dict( startDate=tender.tenderPeriod.startDate, endDate=end_date, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=clarifications_until, )) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now
def create_tender_cancellation_with_post(self): response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": test_cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "active") cancellation = dict(**test_cancellation) cancellation.update({"status": "active"}) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") if get_now() < RELEASE_2020_04_19: self.assertEqual(cancellation["status"], "active") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) else: self.assertEqual(cancellation["status"], "draft") activate_cancellation_after_2020_04_19(self, cancellation["id"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "cancelled")
def cancel_tender(self): cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "tender", }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") if get_now() < RELEASE_2020_04_19: self.assertEqual(cancellation["status"], "active") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) else: activate_cancellation_with_complaints_after_2020_04_19( self, cancellation["id"]) # Check tender response = self.app.get("/tenders/{}?acc_token={}".format( self.tender_id, self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "cancelled") # Check lots response = self.app.get("/tenders/{}/lots?acc_token={}".format( self.tender_id, self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"][0]["status"], "active") self.assertEqual(response.json["data"][1]["status"], "active")
def check_status(request): tender = request.validated["tender"] now = get_now() check_complaint_statuses_at_complaint_period_end(tender, now) check_cancellation_status(request) if cancellation_block_tender(tender): return for award in tender.awards: if award.status == "active" and not any( [i.awardID == award.id for i in tender.contracts]): add_contract(request, award, now) add_next_award(request) if (not tender.lots and tender.status == "active.tendering" and tender.tenderPeriod.endDate <= now and not has_unanswered_complaints(tender) and not has_unanswered_questions(tender)): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info( "Switched tender {} to {}".format(tender["id"], "active.auction"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_active.auction"}), ) tender.status = "active.auction" check_bids(request) if tender.numberOfBids < 2 and tender.auctionPeriod: tender.auctionPeriod.startDate = None return elif (tender.lots and tender.status == "active.tendering" and tender.tenderPeriod.endDate <= now and not has_unanswered_complaints(tender) and not has_unanswered_questions(tender)): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info( "Switched tender {} to {}".format(tender["id"], "active.auction"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_active.auction"}), ) tender.status = "active.auction" check_bids(request) [ setattr(i.auctionPeriod, "startDate", None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod ] return elif not tender.lots and tender.status == "active.awarded": standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in tender.awards if a.complaintPeriod and a.complaintPeriod.endDate ] if not standStillEnds: return standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i["status"] in tender.block_complaint_status for i in tender.complaints ]) pending_awards_complaints = any([ i["status"] in tender.block_complaint_status for a in tender.awards for i in a.complaints ]) awarded = any([i["status"] == "active" for i in tender.awards]) if not pending_complaints and not pending_awards_complaints and not awarded: LOGGER.info( "Switched tender {} to {}".format(tender.id, "unsuccessful"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_unsuccessful"}), ) check_tender_status(request) return elif tender.lots and tender.status in [ "active.qualification", "active.awarded" ]: if any([ i["status"] in tender.block_complaint_status and i.relatedLot is None for i in tender.complaints ]): return for lot in tender.lots: if lot["status"] != "active": continue lot_awards = [i for i in tender.awards if i.lotID == lot.id] standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in lot_awards if a.complaintPeriod and a.complaintPeriod.endDate ] if not standStillEnds: continue standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i["status"] in tender.block_complaint_status and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i["status"] in tender.block_complaint_status for a in lot_awards for i in a.complaints ]) awarded = any([i["status"] == "active" for i in lot_awards]) if not pending_complaints and not pending_awards_complaints and not awarded: 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"]}), ) check_tender_status(request)
def patch_tender_cancellation_2020_04_19(self): reasonType_choices = self.valid_reasonType_choices cancellation = dict(**test_cancellation) cancellation.update({"reasonType": reasonType_choices[0]}) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] cancellation_id = cancellation["id"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn("date", cancellation) self.assertEqual(cancellation["reasonType"], reasonType_choices[0]) self.assertEqual(cancellation["status"], "draft") self.assertIn(cancellation_id, response.headers["Location"]) response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "pending" }}, status=422) self.assertEqual(response.status, "422 Unprocessable Entity") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Fields reason, cancellationOf and documents must be filled for switch cancellation to pending status", "location": "body", "name": "data", }]) response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "active" }}, status=422) self.assertEqual(response.status, "422 Unprocessable Entity") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Cancellation can't be updated from draft to active status", "location": "body", "name": "data", }]) response = self.app.post( "/tenders/{}/cancellations/{}/documents?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), upload_files=[("file", "name.doc", b"content")], ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") request_path = "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token) response = self.app.patch_json( request_path, {"data": { "status": "pending" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["status"], "pending") response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "draft" }}, status=422) self.assertEqual(response.status, "422 Unprocessable Entity") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Cancellation can't be updated from pending to draft status", "location": "body", "name": "data", }]) response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "active" }}, status=422) self.assertEqual(response.status, "422 Unprocessable Entity") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Cancellation can't be updated from pending to active status", "location": "body", "name": "data", }]) response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "unsuccessful" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "unsuccessful") response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": None }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") # self.assertEqual(response.json["data"]["status"], "unsuccessful") response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "pending" }}, status=422) self.assertEqual(response.status, "422 Unprocessable Entity") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Cancellation can't be updated from unsuccessful to pending status", "location": "body", "name": "data", }]) cancellation = dict(**test_cancellation) cancellation.update({"reasonType": reasonType_choices[1]}) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] cancellation_id = cancellation["id"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn("date", cancellation) self.assertEqual(cancellation["reasonType"], reasonType_choices[1]) self.assertEqual(cancellation["status"], "draft") self.assertIn(cancellation_id, response.headers["Location"]) response = self.app.post( "/tenders/{}/cancellations/{}/documents?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), upload_files=[("file", "name.doc", b"content")], ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "pending" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["status"], "pending") with patch("openprocurement.tender.core.validation.get_now", return_value=get_now() + timedelta(days=20)) as mock_date: response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "active" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "active") response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, cancellation_id, self.tender_token), {"data": { "status": "pending" }}, status=403) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"], [{ "description": "Can't update tender in current (cancelled) status", "location": "body", "name": "data", }])
def lot_patch_tender_question(self): response = self.app.get("/tenders/{}".format(self.tender_id)) tender = response.json["data"] response = self.app.post_json( "/tenders/{}/questions".format(self.tender_id, self.tender_token), { "data": { "title": "question title", "description": "question description", "questionOf": "lot", "relatedItem": self.initial_lots[0]["id"], "author": self.author_data, } }, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") question = response.json["data"] without_complaints = [ "reporting", "belowThreshold", "closeFrameworkAgreementSelectionUA" ] if RELEASE_2020_04_19 > get_now( ) or tender["procurementMethodType"] in without_complaints: # For procedures: openua, openuadefense, openeu, negotiation, negotiation.quick, esco, copetitivedialogue, cfaua # validation after RELEASE_2020_04_19 is useless, because enquiryPeriod ended before complaintPeriod cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.initial_lots[0]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") cancellation_id = response.json["data"]["id"] if RELEASE_2020_04_19 < get_now(): activate_cancellation_after_2020_04_19(self, cancellation_id) response = self.app.patch_json( "/tenders/{}/questions/{}?acc_token={}".format( self.tender_id, question["id"], self.tender_token), {"data": { "answer": "answer" }}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update question only in active lot status") response = self.app.post_json( "/tenders/{}/questions".format(self.tender_id, self.tender_token), { "data": { "title": "question title", "description": "question description", "questionOf": "lot", "relatedItem": self.initial_lots[1]["id"], "author": self.author_data, } }, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") question = response.json["data"] response = self.app.patch_json( "/tenders/{}/questions/{}?acc_token={}".format(self.tender_id, question["id"], self.tender_token), {"data": { "answer": "answer" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["answer"], "answer") self.assertIn("dateAnswered", response.json["data"]) response = self.app.get("/tenders/{}/questions/{}?acc_token={}".format( self.tender_id, question["id"], self.tender_token)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["answer"], "answer") self.assertIn("dateAnswered", response.json["data"])
def check_status(request): tender = request.validated['tender'] now = get_now() for award in tender.awards: if award.status == 'active' and not any([i.awardID == award.id for i in tender.contracts]): tender.contracts.append(type(tender).contracts.model_class({ 'awardID': award.id, 'suppliers': award.suppliers, 'value': award.value, 'date': now, 'items': [i for i in tender.items if i.relatedLot == award.lotID], 'contractID': '{}-{}{}'.format(tender.tenderID, request.registry.server_id, len(tender.contracts) + 1)})) add_next_award(request) if not tender.lots and tender.status == 'active.tendering' and tender.tenderPeriod.endDate <= now and \ not has_unanswered_complaints(tender) and not has_unanswered_questions(tender): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info('Switched tender {} to {}'.format(tender['id'], 'active.auction'), extra=context_unpack(request, {'MESSAGE_ID': 'switched_tender_active.auction'})) tender.status = 'active.auction' check_bids(request) if tender.numberOfBids < 2 and tender.auctionPeriod: tender.auctionPeriod.startDate = None return elif tender.lots and tender.status == 'active.tendering' and tender.tenderPeriod.endDate <= now and \ not has_unanswered_complaints(tender) and not has_unanswered_questions(tender): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info('Switched tender {} to {}'.format(tender['id'], 'active.auction'), extra=context_unpack(request, {'MESSAGE_ID': 'switched_tender_active.auction'})) tender.status = 'active.auction' check_bids(request) [setattr(i.auctionPeriod, 'startDate', None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod] return elif not tender.lots and tender.status == 'active.awarded': standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in tender.awards if a.complaintPeriod.endDate ] if not standStillEnds: return standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i['status'] in tender.block_complaint_status for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in tender.block_complaint_status for a in tender.awards for i in a.complaints ]) awarded = any([ i['status'] == 'active' for i in tender.awards ]) if not pending_complaints and not pending_awards_complaints and not awarded: LOGGER.info('Switched tender {} to {}'.format(tender.id, 'unsuccessful'), extra=context_unpack(request, {'MESSAGE_ID': 'switched_tender_unsuccessful'})) check_tender_status(request) return elif tender.lots and tender.status in ['active.qualification', 'active.awarded']: if any([i['status'] in tender.block_complaint_status and i.relatedLot is None for i in tender.complaints]): return for lot in tender.lots: if lot['status'] != 'active': continue lot_awards = [i for i in tender.awards if i.lotID == lot.id] standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in lot_awards if a.complaintPeriod.endDate ] if not standStillEnds: continue standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i['status'] in tender.block_complaint_status and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in tender.block_complaint_status for a in lot_awards for i in a.complaints ]) awarded = any([ i['status'] == 'active' for i in lot_awards ]) if not pending_complaints and not pending_awards_complaints and not awarded: 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']})) check_tender_status(request)
def create_tender_lots_cancellation(self): lot_id = self.initial_lots[0]["id"] cancellation = dict(**test_cancellation) cancellation.update({"cancellationOf": "lot", "relatedLot": lot_id}) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["lots"][0]["status"], "active") self.assertEqual(response.json["data"]["status"], "active") cancellation = dict(**test_cancellation) cancellation.update({ "cancellationOf": "lot", "relatedLot": lot_id, "status": "active" }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) if RELEASE_2020_04_19 > get_now(): self.assertEqual(cancellation["status"], "active") else: activate_cancellation_after_2020_04_19(self, cancellation["id"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["lots"][0]["status"], "cancelled") self.assertNotEqual(response.json["data"]["status"], "cancelled") cancellation = dict(**test_cancellation) cancellation.update({ "cancellationOf": "lot", "relatedLot": lot_id, "status": "active" }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can perform cancellation only in active lot status") cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.initial_lots[1]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) if RELEASE_2020_04_19 > get_now(): self.assertEqual(cancellation["status"], "active") else: activate_cancellation_after_2020_04_19(self, cancellation["id"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["lots"][0]["status"], "cancelled") self.assertEqual(response.json["data"]["lots"][1]["status"], "cancelled") self.assertEqual(response.json["data"]["status"], "cancelled")
def create_tender_cancellation(self): response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": test_cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] self.assertEqual(cancellation["reason"], "cancellation reason") self.assertIn("id", cancellation) self.assertIn(cancellation["id"], response.headers["Location"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "active") response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": test_cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") first_cancellation = response.json["data"] self.assertEqual(first_cancellation["reason"], "cancellation reason") response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": test_cancellation}, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") second_cancellation = response.json["data"] self.assertEqual(second_cancellation["reason"], "cancellation reason") if get_now() < RELEASE_2020_04_19: response = self.app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( self.tender_id, second_cancellation["id"], self.tender_token), {"data": { "status": "active" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "active") else: activate_cancellation_after_2020_04_19(self, second_cancellation["id"]) response = self.app.get("/tenders/{}".format(self.tender_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "cancelled") response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": test_cancellation}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can't update tender in current (cancelled) status")
"relatedLot": lot["id"], }) response = self.app.post_json( "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token), {"data": cancellation}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can perform cancellation only in active lot status") @patch("openprocurement.tender.core.models.RELEASE_2020_04_19", get_now() - timedelta(days=1)) @patch("openprocurement.tender.core.views.cancellation.RELEASE_2020_04_19", get_now() - timedelta(days=1)) def create_tender_cancellation_2020_04_19(self): reasonType_choices = self.valid_reasonType_choices request_path = "/tenders/{}/cancellations?acc_token={}".format( self.tender_id, self.tender_token) cancellation = dict(**test_cancellation) cancellation.update({"reasonType": reasonType_choices[0]}) response = self.app.post_json(request_path, {"data": cancellation}) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") cancellation = response.json["data"] cancellation_id = cancellation["id"] self.assertEqual(cancellation["reason"], "cancellation reason")
def check_status(request): tender = request.validated['tender'] now = get_now() for award in tender.awards: if award.status == 'active' and not any( [i.awardID == award.id for i in tender.contracts]): tender.contracts.append( type(tender).contracts.model_class({ 'awardID': award.id, 'suppliers': award.suppliers, 'value': award.value, 'date': now, 'items': [i for i in tender.items if i.relatedLot == award.lotID], 'contractID': '{}-{}{}'.format(tender.tenderID, request.registry.server_id, len(tender.contracts) + 1) })) add_next_award(request) if not tender.lots and tender.status == 'active.tendering' and tender.tenderPeriod.endDate <= now and \ not has_unanswered_complaints(tender) and not has_unanswered_questions(tender): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info('Switched tender {} to {}'.format(tender['id'], 'active.auction'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_active.auction'})) tender.status = 'active.auction' check_bids(request) if tender.numberOfBids < 2 and tender.auctionPeriod: tender.auctionPeriod.startDate = None return elif tender.lots and tender.status == 'active.tendering' and tender.tenderPeriod.endDate <= now and \ not has_unanswered_complaints(tender) and not has_unanswered_questions(tender): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info('Switched tender {} to {}'.format(tender['id'], 'active.auction'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_active.auction'})) tender.status = 'active.auction' check_bids(request) [ setattr(i.auctionPeriod, 'startDate', None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod ] return elif not tender.lots and tender.status == 'active.awarded': standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in tender.awards if a.complaintPeriod.endDate ] if not standStillEnds: return standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i['status'] in tender.block_complaint_status for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in tender.block_complaint_status for a in tender.awards for i in a.complaints ]) awarded = any([i['status'] == 'active' for i in tender.awards]) if not pending_complaints and not pending_awards_complaints and not awarded: LOGGER.info( 'Switched tender {} to {}'.format(tender.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_unsuccessful'})) check_tender_status(request) return elif tender.lots and tender.status in [ 'active.qualification', 'active.awarded' ]: if any([ i['status'] in tender.block_complaint_status and i.relatedLot is None for i in tender.complaints ]): return for lot in tender.lots: if lot['status'] != 'active': continue lot_awards = [i for i in tender.awards if i.lotID == lot.id] standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in lot_awards if a.complaintPeriod.endDate ] if not standStillEnds: continue standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i['status'] in tender.block_complaint_status and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in tender.block_complaint_status for a in lot_awards for i in a.complaints ]) awarded = any([i['status'] == 'active' for i in lot_awards]) if not pending_complaints and not pending_awards_complaints and not awarded: 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']})) check_tender_status(request)
def calculate_business_date(date_obj, timedelta_obj, context=None, working_days=False): if (context.get('revisions')[0].date if context and context.get('revisions') else get_now()) < CALCULATE_BUSINESS_DATE_FROM: return calculate_business_date_base(date_obj, timedelta_obj, context, working_days) if context and 'procurementMethodDetails' in context and context['procurementMethodDetails']: re_obj = ACCELERATOR_RE.search(context['procurementMethodDetails']) if re_obj and 'accelerator' in re_obj.groupdict(): return date_obj + (timedelta_obj / int(re_obj.groupdict()['accelerator'])) if working_days: if timedelta_obj > timedelta(): if date_obj.weekday() in [5, 6] and WORKING_DAYS.get(date_obj.date().isoformat(), True) or WORKING_DAYS.get(date_obj.date().isoformat(), False): date_obj = datetime.combine(date_obj.date(), time(0, tzinfo=date_obj.tzinfo)) + timedelta(1) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get(date_obj.date().isoformat(), True) or WORKING_DAYS.get(date_obj.date().isoformat(), False): date_obj += timedelta(1) else: if date_obj.weekday() in [5, 6] and WORKING_DAYS.get(date_obj.date().isoformat(), True) or WORKING_DAYS.get(date_obj.date().isoformat(), False): date_obj = datetime.combine(date_obj.date(), time(0, tzinfo=date_obj.tzinfo)) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get(date_obj.date().isoformat(), True) or WORKING_DAYS.get(date_obj.date().isoformat(), False): date_obj -= timedelta(1) date_obj += timedelta(1) for _ in xrange(abs(timedelta_obj.days)): date_obj += timedelta(1) if timedelta_obj > timedelta() else -timedelta(1) while date_obj.weekday() in [5, 6] and WORKING_DAYS.get(date_obj.date().isoformat(), True) or WORKING_DAYS.get(date_obj.date().isoformat(), False): date_obj += timedelta(1) if timedelta_obj > timedelta() else -timedelta(1) return date_obj return date_obj + timedelta_obj
def tender_init_handler_reporting(event): """ initialization handler for tenders 'reporting'""" event.tender.date = get_now()
def tender_init_handler_base(event): tender = event.tender tender.date = get_now() if tender.lots: for lot in tender.lots: lot.date = get_now()
def add_next_award(request, reverse=False, awarding_criteria_key="amount"): """Adding next award. reverse and awarding_criteria_key are deprecated, since we can get them from request :param request: The pyramid request object. :param reverse: Is used for sorting bids to generate award. By default (reverse = False) awards are generated from lower to higher by value.amount When reverse is set to True awards are generated from higher to lower by value.amount """ tender = request.validated["tender"] now = get_now() if not tender.awardPeriod: tender.awardPeriod = type(tender).awardPeriod({}) if not tender.awardPeriod.startDate: tender.awardPeriod.startDate = now if tender.lots: statuses = set() for lot in tender.lots: if lot.status != "active": continue lot_awards = [i for i in tender.awards if i.lotID == lot.id] if lot_awards and lot_awards[-1].status in ["pending", "active"]: statuses.add( lot_awards[-1].status if lot_awards else "unsuccessful") continue all_bids = prepare_bids_for_awarding(tender, tender.bids, lot_id=lot.id) if all_bids: bids = exclude_unsuccessful_awarded_bids(tender, all_bids, lot_id=lot.id) if bids: tender.append_award(bids[0], all_bids, lot_id=lot.id) request.response.headers["Location"] = request.route_url( "{}:Tender Awards".format( tender.procurementMethodType), tender_id=tender.id, award_id=tender.awards[-1]["id"]) statuses.add("pending") else: statuses.add("unsuccessful") else: lot.status = "unsuccessful" statuses.add("unsuccessful") if statuses.difference(set(["unsuccessful", "active"])): tender.awardPeriod.endDate = None tender.status = "active.qualification" else: tender.awardPeriod.endDate = now tender.status = "active.awarded" first_revision_date = get_first_revision_date(tender) new_defence_complaints = NEW_DEFENSE_COMPLAINTS_FROM < first_revision_date < NEW_DEFENSE_COMPLAINTS_TO if new_defence_complaints and statuses == set(["unsuccessful"]): for lot in tender.lots: if lot.status != "active": continue pending_complaints = any([ i["status"] in tender.block_complaint_status and i.relatedLot == lot.id for i in tender.complaints ]) lot_awards = [ i for i in tender.awards if i.lotID == lot.id ] if not lot_awards: continue awards_no_complaint_periods = all([ not a.complaintPeriod for a in lot_awards if a["status"] == "unsuccessful" ]) if (not pending_complaints and awards_no_complaint_periods): 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" lot_statuses = set([lot.status for lot in tender.lots]) if not lot_statuses.difference( set(["unsuccessful", "cancelled"])): LOGGER.info( "Switched tender {} to {}".format( tender.id, "unsuccessful"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_unsuccessful"}), ) tender.status = "unsuccessful" else: if not tender.awards or tender.awards[-1].status not in [ "pending", "active" ]: all_bids = prepare_bids_for_awarding(tender, tender.bids, lot_id=None) bids = exclude_unsuccessful_awarded_bids(tender, all_bids, lot_id=None) if bids: tender.append_award(bids[0], all_bids) request.response.headers["Location"] = request.route_url( "{}:Tender Awards".format(tender.procurementMethodType), tender_id=tender.id, award_id=tender.awards[-1]["id"]) if tender.awards[-1].status == "pending": tender.awardPeriod.endDate = None tender.status = "active.qualification" else: tender.awardPeriod.endDate = now tender.status = "active.awarded" first_revision_date = get_first_revision_date(tender) new_defence_complaints = NEW_DEFENSE_COMPLAINTS_FROM < first_revision_date < NEW_DEFENSE_COMPLAINTS_TO if new_defence_complaints: pending_complaints = any([ i["status"] in tender.block_complaint_status for i in tender.complaints ]) last_award_unsuccessful = tender.awards[ -1].status == "unsuccessful" awards_no_complaint_periods = all([ not a.complaintPeriod for a in tender.awards if a["status"] == "unsuccessful" ]) if (not pending_complaints and last_award_unsuccessful and awards_no_complaint_periods): LOGGER.info( "Switched tender {} to {}".format( tender.id, "unsuccessful"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_unsuccessful"}), ) tender.status = "unsuccessful"