def prepare_qualifications(request, bids=[], lotId=None): """ creates Qualification for each Bid """ new_qualifications = [] tender = request.validated['tender'] if not bids: bids = tender.bids if tender.lots: active_lots = [lot.id for lot in tender.lots if lot.status == 'active'] for bid in bids: if bid.status not in ['invalid', 'deleted']: for lotValue in bid.lotValues: if lotValue.status == 'pending' and lotValue.relatedLot in active_lots: if lotId: if lotValue.relatedLot == lotId: qualification = Qualification({'bidID': bid.id, 'status': 'pending', 'lotID': lotId}) qualification.date = get_now() tender.qualifications.append(qualification) new_qualifications.append(qualification.id) else: qualification = Qualification({'bidID': bid.id, 'status': 'pending', 'lotID': lotValue.relatedLot}) qualification.date = get_now() tender.qualifications.append(qualification) new_qualifications.append(qualification.id) else: for bid in bids: if bid.status == 'pending': qualification = Qualification({'bidID': bid.id, 'status': 'pending'}) qualification.date = get_now() tender.qualifications.append(qualification) new_qualifications.append(qualification.id) return new_qualifications
def patch(self): """Update of proposal Example request to change bid proposal: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/bids/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ if self.request.validated['tender_status'] != 'active.tendering': self.request.errors.add('body', 'data', 'Can\'t update bid in current ({}) tender status'.format(self.request.validated['tender_status'])) self.request.errors.status = 403 return if self.request.authenticated_role != 'Administrator': bid_status_to = self.request.validated['data'].get("status", self.request.context.status) if bid_status_to != 'pending': self.request.errors.add('body', 'bid', 'Can\'t update bid to ({}) status'.format(bid_status_to)) self.request.errors.status = 403 return value = self.request.validated['data'].get("value") and self.request.validated['data']["value"].get("amount") if value and value != self.request.context.get("value", {}).get("amount"): self.request.validated['data']['date'] = get_now().isoformat() if self.request.context.lotValues: lotValues = dict([(i.relatedLot, i.value.amount) for i in self.request.context.lotValues]) for lotvalue in self.request.validated['data'].get("lotValues", []): if lotvalue['relatedLot'] in lotValues and lotvalue.get("value", {}).get("amount") != lotValues[lotvalue['relatedLot']]: lotvalue['date'] = get_now().isoformat() self.request.validated['tender'].modified = False if apply_patch(self.request, src=self.request.context.serialize()): self.LOGGER.info('Updated tender bid {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_bid_patch'})) return {'data': self.request.context.serialize("view")}
def collection_post(self): """Post a complaint """ auction = self.context if auction.status not in ['active.enquiries', 'active.tendering']: self.request.errors.add('body', 'data', 'Can\'t add complaint in current ({}) auction status'.format(auction.status)) self.request.errors.status = 403 return complaint = self.request.validated['complaint'] complaint.date = get_now() if complaint.status == 'claim': complaint.dateSubmitted = get_now() else: complaint.status = 'draft' complaint.complaintID = '{}.{}{}'.format(auction.auctionID, self.server_id, sum([len(i.complaints) for i in auction.awards], len(auction.complaints)) + 1) set_ownership(complaint, self.request) auction.complaints.append(complaint) if save_auction(self.request): self.LOGGER.info('Created auction complaint {}'.format(complaint.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'auction_complaint_create'}, {'complaint_id': complaint.id})) self.request.response.status = 201 route = self.request.matched_route.name.replace("collection_", "") self.request.response.headers['Location'] = self.request.current_route_url(_route_name=route, complaint_id=complaint.id, _query={}) return { 'data': complaint.serialize(auction.status), 'access': { 'token': complaint.owner_token } }
def collection_post(self): """Post a complaint for award """ tender = self.request.validated['tender'] if tender.status != 'active': self.request.errors.add('body', 'data', 'Can\'t add complaint in current ({}) tender status'.format(tender.status)) self.request.errors.status = 403 return if self.context.complaintPeriod and \ (self.context.complaintPeriod.startDate and self.context.complaintPeriod.startDate > get_now() or self.context.complaintPeriod.endDate and self.context.complaintPeriod.endDate < get_now()): self.request.errors.add('body', 'data', 'Can add complaint only in complaintPeriod') self.request.errors.status = 403 return complaint = self.request.validated['complaint'] complaint.date = get_now() complaint.type = 'complaint' if complaint.status == 'pending': complaint.dateSubmitted = get_now() else: complaint.status = 'draft' complaint.complaintID = '{}.{}{}'.format(tender.tenderID, self.server_id, sum([len(i.complaints) for i in tender.awards], 1)) set_ownership(complaint, self.request) self.context.complaints.append(complaint) if save_tender(self.request): self.LOGGER.info('Created tender award complaint {}'.format(complaint.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_award_complaint_create'}, {'complaint_id': complaint.id})) self.request.response.status = 201 self.request.response.headers['Location'] = self.request.route_url('Tender negotiation Award Complaints', tender_id=tender.id, award_id=self.request.validated['award_id'], complaint_id=complaint['id']) return { 'data': complaint.serialize("view"), 'access': { 'token': complaint.owner_token } }
def test_empty_listing(self): before = get_now().isoformat() response = self.app.get('/tenders') after = get_now().isoformat() self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data'], []) self.assertFalse('{\n "' in response.body) self.assertFalse('callback({' in response.body) self.assertTrue(before < response.json['next_page']['offset'] < after) response = self.app.get('/tenders?opt_jsonp=callback') self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/javascript') self.assertFalse('{\n "' in response.body) self.assertIn('callback({', response.body) response = self.app.get('/tenders?opt_pretty=1') self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertIn('{\n "', response.body) self.assertFalse('callback({' in response.body) response = self.app.get('/tenders?opt_jsonp=callback&opt_pretty=1') self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/javascript') self.assertIn('{\n "', response.body) self.assertIn('callback({', response.body) response = self.app.get('/tenders?offset={}&descending=1&limit=10'.format(before)) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data'], []) self.assertIn('descending=1', response.json['next_page']['uri']) self.assertIn('limit=10', response.json['next_page']['uri'])
def patch(self): """Update of contract """ if self.request.validated['tender_status'] not in ['active']: self.request.errors.add('body', 'data', 'Can\'t update contract in current ({}) tender status'.format(self.request.validated['tender_status'])) self.request.errors.status = 403 return if self.request.context.status == 'cancelled': self.request.errors.add('body', 'data', 'Can\'t update contract in current ({}) status'.format(self.request.context.status)) self.request.errors.status = 403 return data = self.request.validated['data'] if self.request.context.status != 'active' and 'status' in data and data['status'] == 'active': tender = self.request.validated['tender'] award = [a for a in tender.awards if a.id == self.request.context.awardID][0] stand_still_end = award.complaintPeriod.endDate if stand_still_end > get_now(): self.request.errors.add('body', 'data', 'Can\'t sign contract before stand-still period end ({})'.format(stand_still_end.isoformat())) self.request.errors.status = 403 return if any([ i.status in tender.block_complaint_status and a.lotID == award.lotID for a in tender.awards for i in a.complaints ]): self.request.errors.add('body', 'data', 'Can\'t sign contract before reviewing all complaints') self.request.errors.status = 403 return if data['value']: for ro_attr in ('valueAddedTaxIncluded', 'currency'): if data['value'][ro_attr] != getattr(self.context.value, ro_attr): self.request.errors.add('body', 'data', 'Can\'t update {} for contract value'.format(ro_attr)) self.request.errors.status = 403 return award = [a for a in self.request.validated['tender'].awards if a.id == self.request.context.awardID][0] if data['value']['amount'] > award.value.amount: self.request.errors.add('body', 'data', 'Value amount should be less or equal to awarded amount ({})'.format(award.value.amount)) self.request.errors.status = 403 return contract_status = self.request.context.status apply_patch(self.request, save=False, src=self.request.context.serialize()) self.request.context.date = get_now() if contract_status != self.request.context.status and contract_status != 'pending' and self.request.context.status != 'active': self.request.errors.add('body', 'data', 'Can\'t update contract status') self.request.errors.status = 403 return if self.request.context.status == 'active' and not self.request.context.dateSigned: self.request.context.dateSigned = get_now() check_tender_negotiation_status(self.request) if save_tender(self.request): self.LOGGER.info('Updated tender contract {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_contract_patch'})) return {'data': self.request.context.serialize()}
def initialize(self): if not self.enquiryPeriod.startDate: self.enquiryPeriod.startDate = get_now() if not self.tenderPeriod.startDate: self.tenderPeriod.startDate = self.enquiryPeriod.endDate now = get_now() self.date = now if self.lots: for lot in self.lots: lot.date = now
def patch(self): """Post a complaint resolution for award """ tender = self.request.validated["tender"] if tender.status not in ["active.qualification", "active.awarded"]: self.request.errors.add( "body", "data", "Can't update complaint in current ({}) tender status".format(tender.status) ) self.request.errors.status = 403 return complaint = self.request.context if complaint.status != "pending": self.request.errors.add( "body", "data", "Can't update complaint in current ({}) status".format(complaint.status) ) self.request.errors.status = 403 return apply_patch(self.request, save=False, src=complaint.serialize()) if complaint.status == "cancelled": self.request.errors.add("body", "data", "Can't cancel complaint") self.request.errors.status = 403 return if complaint.status == "resolved": award = self.request.validated["award"] if tender.status == "active.awarded": tender.status = "active.qualification" tender.awardPeriod.endDate = None if award.status == "unsuccessful": for i in tender.awards[tender.awards.index(award) :]: i.complaintPeriod.endDate = get_now() + STAND_STILL_TIME i.status = "cancelled" for j in i.complaints: if j.status == "pending": j.status = "cancelled" for i in award.contracts: i.status = "cancelled" award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME award.status = "cancelled" add_next_award(self.request) elif complaint.status in ["declined", "invalid"] and tender.status == "active.awarded": pending_complaints = [i for i in tender.complaints if i.status == "pending"] pending_awards_complaints = [i for a in tender.awards for i in a.complaints if i.status == "pending"] stand_still_ends = [a.complaintPeriod.endDate for a in tender.awards if a.complaintPeriod.endDate] stand_still_end = max(stand_still_ends) if stand_still_ends else get_now() stand_still_time_expired = stand_still_end < get_now() if not pending_complaints and not pending_awards_complaints and stand_still_time_expired: active_awards = [a for a in tender.awards if a.status == "active"] if not active_awards: tender.status = "unsuccessful" if save_tender(self.request): LOGGER.info( "Updated tender award complaint {}".format(self.request.context.id), extra={"MESSAGE_ID": "tender_award_complaint_patch"}, ) return {"data": complaint.serialize("view")}
def collection_post(self): """Post a complaint """ tender = self.request.validated["tender"] if tender.status not in ["active.pre-qualification.stand-still"]: self.request.errors.add( "body", "data", "Can't add complaint in current ({}) tender status".format(tender.status) ) self.request.errors.status = 403 return if any([i.status != "active" for i in tender.lots if i.id == self.context.lotID]): self.request.errors.add("body", "data", "Can add complaint only in active lot status") self.request.errors.status = 403 return if tender.qualificationPeriod and ( tender.qualificationPeriod.startDate and tender.qualificationPeriod.startDate > get_now() or tender.qualificationPeriod.endDate and tender.qualificationPeriod.endDate < get_now() ): self.request.errors.add("body", "data", "Can add complaint only in qualificationPeriod") self.request.errors.status = 403 return complaint = self.request.validated["complaint"] complaint.relatedLot = self.context.lotID complaint.date = get_now() if complaint.status == "claim": complaint.dateSubmitted = get_now() elif complaint.status == "pending": complaint.type = "complaint" complaint.dateSubmitted = get_now() else: complaint.status = "draft" complaint.complaintID = "{}.{}{}".format(tender.tenderID, self.server_id, self.complaints_len(tender) + 1) set_ownership(complaint, self.request) self.context.complaints.append(complaint) if save_tender(self.request): self.LOGGER.info( "Created tender qualification complaint {}".format(complaint.id), extra=context_unpack( self.request, {"MESSAGE_ID": "tender_qualification_complaint_create"}, {"complaint_id": complaint.id}, ), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "Tender EU Qualification Complaints", tender_id=tender.id, qualification_id=self.request.validated["qualification_id"], complaint_id=complaint["id"], ) return {"data": complaint.serialize("view"), "access": {"token": complaint.owner_token}}
def patch(self): """Post a complaint resolution """ tender = self.request.validated["tender"] if tender.status not in [ "active.enquiries", "active.tendering", "active.auction", "active.qualification", "active.awarded", ]: self.request.errors.add( "body", "data", "Can't update complaint in current ({}) tender status".format(tender.status) ) self.request.errors.status = 403 return if self.request.context.status != "pending": self.request.errors.add( "body", "data", "Can't update complaint in current ({}) status".format(self.request.context.status) ) self.request.errors.status = 403 return apply_patch(self.request, save=False, src=self.request.context.serialize()) if self.request.context.status == "cancelled": self.request.errors.add("body", "data", "Can't cancel complaint") self.request.errors.status = 403 return if self.request.context.status == "resolved" and tender.status != "active.enquiries": for i in tender.complaints: if i.status == "pending": i.status = "cancelled" tender.status = "cancelled" elif self.request.context.status in ["declined", "invalid"] and tender.status == "active.awarded": pending_complaints = [i for i in tender.complaints if i.status == "pending"] pending_awards_complaints = [i for a in tender.awards for i in a.complaints if i.status == "pending"] stand_still_ends = [a.complaintPeriod.endDate for a in tender.awards if a.complaintPeriod.endDate] stand_still_end = max(stand_still_ends) if stand_still_ends else get_now() stand_still_time_expired = stand_still_end < get_now() if not pending_complaints and not pending_awards_complaints and stand_still_time_expired: active_awards = [a for a in tender.awards if a.status == "active"] if not active_awards: tender.status = "unsuccessful" if save_tender(self.request): LOGGER.info( "Updated tender complaint {}".format(self.request.context.id), extra={"MESSAGE_ID": "tender_complaint_patch"}, ) return {"data": self.request.context.serialize("view")}
def patch(self): """Update of contract """ if self.request.validated['tender_status'] not in ['active.qualification', 'active.awarded']: self.request.errors.add('body', 'data', 'Can\'t update contract in current ({}) tender status'.format(self.request.validated['tender_status'])) self.request.errors.status = 403 return tender = self.request.validated['tender'] if any([i.status != 'active' for i in tender.lots if i.id in [a.lotID for a in tender.awards if a.id == self.request.context.awardID]]): self.request.errors.add('body', 'data', 'Can update contract only in active lot status') self.request.errors.status = 403 return data = self.request.validated['data'] if self.request.context.status != 'active' and 'status' in data and data['status'] == 'active': award = [a for a in tender.awards if a.id == self.request.context.awardID][0] stand_still_end = award.complaintPeriod.endDate if stand_still_end > get_now(): self.request.errors.add('body', 'data', 'Can\'t sign contract before stand-still period end ({})'.format(stand_still_end.isoformat())) self.request.errors.status = 403 return pending_complaints = [ i for i in tender.complaints if i.status in ['claim', 'answered', 'pending'] and i.relatedLot in [None, award.lotID] ] pending_awards_complaints = [ i for a in tender.awards for i in a.complaints if i.status in ['claim', 'answered', 'pending'] and a.lotID == award.lotID ] if pending_complaints or pending_awards_complaints: self.request.errors.add('body', 'data', 'Can\'t sign contract before reviewing all complaints') self.request.errors.status = 403 return contract_status = self.request.context.status apply_patch(self.request, save=False, src=self.request.context.serialize()) if contract_status != self.request.context.status and (contract_status != 'pending' or self.request.context.status != 'active'): self.request.errors.add('body', 'data', 'Can\'t update contract status') self.request.errors.status = 403 return if self.request.context.status == 'active' and not self.request.context.dateSigned: self.request.context.dateSigned = get_now() check_tender_status(self.request) if save_tender(self.request): LOGGER.info('Updated tender contract {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_contract_patch'})) return {'data': self.request.context.serialize()}
def from18to19(db): def update_documents_type(item, changed): for document in item.get('documents', []): if document.get('documentType') == 'contractAnnexes': document['documentType'] = 'contractAnnexe' changed = True return changed results = db.iterview('tenders/all', 2 ** 10, include_docs=True) docs = [] for i in results: doc = i.doc changed = update_documents_type(doc, False) for item in ('bids', 'complaints', 'cancellations', 'contracts', 'awards'): for item_val in doc.get(item, []): changed = update_documents_type(item_val, changed) if item == 'awards': for complaint in item_val.get('complaints', []): changed = update_documents_type(complaint, changed) if changed: doc['dateModified'] = get_now().isoformat() docs.append(doc) if len(docs) >= 2 ** 7: db.update(docs) docs = [] if docs: db.update(docs)
def from14to15(db): results = db.view('tenders/all', include_docs=True) for i in results: doc = i.doc changed = False if not doc.get('title'): doc['title'] = doc["items"][0]['description'] changed = True for item in doc["items"]: if not item.get('classification'): changed = True item['classification'] = { "scheme": u"CPV", "id": u"41110000-3", "description": u"Drinking water" } if not item.get('additionalClassifications'): changed = True item['additionalClassifications'] = [ { "scheme": u"ДКПП", "id": u"36.00.11-00.00", "description": u"Вода питна" } ] if changed: doc['dateModified'] = get_now().isoformat() db.save(doc)
def from13to14(db): results = db.view('tenders/all', include_docs=True) for i in results: doc = i.doc changed = False for i in doc.get('documents', []): changed = fix_rfc2047(i, changed) for c in doc.get('complaints', []): for i in c.get('documents', []): changed = fix_rfc2047(i, changed) for b in doc.get('bids', []): for i in b.get('documents', []): changed = fix_rfc2047(i, changed) for a in doc.get('awards', []): for i in c.get('documents', []): changed = fix_rfc2047(i, changed) for c in a.get('complaints', []): for i in c.get('documents', []): changed = fix_rfc2047(i, changed) for c in a.get('contracts', []): for i in c.get('documents', []): changed = fix_rfc2047(i, changed) if changed: doc['dateModified'] = get_now().isoformat() db.save(doc)
def from10to11(db): results = db.view('tenders/all', include_docs=True) for i in results: doc = i.doc changed = False if doc.get("procuringEntity", {}).get("identifier", {}).get("scheme"): changed = True doc["procuringEntity"]["identifier"]["scheme"] = 'UA-EDR' for j in doc.get('bids', []): for i in j.get('tenderers', []): if i.get("identifier", {}).get("scheme"): changed = True i["identifier"]["scheme"] = 'UA-EDR' for i in doc.get('questions', []): if i.get("author", {}).get("identifier", {}).get("scheme"): changed = True i["author"]["identifier"]["scheme"] = 'UA-EDR' for i in doc.get('complaints', []): if i.get("author", {}).get("identifier", {}).get("scheme"): changed = True i["author"]["identifier"]["scheme"] = 'UA-EDR' for j in doc.get('awards', []): for i in j.get('suppliers', []): if i.get("identifier", {}).get("scheme"): changed = True i["identifier"]["scheme"] = 'UA-EDR' for i in j.get('complaints', []): if i.get("author", {}).get("identifier", {}).get("scheme"): changed = True i["author"]["identifier"]["scheme"] = 'UA-EDR' if changed: doc['dateModified'] = get_now().isoformat() db.save(doc)
def calculate_normalized_date(dt, tender, ceil=False): if (tender.revisions[0].date if tender.revisions else get_now()) > NORMALIZED_COMPLAINT_PERIOD_FROM and \ not (SANDBOX_MODE and tender.procurementMethodDetails): if ceil: return dt.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0) + timedelta(days=1) return dt.astimezone(TZ).replace(hour=0, minute=0, second=0, microsecond=0) return dt
def set_journal_handler(event): request = event.request params = { 'TENDERS_API_VERSION': VERSION, 'TAGS': 'python,api', 'USER': str(request.authenticated_userid or ''), #'ROLE': str(request.authenticated_role), 'CURRENT_URL': request.url, 'CURRENT_PATH': request.path_info, 'REMOTE_ADDR': request.remote_addr or '', 'USER_AGENT': request.user_agent or '', 'REQUEST_METHOD': request.method, 'AWARD_ID': '', 'BID_ID': '', 'COMPLAINT_ID': '', 'CONTRACT_ID': '', 'DOCUMENT_ID': '', 'QUESTION_ID': '', 'TENDER_ID': '', 'TIMESTAMP': get_now().isoformat(), 'REQUEST_ID': request.environ.get('REQUEST_ID', ''), 'CLIENT_REQUEST_ID': request.headers.get('X-Client-Request-ID', ''), } for i in LOGGER.handlers: LOGGER.removeHandler(i) LOGGER.addHandler(JournalHandler(**params))
def test_listing_changes(self): tender = self.create_tender() data = self.db[tender['id']] awards = data['awards'] for i in range(3): award = deepcopy(test_award) award['date'] = get_now().isoformat() award['id'] = uuid4().hex awards.append(award) self.db.save(data) ids = ','.join([i['id'] for i in awards]) response = self.app.get('/tenders/{}/awards'.format(tender['id'])) self.assertTrue(ids.startswith(','.join([i['id'] for i in response.json['data']]))) self.assertEqual(response.status, '200 OK') self.assertEqual(len(response.json['data']), len(awards)) self.assertEqual(set([i['id'] for i in response.json['data']]), set([i['id'] for i in awards])) self.assertEqual(set([i['date'] for i in response.json['data']]), set([i['date'] for i in awards])) self.assertEqual([i['date'] for i in response.json['data']], sorted([i['date'] for i in awards]))
def patch(self): """Update of contract """ if self.request.validated['tender_status'] not in ['active', 'complete']: self.request.errors.add('body', 'data', 'Can\'t update contract in current ({}) tender status'.format(self.request.validated['tender_status'])) self.request.errors.status = 403 return data = self.request.validated['data'] if self.request.context.status == 'cancelled': self.request.errors.add('body', 'data', 'Can\'t update contract in current ({}) status'.format(self.request.context.status)) self.request.errors.status = 403 return if self.request.context.status != 'active' and 'status' in data and data['status'] == 'active': tender = self.request.validated['tender'] award = [a for a in tender.awards if a.id == self.request.context.awardID][0] stand_still_end = award.complaintPeriod.endDate if stand_still_end > get_now(): self.request.errors.add('body', 'data', 'Can\'t sign contract before stand-still period end ({})'.format(stand_still_end.isoformat())) self.request.errors.status = 403 return contract_status = self.request.context.status apply_patch(self.request, save=False, src=self.request.context.serialize()) if contract_status != self.request.context.status and contract_status != 'pending' and self.request.context.status != 'active': self.request.errors.add('body', 'data', 'Can\'t update contract status') self.request.errors.status = 403 return check_tender_status(self.request) if save_tender(self.request): LOGGER.info('Updated tender contract {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_contract_patch'})) return {'data': self.request.context.serialize()}
def test_listing_changes(self): tender = self.create_tender() data = self.db[tender['id']] award = data['awards'][0] award_complaint = award['complaints'][0] award_complaint_documents = award_complaint['documents'] for i in range(3): document = deepcopy(test_document) document['dateModified'] = get_now().isoformat() document['id'] = uuid4().hex award_complaint_documents.append(document) self.db.save(data) ids = ','.join([i['id'] for i in award_complaint_documents]) response = self.app.get('/tenders/{}/awards/{}/complaints/{}/documents'.format(tender['id'], award['id'], award_complaint['id'])) self.assertTrue(ids.startswith(','.join([i['id'] for i in response.json['data']]))) self.assertEqual(response.status, '200 OK') self.assertEqual(len(response.json['data']), len(award_complaint_documents)) self.assertEqual(set([i['id'] for i in response.json['data']]), set([i['id'] for i in award_complaint_documents])) self.assertEqual(set([i['dateModified'] for i in response.json['data']]), set([i['dateModified'] for i in award_complaint_documents])) self.assertEqual([i['dateModified'] for i in response.json['data']], sorted([i['dateModified'] for i in award_complaint_documents]))
def collection_post(self): """Post a complaint """ tender = self.context if tender.status not in ['active.enquiries', 'active.tendering']: self.request.errors.add('body', 'data', 'Can\'t add complaint in current ({}) tender status'.format(tender.status)) self.request.errors.status = 403 return complaint = self.request.validated['complaint'] if complaint.status == 'claim': complaint.dateSubmitted = get_now() else: complaint.status = 'draft' complaint.complaintID = '{}.{}{}'.format(tender.tenderID, self.server_id, sum([len(i.complaints) for i in tender.awards], len(tender.complaints)) + 1) set_ownership(complaint, self.request) tender.complaints.append(complaint) if save_tender(self.request): self.LOGGER.info('Created tender complaint {}'.format(complaint.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_complaint_create'}, {'complaint_id': complaint.id})) self.request.response.status = 201 self.request.response.headers['Location'] = self.request.route_url('Tender Complaints', tender_id=tender.id, complaint_id=complaint.id) return { 'data': complaint.serialize(tender.status), 'access': { 'token': complaint.owner_token } }
def from18to19(db): def update_documents_type(item): changed = False for document in item.get('documents', []): if document.get('documentType') == 'contractAnnexes': document['documentType'] = 'contractAnnexe' changed = True return changed changed = False results = db.view('tenders/all', include_docs=True) for i in results: doc = i.doc changed = update_documents_type(doc) or changed for item in ('bids', 'complaints', 'cancellations', 'contracts', 'awards'): for item_val in doc.get(item, []): changed = update_documents_type(item_val) or changed if item == 'awards': for complaint in item_val.get('complaints', []): changed = update_documents_type(complaint) or changed if changed: doc['dateModified'] = get_now().isoformat() db.save(doc)
def test_switch_to_complaint(self): for status in ['invalid', 'resolved', 'declined']: self.app.authorization = ('Basic', ('token', '')) response = self.app.post_json('/auctions/{}/complaints'.format(self.auction_id), {'data': { 'title': 'complaint title', 'description': 'complaint description', 'author': self.initial_organization, 'status': 'claim' }}) self.assertEqual(response.status, '201 Created') self.assertEqual(response.json['data']['status'], 'claim') complaint = response.json['data'] response = self.app.patch_json('/auctions/{}/complaints/{}?acc_token={}'.format(self.auction_id, complaint['id'], self.auction_token), {"data": { "status": "answered", "resolution": status * 4, "resolutionType": status }}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data']["status"], "answered") self.assertEqual(response.json['data']["resolutionType"], status) auction = self.db.get(self.auction_id) auction['complaints'][-1]['dateAnswered'] = (get_now() - timedelta(days=1 if 'procurementMethodDetails' in auction else 4)).isoformat() self.db.save(auction) self.app.authorization = ('Basic', ('chronograph', '')) response = self.app.patch_json('/auctions/{}'.format(self.auction_id), {'data': {'id': self.auction_id}}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']["complaints"][-1]['status'], status)
def test_listing_changes(self): contract = self.create_contract() data = self.db[contract['id']] documents = data['documents'] for i in range(3): document = deepcopy(test_document) document['dateModified'] = get_now().isoformat() document['id'] = uuid4().hex documents.append(document) self.db.save(data) ids = ','.join([i['id'] for i in documents]) response = self.app.get('/contracts/{}/documents'.format(contract['id'])) self.assertTrue(ids.startswith(','.join([i['id'] for i in response.json['data']]))) self.assertEqual(response.status, '200 OK') self.assertEqual(len(response.json['data']), len(documents)) self.assertEqual(set([i['id'] for i in response.json['data']]), set([i['id'] for i in documents])) self.assertEqual(set([i['dateModified'] for i in response.json['data']]), set([i['dateModified'] for i in documents])) self.assertEqual([i['dateModified'] for i in response.json['data']], sorted([i['dateModified'] for i in documents]))
def next_check(self): now = get_now() checks = [] if self.status == 'active.tendering' and self.tenderPeriod.endDate and not any([i.status in ['pending', 'accepted'] for i in self.complaints]): checks.append(self.tenderPeriod.endDate.astimezone(TZ)) elif not self.lots and self.status == 'active.awarded': standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in self.awards if a.complaintPeriod.endDate ] if standStillEnds: standStillEnd = max(standStillEnds) if standStillEnd > now: checks.append(standStillEnd) elif self.lots and self.status in ['active.qualification', 'active.awarded']: lots_ends = [] for lot in self.lots: if lot['status'] != 'active': continue lot_awards = [i for i in self.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: lots_ends.append(standStillEnd) if lots_ends: checks.append(min(lots_ends)) return sorted(checks)[0].isoformat() if checks else None
def test_switch_to_pending(self): response = self.app.post_json( "/auctions/{}/awards/{}/complaints".format(self.auction_id, self.award_id), { "data": { "title": "complaint title", "description": "complaint description", "author": test_organization, "status": "claim", } }, ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.json["data"]["status"], "claim") response = self.app.patch_json( "/auctions/{}/awards/{}".format(self.auction_id, self.award_id), {"data": {"status": "active"}} ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["status"], "active") auction = self.db.get(self.auction_id) auction["awards"][0]["complaints"][0]["dateSubmitted"] = ( get_now() - timedelta(days=1 if "procurementMethodDetails" in auction else 4) ).isoformat() self.db.save(auction) self.app.authorization = ("Basic", ("chronograph", "")) response = self.app.patch_json("/auctions/{}".format(self.auction_id), {"data": {"id": self.auction_id}}) self.assertEqual(response.status, "200 OK") self.assertEqual(response.json["data"]["awards"][0]["complaints"][0]["status"], "pending")
def add_logging_context(event): request = event.request params = { 'TENDERS_API_VERSION': VERSION, 'TAGS': 'python,api', 'USER': str(request.authenticated_userid or ''), #'ROLE': str(request.authenticated_role), 'CURRENT_URL': request.url, 'CURRENT_PATH': request.path_info, 'REMOTE_ADDR': request.remote_addr or '', 'USER_AGENT': request.user_agent or '', 'REQUEST_METHOD': request.method, 'AWARD_ID': '', 'BID_ID': '', 'COMPLAINT_ID': '', 'CONTRACT_ID': '', 'DOCUMENT_ID': '', 'QUESTION_ID': '', 'TENDER_ID': '', 'TIMESTAMP': get_now().isoformat(), 'REQUEST_ID': request.environ.get('REQUEST_ID', ''), 'CLIENT_REQUEST_ID': request.headers.get('X-Client-Request-ID', ''), } request.logging_context = params
def delete(self): """Cancelling the proposal Example request for cancelling the proposal: .. sourcecode:: http DELETE /tenders/4879d3f8ee2443169b5fbbc9f89fa607/bids/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json """ bid = self.request.context if self.request.validated['tender_status'] != 'active.tendering': self.request.errors.add('body', 'data', 'Can\'t delete bid in current ({}) tender status'.format(self.request.validated['tender_status'])) self.request.errors.status = 403 return tender = self.request.validated['tender'] if tender.tenderPeriod.startDate and get_now() < tender.tenderPeriod.startDate or get_now() > tender.tenderPeriod.endDate: self.request.errors.add('body', 'data', 'Bid can be deleted only during the tendering period: from ({}) to ({}).'.format(tender.tenderPeriod.startDate and tender.tenderPeriod.startDate.isoformat(), tender.tenderPeriod.endDate.isoformat())) self.request.errors.status = 403 return bid.status = 'deleted' if tender.lots: bid.lotValues = [] self.request.validated['tender'].modified = False if save_tender(self.request): res = bid.serialize("view") self.LOGGER.info('Deleted tender bid {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_bid_delete'})) return {'data': res}
def collection_post(self): """Post a cancellation """ tender = self.request.validated["tender"] if tender.status in ["complete", "cancelled", "unsuccessful"]: self.request.errors.add( "body", "data", "Can't add cancellation in current ({}) tender status".format(tender.status) ) self.request.errors.status = 403 return cancellation = self.request.validated["cancellation"] cancellation.date = get_now() if cancellation.status == "active": tender.status = "cancelled" tender.cancellations.append(cancellation) if save_tender(self.request): self.LOGGER.info( "Created tender cancellation {}".format(cancellation.id), extra=context_unpack( self.request, {"MESSAGE_ID": "tender_cancellation_create"}, {"cancellation_id": cancellation.id} ), ) self.request.response.status = 201 self.request.response.headers["Location"] = self.request.route_url( "Tender Cancellations", tender_id=tender.id, cancellation_id=cancellation.id ) return {"data": cancellation.serialize("view")}
def validate_tender_auction_data(request): data = validate_patch_tender_data(request) tender = request.context if data is not None: if tender.status != 'active.auction': request.errors.add('body', 'data', 'Can\'t report auction results in current ({}) tender status'.format(tender.status)) request.errors.status = 403 return bids = data.get('bids', []) #if not bids: #request.errors.add('body', 'data', "Bids data not available") #request.errors.status = 422 #return tender_bids_ids = [i.id for i in tender.bids] if len(bids) != len(tender.bids): request.errors.add('body', 'bids', "Number of auction results did not match the number of tender bids") request.errors.status = 422 return #elif not all(['id' in i for i in bids]): #request.errors.add('body', 'bids', "Results of auction bids should contains id of bid") #request.errors.status = 422 #return elif set([i['id'] for i in bids]) != set(tender_bids_ids): request.errors.add('body', 'bids', "Auction bids should be identical to the tender bids") request.errors.status = 422 return data['bids'] = [x for (y, x) in sorted(zip([tender_bids_ids.index(i['id']) for i in bids], bids))] else: data = {} if request.method == 'POST': now = get_now().isoformat() data['auctionPeriod'] = {'endDate': now} data['awardPeriod'] = {'startDate': now} data['status'] = 'active.qualification' request.validated['data'] = data
def check_tender_status(request): tender = request.validated['tender'] now = get_now() if tender.lots: if any([ i.status in ['claim', 'answered', 'pending'] 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] if not lot_awards: continue last_award = lot_awards[-1] pending_complaints = any([ i['status'] in ['claim', 'answered', 'pending'] and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i.status in ['claim', 'answered', 'pending'] for a in lot_awards for i in a.complaints ]) stand_still_end = max( [a.complaintPeriod.endDate or now for a in lot_awards]) if pending_complaints or pending_awards_complaints or not stand_still_end <= now: continue elif last_award.status == 'unsuccessful': lot.status = 'unsuccessful' continue elif last_award.status == 'active' and any([ i.status == 'active' and i.awardID == last_award.id for i in tender.contracts ]): lot.status = 'complete' statuses = set([lot.status for lot in tender.lots]) if statuses == set(['cancelled']): tender.status = 'cancelled' elif not statuses.difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' elif not statuses.difference( set(['complete', 'unsuccessful', 'cancelled'])): tender.status = 'complete' else: pending_complaints = any([ i.status in ['claim', 'answered', 'pending'] for i in tender.complaints ]) pending_awards_complaints = any([ i.status in ['claim', 'answered', 'pending'] for a in tender.awards for i in a.complaints ]) stand_still_ends = [ a.complaintPeriod.endDate for a in tender.awards if a.complaintPeriod.endDate ] stand_still_end = max(stand_still_ends) if stand_still_ends else now stand_still_time_expired = stand_still_end < now active_awards = any( [a.status in ['active', 'pending'] for a in tender.awards]) if not active_awards and not pending_complaints and not pending_awards_complaints and stand_still_time_expired: tender.status = 'unsuccessful' if tender.contracts and tender.contracts[-1].status == 'active': tender.status = 'complete'
:param request: :return: True if OK """ transfer = request.validated['transfer'] transfer.date = get_now() try: transfer.store(request.registry.db) except ModelValidationError, e: # pragma: no cover for i in e.message: request.errors.add('body', i, e.message[i]) request.errors.status = 422 except Exception, e: # pragma: no cover request.errors.add('body', 'data', str(e)) else: LOGGER.info('Saved transfer {}: at {}'.format(transfer.id, get_now().isoformat()), extra=context_unpack(request, {'MESSAGE_ID': 'save_transfer'})) return True def set_ownership(item, request, access_token=None, transfer_token=None): """ Set ownership for item :param item: :param request: :param access_token: :param transfer_token: :return: None """ item.owner = request.authenticated_userid item.access_token = sha512(access_token).hexdigest()
def check_auction_status(request): auction = request.validated['auction'] now = get_now() if auction.lots: if any([ i.status in auction.block_complaint_status and i.relatedLot is None for i in auction.complaints ]): return for lot in auction.lots: if lot.status != 'active': continue lot_awards = [i for i in auction.awards if i.lotID == lot.id] if not lot_awards: continue last_award = lot_awards[-1] pending_complaints = any([ i['status'] in auction.block_complaint_status and i.relatedLot == lot.id for i in auction.complaints ]) pending_awards_complaints = any([ i.status in auction.block_complaint_status for a in lot_awards for i in a.complaints ]) stand_still_end = max( [a.complaintPeriod.endDate or now for a in lot_awards]) if pending_complaints or pending_awards_complaints or not stand_still_end <= now: continue elif last_award.status == 'unsuccessful': LOGGER.info('Switched lot {} of auction {} to {}'.format( lot.id, auction.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_unsuccessful'}, {'LOT_ID': lot.id})) lot.status = 'unsuccessful' continue elif last_award.status == 'active' and any([ i.status == 'active' and i.awardID == last_award.id for i in auction.contracts ]): LOGGER.info('Switched lot {} of auction {} to {}'.format( lot.id, auction.id, 'complete'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_complete'}, {'LOT_ID': lot.id})) lot.status = 'complete' statuses = set([lot.status for lot in auction.lots]) if statuses == set(['cancelled']): LOGGER.info('Switched lot {} of auction {} to {}'.format( lot.id, auction.id, 'cancelled'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_cancelled'}, {'LOT_ID': lot.id})) auction.status = 'cancelled' elif not statuses.difference(set(['unsuccessful', 'cancelled'])): LOGGER.info('Switched lot {} of auction {} to {}'.format( lot.id, auction.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_unsuccessful'}, {'LOT_ID': lot.id})) auction.status = 'unsuccessful' elif not statuses.difference( set(['complete', 'unsuccessful', 'cancelled'])): LOGGER.info('Switched lot {} of auction {} to {}'.format( lot.id, auction.id, 'complete'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_complete'}, {'LOT_ID': lot.id})) auction.status = 'complete' else: pending_complaints = any([ i.status in auction.block_complaint_status for i in auction.complaints ]) pending_awards_complaints = any([ i.status in auction.block_complaint_status for a in auction.awards for i in a.complaints ]) stand_still_ends = [ a.complaintPeriod.endDate for a in auction.awards if a.complaintPeriod.endDate ] stand_still_end = max(stand_still_ends) if stand_still_ends else now stand_still_time_expired = stand_still_end < now last_award_status = auction.awards[-1].status if auction.awards else '' if not pending_complaints and not pending_awards_complaints and stand_still_time_expired \ and last_award_status == 'unsuccessful': LOGGER.info( 'Switched auction {} to {}'.format(auction.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_unsuccessful'})) auction.status = 'unsuccessful' if auction.contracts and auction.contracts[-1].status == 'active': LOGGER.info( 'Switched auction {} to {}'.format(auction.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_complete'})) auction.status = 'complete'
def patch_tender_award_complaint_document(self): response = self.app.post( "/tenders/{}/awards/{}/complaints/{}/documents?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, self.complaint_owner_token), upload_files=[("file", "name.doc", "content")], ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") doc_id = response.json["data"]["id"] self.assertIn(doc_id, response.headers["Location"]) response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.tender_token), {"data": { "description": "document description" }}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update document only author") response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), {"data": { "description": "document description" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) self.assertEqual("document description", response.json["data"]["description"]) if get_now() < RELEASE_2020_04_19: response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, self.complaint_owner_token), {"data": { "status": "pending" }}, ) else: with change_auth(self.app, ("Basic", ("bot", ""))): response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}".format( self.tender_id, self.award_id, self.complaint_id), {"data": { "status": "pending" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.json["data"]["status"], "pending") response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), "content2", content_type="application/msword", ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") key = response.json["data"]["url"].split("?")[-1] response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}?{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, key)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/msword") self.assertEqual(response.content_length, 8) self.assertEqual(response.body, "content2") self.set_status("complete") response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), {"data": { "description": "document description" }}, 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 document in current (complete) tender status")
def put_tender_award_complaint_document(self): response = self.app.post( "/tenders/{}/awards/{}/complaints/{}/documents?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, self.complaint_owner_token), upload_files=[("file", "name.doc", "content")], ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") doc_id = response.json["data"]["id"] self.assertIn(doc_id, response.headers["Location"]) response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id), status=404, upload_files=[("invalid_name", "name.doc", "content")], ) self.assertEqual(response.status, "404 Not Found") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["status"], "error") self.assertEqual(response.json["errors"], [{ u"description": u"Not Found", u"location": u"body", u"name": u"file" }]) response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id), upload_files=[("file", "name.doc", "content2")], status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update document only author") response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), upload_files=[("file", "name.doc", "content2")], ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) key = response.json["data"]["url"].split("?")[-1] response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}?{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, key)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/msword") self.assertEqual(response.content_length, 8) self.assertEqual(response.body, "content2") response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) self.assertEqual("name.doc", response.json["data"]["title"]) response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), "content3", content_type="application/msword", ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) key = response.json["data"]["url"].split("?")[-1] response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}?{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, key)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/msword") self.assertEqual(response.content_length, 8) self.assertEqual(response.body, "content3") response = self.app.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), "content4", content_type="application/msword", ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") key = response.json["data"]["url"].split("?")[-1] response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}?{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, key)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/msword") self.assertEqual(response.content_length, 8) self.assertEqual(response.body, "content4") if RELEASE_2020_04_19 < get_now(): self.set_all_awards_complaint_period_end() cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.lots[0]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations".format(self.tender_id), {"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.put( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), upload_files=[("file", "name.doc", "content3")], status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update document only in active lot status")
def invalidate_bids_data(self): self.check_auction_time() self.enquiryPeriod.invalidationDate = get_now() for bid in self.bids: if bid.status not in ["deleted", "draft"]: bid.status = "invalid"
def patch(self): """Post a complaint resolution """ tender = self.request.validated['tender'] if tender.status not in [ 'active.enquiries', 'active.tendering', 'active.auction', 'active.qualification', 'active.awarded' ]: self.request.errors.add( 'body', 'data', 'Can\'t update complaint in current ({}) tender status'.format( tender.status)) self.request.errors.status = 403 return if self.request.context.status != 'pending': self.request.errors.add( 'body', 'data', 'Can\'t update complaint in current ({}) status'.format( self.request.context.status)) self.request.errors.status = 403 return apply_patch(self.request, save=False, src=self.request.context.serialize()) if self.request.context.status == 'cancelled': self.request.errors.add('body', 'data', 'Can\'t cancel complaint') self.request.errors.status = 403 return if self.request.context.status == 'resolved' and tender.status != 'active.enquiries': for i in tender.complaints: if i.status == 'pending': i.status = 'cancelled' tender.status = 'cancelled' elif self.request.context.status in [ 'declined', 'invalid' ] and tender.status == 'active.awarded': pending_complaints = [ i for i in tender.complaints if i.status == 'pending' ] pending_awards_complaints = [ i for a in tender.awards for i in a.complaints if i.status == 'pending' ] stand_still_ends = [ a.complaintPeriod.endDate for a in tender.awards if a.complaintPeriod.endDate ] stand_still_end = max( stand_still_ends) if stand_still_ends else get_now() stand_still_time_expired = stand_still_end < get_now() if not pending_complaints and not pending_awards_complaints and stand_still_time_expired: active_awards = [ a for a in tender.awards if a.status == 'active' ] if not active_awards: tender.status = 'unsuccessful' if save_tender(self.request): LOGGER.info('Updated tender complaint {}'.format( self.request.context.id), extra={'MESSAGE_ID': 'tender_complaint_patch'}) return {'data': self.request.context.serialize("view")}
def test_complaint_value(self): for item in self.initial_data['items']: item['deliveryDate'] = { "startDate": (get_now() + timedelta(days=2)).isoformat(), "endDate": (get_now() + timedelta(days=5)).isoformat() } self.initial_data.update({ "enquiryPeriod": {"endDate": (get_now() + timedelta(days=8)).isoformat()}, "tenderPeriod": {"endDate": (get_now() + timedelta(days=16)).isoformat()} }) response = self.app.post_json("/tenders", {"data": self.initial_data}) with open(TARGET_VALUE_DIR + 'complaint-creation.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/complaints'.format(response.json["data"]["id"]), {'data': complaint}) self.assertEqual(response.status, '201 Created') self.initial_data["value"]["currency"] = self.initial_data["minimalStep"]["currency"] = "USD" response = self.app.post_json("/tenders", {"data": self.initial_data}) with open(TARGET_VALUE_DIR + 'complaint-creation-decoding.http', 'w') as self.app.file_obj: def mock_get_uah_amount_from_value(r, *_): raise raise_operation_error(r, "Failure of decoding data from bank.gov.ua", status=409) with patch("openprocurement.tender.core.models.get_uah_amount_from_value", mock_get_uah_amount_from_value): self.app.post_json( '/tenders/{}/complaints'.format(response.json["data"]["id"]), {'data': complaint}, status=409 ) with open(TARGET_VALUE_DIR + 'complaint-creation-connection.http', 'w') as self.app.file_obj: def mock_get_uah_amount_from_value(r, *_): raise raise_operation_error(r, "Error while getting data from bank.gov.ua: Connection closed", status=409) with patch("openprocurement.tender.core.models.get_uah_amount_from_value", mock_get_uah_amount_from_value): self.app.post_json( '/tenders/{}/complaints'.format(response.json["data"]["id"]), {'data': complaint}, status=409 ) with open(TARGET_VALUE_DIR + 'complaint-creation-rur.http', 'w') as self.app.file_obj: def mock_get_uah_amount_from_value(r, *_): raise raise_operation_error(r, "Couldn't find currency RUR on bank.gov.ua", status=422) with patch("openprocurement.tender.core.models.get_uah_amount_from_value", mock_get_uah_amount_from_value): self.app.post_json( '/tenders/{}/complaints'.format(response.json["data"]["id"]), {'data': complaint}, status=422 )
def test_docs(self): request_path = '/tenders?opt_pretty=1' #### Exploring basic rules with open(TARGET_DIR + 'tender-listing.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.file_obj.write("\n") with open(TARGET_DIR + 'tender-post-attempt.http', 'w') as self.app.file_obj: response = self.app.post(request_path, 'data', status=415) self.assertEqual(response.status, '415 Unsupported Media Type') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'tender-post-attempt-json.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.post( request_path, 'data', content_type='application/json', status=422) self.assertEqual(response.status, '422 Unprocessable Entity') #### Creating tender with open(TARGET_DIR + 'tender-post-attempt-json-data.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders?opt_pretty=1', {'data': test_tender_ua_data}) self.assertEqual(response.status, '201 Created') tender = response.json['data'] owner_token = response.json['access']['token'] with open(TARGET_DIR + 'blank-tender-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(tender['id'])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-listing-no-auth.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) #### Modifying tender tender_period_end_date = get_now() + timedelta(days=16) with open(TARGET_DIR + 'patch-items-value-periods.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"tenderPeriod": {"endDate": tender_period_end_date.isoformat()}}}) with open(TARGET_DIR + 'tender-listing-after-patch.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) self.tender_id = tender['id'] # Setting Bid guarantee with open(TARGET_DIR + 'set-bid-guarantee.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format( self.tender_id, owner_token), {'data': {"guarantee": {"amount": 8, "currency": "USD"}}}) self.assertEqual(response.status, '200 OK') self.assertIn('guarantee', response.json['data']) #### Uploading documentation with open(TARGET_DIR + 'upload-tender-notice.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', 'Notice.pdf', b'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'upload-award-criteria.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', 'AwardCriteria.pdf', b'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents-2.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-award-criteria.http', 'w') as self.app.file_obj: response = self.app.put( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), upload_files=[('file', 'AwardCriteria-2.pdf', b'content2')]) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-documents-3.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/documents'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') #### Enquiries with open(TARGET_DIR + 'ask-question.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/questions'.format(self.tender_id), {'data': question}, status=201) question_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'answer-question.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/questions/{}?acc_token={}'.format( self.tender_id, question_id, owner_token), {"data": {"answer": "Таблицю додано в файлі \"Kalorijnist.xslx\""}}, status=200) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'list-question.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'get-answer.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions/{}'.format( self.tender_id, question_id)) self.assertEqual(response.status, '200 OK') self.set_enquiry_period_end() self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'update-tender-after-enqiery.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"value": {'amount': 501.0}}}, status=403) self.assertEqual(response.status, '403 Forbidden') with open(TARGET_DIR + 'ask-question-after-enquiry-period.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/questions'.format(self.tender_id), {'data': question}, status=403) self.assertEqual(response.status, '403 Forbidden') with open(TARGET_DIR + 'update-tender-after-enqiery-with-update-periods.http', 'w') as self.app.file_obj: tender_period_end_date = get_now() + timedelta(days=8) response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': { "value": { "amount": 501, "currency": "UAH" }, "tenderPeriod": { "endDate": tender_period_end_date.isoformat() } }}) self.assertEqual(response.status, '200 OK') #### Registering bid bids_access = {} with open(TARGET_DIR + 'register-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid}) bid1_id = response.json['data']['id'] bids_access[bid1_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'activate-bidder.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), {'data': {"status": "active"}}) self.assertEqual(response.status, '200 OK') #### Proposal Uploading with open(TARGET_DIR + 'upload-bid-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'Proposal.pdf', b'content')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'bidder-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"value": {'amount': 501.0}}}) self.assertEqual(response.status, '200 OK') #### Bid invalidation with open(TARGET_DIR + 'bidder-after-changing-tender.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') #### Bid confirmation with open(TARGET_DIR + 'bidder-activate-after-changing-tender.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), {'data': {"status": "active"}}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'register-2nd-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid2}) bid2_id = response.json['data']['id'] bids_access[bid2_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') #### Auction self.set_status('active.auction') self.app.authorization = ('Basic', ('auction', '')) auction_url = '{}/tenders/{}'.format(self.auctions_url, self.tender_id) patch_data = { 'auctionUrl': auction_url, 'bids': [{ "id": bid1_id, "participationUrl": '{}?key_for_bid={}'.format(auction_url, bid1_id) }, { "id": bid2_id, "participationUrl": '{}?key_for_bid={}'.format(auction_url, bid2_id) }] } response = self.app.patch_json( '/tenders/{}/auction?acc_token={}'.format(self.tender_id, owner_token), {'data': patch_data}) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'auction-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder2-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid2_id, bids_access[bid2_id])) self.assertEqual(response.status, '200 OK') #### Confirming qualification self.app.authorization = ('Basic', ('auction', '')) response = self.app.get('/tenders/{}/auction'.format(self.tender_id)) auction_bids_data = response.json['data']['bids'] auction_bids_data[0]["value"]["amount"] = 250 # too low price response = self.app.post_json( '/tenders/{}/auction'.format(self.tender_id), {'data': {'bids': auction_bids_data}}) self.app.authorization = ('Basic', ('broker', '')) # get pending award with open(TARGET_DIR + 'get-awards-list.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/awards?acc_token={}'.format(self.tender_id, owner_token)) award = response.json["data"][0] award_id = award["id"] award_bid_id = award["bid_id"] self.assertEqual(len(award.get("milestones", "")), 1) with open(TARGET_DIR + 'fail-disqualification.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format(self.tender_id, award_id, owner_token), {"data": { "status": "unsuccessful", }}, status=403 ) with open(TARGET_DIR + 'post-evidence-document.http', 'w') as self.app.file_obj: self.app.post_json( "/tenders/{}/bids/{}/documents?acc_token={}".format( self.tender_id, award_bid_id, bids_access[award_bid_id]), {"data": { "title": "lorem.doc", "url": self.generate_docservice_url(), "hash": "md5:" + "0" * 32, "format": "application/msword", "documentType": "evidence" }}, status=201 ) tender = self.db.get(self.tender_id) tender["awards"][0]["milestones"][0]["dueDate"] = (get_now() - timedelta(days=1)).isoformat() self.db.save(tender) with open(TARGET_DIR + 'confirm-qualification.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format(self.tender_id, award_id, owner_token), {"data": { "status": "active", "qualified": True, "eligible": True }}, status=200 ) response = self.app.get('/tenders/{}/contracts?acc_token={}'.format( self.tender_id, owner_token)) self.contract_id = response.json['data'][0]['id'] #### Set contract value tender = self.db.get(self.tender_id) for i in tender.get('awards', []): i['complaintPeriod']['endDate'] = i['complaintPeriod']['startDate'] self.db.save(tender) with open(TARGET_DIR + 'tender-contract-set-contract-value.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {"data": {"value": {"amount": 238, "amountNet": 230}}}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']['value']['amount'], 238) #### Setting contract signature date self.tick() with open(TARGET_DIR + 'tender-contract-sign-date.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {"dateSigned": get_now().isoformat()}}) self.assertEqual(response.status, '200 OK') #### Setting contract period period_dates = {"period": { "startDate": (get_now()).isoformat(), "endDate": (get_now() + timedelta(days=365)).isoformat() }} with open(TARGET_DIR + 'tender-contract-period.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {'period': period_dates["period"]}}) self.assertEqual(response.status, '200 OK') #### Uploading contract documentation with open(TARGET_DIR + 'tender-contract-upload-document.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), upload_files=[('file', 'contract_document.doc', b'content')]) self.assertEqual(response.status, '201 Created') self.document_id = response.json['data']['id'] with open(TARGET_DIR + 'tender-contract-get.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token)) self.assertEqual(response.status, '200 OK') #### Preparing the cancellation request with open(TARGET_DIR + 'prepare-cancellation.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/cancellations?acc_token={}'.format( self.tender_id, owner_token), {'data': {'reason': 'cancellation reason', 'reasonType': 'unFixable'}}) self.assertEqual(response.status, '201 Created') cancellation_id = response.json['data']['id'] with open(TARGET_DIR + 'update-cancellation-reasonType.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {'data': {'reasonType': 'forceMajeure'}}) self.assertEqual(response.status, '200 OK') #### Filling cancellation with protocol and supplementary documentation with open(TARGET_DIR + 'upload-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/cancellations/{}/documents?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), upload_files=[('file', 'Notice.pdf', b'content')]) cancellation_doc_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'patch-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), {'data': {"description": 'Changed description'}}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.put( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), upload_files=[('file', 'Notice-2.pdf', b'content2')]) self.assertEqual(response.status, '200 OK') #### Activating the request and cancelling tender with open(TARGET_DIR + 'pending-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {'data': {"status": "pending"}}) self.assertEqual(response.status, '200 OK') self.tick(delta=timedelta(days=11)) self.check_chronograph() with open(TARGET_DIR + 'active-cancellation.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token)) self.assertEqual(response.status, '200 OK') #### Creating tender with open(TARGET_DIR + 'tender-post-attempt-json-data.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders?opt_pretty=1', {'data': test_tender_ua_data}) self.assertEqual(response.status, '201 Created')
def patch(self): """Update of contract """ if self.request.validated['auction_status'] not in [ 'active.qualification', 'active.awarded' ]: self.request.errors.add( 'body', 'data', 'Can\'t update contract in current ({}) auction status'.format( self.request.validated['auction_status'])) self.request.errors.status = 403 return auction = self.request.validated['auction'] if any([ i.status != 'active' for i in auction.lots if i.id in [ a.lotID for a in auction.awards if a.id == self.request.context.awardID ] ]): self.request.errors.add( 'body', 'data', 'Can update contract only in active lot status') self.request.errors.status = 403 return data = self.request.validated['data'] if data['value']: for ro_attr in ('valueAddedTaxIncluded', 'currency'): if data['value'][ro_attr] != getattr(self.context.value, ro_attr): self.request.errors.add( 'body', 'data', 'Can\'t update {} for contract value'.format(ro_attr)) self.request.errors.status = 403 return award = [ a for a in auction.awards if a.id == self.request.context.awardID ][0] if data['value']['amount'] < award.value.amount: self.request.errors.add( 'body', 'data', 'Value amount should be greater or equal to awarded amount ({})' .format(award.value.amount)) self.request.errors.status = 403 return if self.request.context.status != 'active' and 'status' in data and data[ 'status'] == 'active': award = [ a for a in auction.awards if a.id == self.request.context.awardID ][0] stand_still_end = award.complaintPeriod.endDate if stand_still_end > get_now(): self.request.errors.add( 'body', 'data', 'Can\'t sign contract before stand-still period end ({})'. format(stand_still_end.isoformat())) self.request.errors.status = 403 return pending_complaints = [ i for i in auction.complaints if i.status in ['claim', 'answered', 'pending'] and i.relatedLot in [None, award.lotID] ] pending_awards_complaints = [ i for a in auction.awards for i in a.complaints if i.status in ['claim', 'answered', 'pending'] and a.lotID == award.lotID ] if pending_complaints or pending_awards_complaints: self.request.errors.add( 'body', 'data', 'Can\'t sign contract before reviewing all complaints') self.request.errors.status = 403 return contract_status = self.request.context.status apply_patch(self.request, save=False, src=self.request.context.serialize()) if contract_status != self.request.context.status and ( contract_status != 'pending' or self.request.context.status != 'active'): self.request.errors.add('body', 'data', 'Can\'t update contract status') self.request.errors.status = 403 return if self.request.context.status == 'active' and not self.request.context.dateSigned: self.request.context.dateSigned = get_now() check_auction_status(self.request) if save_auction(self.request): self.LOGGER.info( 'Updated auction contract {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'auction_contract_patch'})) return {'data': self.request.context.serialize()}
def patch(self): """Update of award Example request to change the award: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated['tender'] if tender.status not in ['active.qualification', 'active.awarded']: self.request.errors.add( 'body', 'data', 'Can\'t update award in current ({}) tender status'.format( tender.status)) self.request.errors.status = 403 return award = self.request.context if any( [i.status != 'active' for i in tender.lots if i.id == award.lotID]): self.request.errors.add( 'body', 'data', 'Can update award only in active lot status') self.request.errors.status = 403 return award_status = award.status apply_patch(self.request, save=False, src=self.request.context.serialize()) if award_status == 'pending' and award.status == 'active': award.complaintPeriod.endDate = calculate_business_date( get_now(), STAND_STILL_TIME) tender.contracts.append( type(tender).contracts.model_class({ 'awardID': award.id, 'suppliers': award.suppliers, 'value': award.value, 'items': [i for i in tender.items if i.relatedLot == award.lotID], 'contractID': '{}-{}{}'.format(tender.tenderID, self.server_id, len(tender.contracts) + 1) })) add_next_award(self.request) elif award_status == 'active' and award.status == 'cancelled': award.complaintPeriod.endDate = get_now() for i in tender.contracts: if i.awardID == award.id: i.status = 'cancelled' add_next_award(self.request) elif award_status == 'pending' and award.status == 'unsuccessful': award.complaintPeriod.endDate = calculate_business_date( get_now(), STAND_STILL_TIME) add_next_award(self.request) elif award_status == 'unsuccessful' and award.status == 'cancelled' and any( [i.status == 'satisfied' for i in award.complaints]): if tender.status == 'active.awarded': tender.status = 'active.qualification' tender.awardPeriod.endDate = None now = get_now() award.complaintPeriod.endDate = now cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue i.complaintPeriod.endDate = now i.status = 'cancelled' cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = 'cancelled' add_next_award(self.request) else: self.request.errors.add( 'body', 'data', 'Can\'t update award in current ({}) status'.format( award_status)) self.request.errors.status = 403 return if save_tender(self.request): self.LOGGER.info( 'Updated tender award {}'.format(self.request.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_award_patch'})) return {'data': award.serialize("view")}
def initialize(self): self.date = get_now() if self.lots: for lot in self.lots: lot.date = get_now()
def initialize(self): self.date = get_now()
def time_shift(self, status, extra=None): now = get_now() tender = self.db.get(self.tender_id) data = {} if status == 'enquiryPeriod_ends': data.update({ "enquiryPeriod": { "startDate": (now - timedelta(days=28)).isoformat(), "endDate": (now - timedelta(days=1)).isoformat() }, "tenderPeriod": { "startDate": (now - timedelta(days=28)).isoformat(), "endDate": (now + timedelta(days=2)).isoformat() }, }) if status == 'active.pre-qualification': data.update({ "enquiryPeriod": { "startDate": (now - TENDERING_DURATION).isoformat(), "endDate": (now - QUESTIONS_STAND_STILL).isoformat() }, "tenderPeriod": { "startDate": (now - TENDERING_DURATION).isoformat(), "endDate": (now).isoformat(), } }) elif status == 'active.pre-qualification.stand-still': data.update({ "enquiryPeriod": { "startDate": (now - TENDERING_DURATION).isoformat(), "endDate": (now - QUESTIONS_STAND_STILL).isoformat() }, "tenderPeriod": { "startDate": (now - TENDERING_DURATION).isoformat(), "endDate": (now).isoformat(), }, "qualificationPeriod": { "startDate": (now).isoformat(), }, }) if 'lots' in tender and tender['lots']: data['lots'] = [] for index, lot in enumerate(tender['lots']): lot_data = {'id': lot['id']} if lot['status'] is 'active': lot_data["auctionPeriod"] = { "startDate": (now + COMPLAINT_STAND_STILL).isoformat() } data['lots'].append(lot_data) else: data.update({ "auctionPeriod": { "startDate": (now + COMPLAINT_STAND_STILL).isoformat() } }) elif status == 'active.auction': data.update({ "enquiryPeriod": { "startDate": (now - TENDERING_DURATION - COMPLAINT_STAND_STILL).isoformat(), "endDate": (now - COMPLAINT_STAND_STILL - TENDERING_DURATION + QUESTIONS_STAND_STILL).isoformat() }, "tenderPeriod": { "startDate": (now - TENDERING_DURATION - COMPLAINT_STAND_STILL).isoformat(), "endDate": (now - COMPLAINT_STAND_STILL).isoformat() }, "qualificationPeriod": { "startDate": (now - COMPLAINT_STAND_STILL).isoformat(), "endDate": (now).isoformat() } }) if 'lots' in tender and tender['lots']: data['lots'] = [] for index, lot in enumerate(tender['lots']): lot_data = {'id': lot['id']} if lot['status'] == 'active': lot_data["auctionPeriod"] = { "startDate": (now).isoformat() } data['lots'].append(lot_data) else: data.update({"auctionPeriod": {"startDate": now.isoformat()}}) elif status == 'complete': data.update({ "enquiryPeriod": { "startDate": (now - TENDERING_DURATION - COMPLAINT_STAND_STILL - timedelta(days=3)).isoformat(), "endDate": (now - QUESTIONS_STAND_STILL - COMPLAINT_STAND_STILL - timedelta(days=3)).isoformat() }, "tenderPeriod": { "startDate": (now - TENDERING_DURATION - COMPLAINT_STAND_STILL - timedelta(days=3)).isoformat(), "endDate": (now - COMPLAINT_STAND_STILL - timedelta(days=3)).isoformat() }, "auctionPeriod": { "startDate": (now - timedelta(days=3)).isoformat(), "endDate": (now - timedelta(days=2)).isoformat() }, "awardPeriod": { "startDate": (now - timedelta(days=1)).isoformat(), "endDate": (now).isoformat() } }) if self.initial_lots: data.update({ 'lots': [{ "auctionPeriod": { "startDate": (now - timedelta(days=3)).isoformat(), "endDate": (now - timedelta(days=2)).isoformat() } } for i in self.initial_lots] }) if extra: data.update(extra) tender.update(apply_data_patch(tender, data)) self.db.save(tender)
def test_docs_tutorial(self): request_path = '/tenders?opt_pretty=1' # Exploring basic rules # with open('docs/source/tutorial/tender-listing.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.get('/tenders') self.assertEqual(response.status, '200 OK') self.app.file_obj.write("\n") with open('docs/source/tutorial/tender-post-attempt.http', 'w') as self.app.file_obj: response = self.app.post(request_path, 'data', status=415) self.assertEqual(response.status, '415 Unsupported Media Type') self.app.authorization = ('Basic', ('broker', '')) with open('docs/source/tutorial/tender-post-attempt-json.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.post( request_path, 'data', content_type='application/json', status=422) self.assertEqual(response.status, '422 Unprocessable Entity') # Creating tender # with open('docs/source/tutorial/tender-post-attempt-json-data.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders?opt_pretty=1', {"data": test_tender_data}) self.assertEqual(response.status, '201 Created') tender = response.json['data'] owner_token = response.json['access']['token'] with open('docs/source/tutorial/blank-tender-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(tender['id'])) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/initial-tender-listing.http', 'w') as self.app.file_obj: response = self.app.get('/tenders') self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/create-tender-procuringEntity.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders?opt_pretty=1', {"data": test_tender_maximum_data}) self.assertEqual(response.status, '201 Created') response = self.app.post_json('/tenders?opt_pretty=1', {"data": test_tender_data}) self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/tender-listing-after-procuringEntity.http', 'w') as self.app.file_obj: response = self.app.get('/tenders') self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) # Modifying tender # tenderPeriod_endDate = get_now() + timedelta(days=15, seconds=10) with open('docs/source/tutorial/patch-items-value-periods.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': { "tenderPeriod": { "endDate": tenderPeriod_endDate.isoformat() } } }) with open('docs/source/tutorial/tender-listing-after-patch.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) self.tender_id = tender['id'] # Setting Bid guarantee # with open('docs/source/tutorial/set-bid-guarantee.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}?acc_token={}'.format( self.tender_id, owner_token), {"data": {"guarantee": {"amount": 8, "currency": "USD"}}}) self.assertEqual(response.status, '200 OK') self.assertIn('guarantee', response.json['data']) # Uploading documentation # with open('docs/source/tutorial/upload-tender-notice.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', u'Notice.pdf', 'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open('docs/source/tutorial/tender-documents.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents/{}'.format( self.tender_id, doc_id)) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/tender-document-add-documentType.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), {"data": {"documentType": "technicalSpecifications"}}) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/tender-document-edit-docType-desc.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), {"data": {"description": "document description modified"}}) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/upload-award-criteria.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', u'AwardCriteria.pdf', 'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open('docs/source/tutorial/tender-documents-2.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/update-award-criteria.http', 'w') as self.app.file_obj: response = self.app.put('/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), upload_files=[('file', 'AwardCriteria-2.pdf', 'content2')]) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/tender-documents-3.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') # Enquiries # with open('docs/source/tutorial/ask-question.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders/{}/questions'.format( self.tender_id), question, status=201) question_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/answer-question.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/questions/{}?acc_token={}'.format( self.tender_id, question_id, owner_token), answer, status=200) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/list-question.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/get-answer.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions/{}'.format( self.tender_id, question_id)) self.assertEqual(response.status, '200 OK') # Registering bid # self.set_status('active.tendering') self.app.authorization = ('Basic', ('broker', '')) bids_access = {} with open('docs/source/tutorial/register-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders/{}/bids'.format( self.tender_id), bid) bid1_id = response.json['data']['id'] bids_access[bid1_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') # Proposal Uploading # with open('docs/source/tutorial/upload-bid-proposal.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'Proposal.pdf', 'content')]) self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/bidder-documents.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') # Second bidder registration # with open('docs/source/tutorial/register-2nd-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders/{}/bids'.format( self.tender_id), bid2) bid2_id = response.json['data']['id'] bids_access[bid2_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') # Auction # self.set_status('active.auction') self.app.authorization = ('Basic', ('auction', '')) patch_data = { 'auctionUrl': u'http://auction-sandbox.openprocurement.org/tenders/{}'.format(self.tender_id), 'bids': [ { "id": bid1_id, "participationUrl": u'http://auction-sandbox.openprocurement.org/tenders/{}?key_for_bid={}'.format(self.tender_id, bid1_id) }, { "id": bid2_id, "participationUrl": u'http://auction-sandbox.openprocurement.org/tenders/{}?key_for_bid={}'.format(self.tender_id, bid2_id) } ] } response = self.app.patch_json('/tenders/{}/auction?acc_token={}'.format(self.tender_id, owner_token), {'data': patch_data}) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) with open('docs/source/tutorial/auction-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/bidder-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/bidder2-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid2_id, bids_access[bid2_id])) self.assertEqual(response.status, '200 OK') # Confirming qualification # self.app.authorization = ('Basic', ('auction', '')) response = self.app.get('/tenders/{}/auction'.format(self.tender_id)) auction_bids_data = response.json['data']['bids'] response = self.app.post_json('/tenders/{}/auction'.format(self.tender_id), {'data': {'bids': auction_bids_data}}) self.app.authorization = ('Basic', ('broker', '')) response = self.app.get('/tenders/{}/awards'.format(self.tender_id)) # get pending award award_id = [i['id'] for i in response.json['data'] if i['status'] == 'pending'][0] with open('docs/source/tutorial/confirm-qualification.http', 'w') as self.app.file_obj: self.app.patch_json('/tenders/{}/awards/{}?acc_token={}'.format(self.tender_id, award_id, owner_token), {"data": {"status": "active"}}) self.assertEqual(response.status, '200 OK') response = self.app.get('/tenders/{}/contracts'.format(self.tender_id)) self.contract_id = response.json['data'][0]['id'] #### Set contract value tender = self.db.get(self.tender_id) for i in tender.get('awards', []): i['complaintPeriod']['endDate'] = i['complaintPeriod']['startDate'] self.db.save(tender) with open('docs/source/tutorial/tender-contract-set-contract-value.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {"data": {"contractNumber": "contract #13111", "value": {"amount": 238}}}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']['value']['amount'], 238) #### Setting contract signature date # with open('docs/source/tutorial/tender-contract-sign-date.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {"dateSigned": get_now().isoformat()} }) self.assertEqual(response.status, '200 OK') #### Setting contract period period_dates = {"period": {"startDate": (now).isoformat(), "endDate": (now + timedelta(days=365)).isoformat()}} with open('docs/source/tutorial/tender-contract-period.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {'period': period_dates["period"]}}) self.assertEqual(response.status, '200 OK') #### Uploading contract documentation # with open('docs/source/tutorial/tender-contract-upload-document.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), upload_files=[('file', 'contract_first_document.doc', 'content')]) self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/tender-contract-get-documents.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/tender-contract-upload-second-document.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), upload_files=[('file', 'contract_second_document.doc', 'content')]) self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/tender-contract-get-documents-again.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') #### Contract signing # tender = self.db.get(self.tender_id) for i in tender.get('awards', []): i['complaintPeriod']['endDate'] = i['complaintPeriod']['startDate'] self.db.save(tender) with open('docs/source/tutorial/tender-contract-sign.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {'status': 'active'}}) self.assertEqual(response.status, '200 OK') # Preparing the cancellation request # self.set_status('active.awarded') with open('docs/source/tutorial/prepare-cancellation.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders/{}/cancellations?acc_token={}'.format( self.tender_id, owner_token), cancellation) self.assertEqual(response.status, '201 Created') cancellation_id = response.json['data']['id'] # Filling cancellation with protocol and supplementary documentation # with open('docs/source/tutorial/upload-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/cancellations/{}/documents?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), upload_files=[('file', u'Notice.pdf', 'content')]) cancellation_doc_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open('docs/source/tutorial/patch-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), {'data': {"description": 'Changed description'}}) self.assertEqual(response.status, '200 OK') with open('docs/source/tutorial/update-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.put('/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), upload_files=[('file', 'Notice-2.pdf', 'content2')]) self.assertEqual(response.status, '200 OK') # Activating the request and cancelling tender # with open('docs/source/tutorial/active-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json('/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {"data": {"status": "active"}}) self.assertEqual(response.status, '200 OK')
def test_docs(self): request_path = '/tenders?opt_pretty=1' #### Exploring basic rules with open(TARGET_DIR + 'tender-listing.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.file_obj.write("\n") with open(TARGET_DIR + 'tender-post-attempt.http', 'w') as self.app.file_obj: response = self.app.post(request_path, 'data', status=415) self.assertEqual(response.status, '415 Unsupported Media Type') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'tender-post-attempt-json.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.post( request_path, 'data', content_type='application/json', status=422) self.assertEqual(response.status, '422 Unprocessable Entity') #### Creating tender with open(TARGET_DIR + 'tender-post-attempt-json-data.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders?opt_pretty=1', {'data': test_tender_data}) self.assertEqual(response.status, '201 Created') tender = response.json['data'] owner_token = response.json['access']['token'] with open(TARGET_DIR + 'blank-tender-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(tender['id'])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-listing-no-auth.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') # have to make two equal requests, because after first we dont see tender list with open(TARGET_DIR + 'tender-listing-no-auth.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) #### Modifying tender tenderPeriod_endDate = get_now() + timedelta(days=30, seconds=10) with open(TARGET_DIR + 'patch-items-value-periods.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"tenderPeriod": {"endDate": tenderPeriod_endDate.isoformat()}}}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-listing-after-patch.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) self.tender_id = tender['id'] # Setting Bid guarantee with open(TARGET_DIR + 'set-bid-guarantee.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format( self.tender_id, owner_token), {'data': {"guarantee": {"amount": 8, "currency": "USD"}}}) self.assertEqual(response.status, '200 OK') self.assertIn('guarantee', response.json['data']) #### Uploading documentation with open(TARGET_DIR + 'upload-tender-notice.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', u'Notice.pdf', 'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'upload-award-criteria.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), upload_files=[('file', u'AwardCriteria.pdf', 'content')]) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents-2.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-award-criteria.http', 'w') as self.app.file_obj: response = self.app.put( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), upload_files=[('file', 'AwardCriteria-2.pdf', 'content2')]) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-documents-3.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') #### Enquiries with open(TARGET_DIR + 'ask-question.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/questions'.format(self.tender_id), {'data': question}, status=201) question_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'answer-question.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/questions/{}?acc_token={}'.format( self.tender_id, question_id, owner_token), {"data": {"answer": "Таблицю додано в файлі \"Kalorijnist.xslx\""}}, status=200) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'list-question.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'get-answer.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/questions/{}'.format( self.tender_id, question_id)) self.assertEqual(response.status, '200 OK') self.time_shift('enquiryPeriod_ends') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'update-tender-after-enqiery.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}?acc_token={}'.format(tender['id'], owner_token)) self.assertEqual(response.status, '200 OK') response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"minValue": {'amount': 501.0}}}, status=403) self.assertEqual(response.status, '403 Forbidden') with open(TARGET_DIR + 'ask-question-after-enquiry-period.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/questions'.format(self.tender_id), {'data': question}, status=403) self.assertEqual(response.status, '403 Forbidden') with open(TARGET_DIR + 'update-tender-after-enqiery-with-update-periods.http', 'w') as self.app.file_obj: tenderPeriod_endDate = get_now() + timedelta(days=8) response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': { "minValue": { "amount": 501, "currency": u"UAH" }, "tenderPeriod": { "endDate": tenderPeriod_endDate.isoformat() } }}) self.assertEqual(response.status, '200 OK') ### Registering bid bids_access = {} with open(TARGET_DIR + 'register-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid}) bid1_id = response.json['data']['id'] bids_access[bid1_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'activate-bidder.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), {'data': {"status": "pending"}}) self.assertEqual(response.status, '200 OK') #### Proposal Uploading with open(TARGET_DIR + 'upload-bid-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'Proposal.pdf', 'content')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'upload-bid-private-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'Proposal_top_secrets.pdf', 'content')]) self.assertEqual(response.status, '201 Created') priv_doc_id = response.json['data']['id'] # set confidentiality properties with open(TARGET_DIR + 'mark-bid-doc-private.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}/documents/{}?acc_token={}'.format( self.tender_id, bid1_id, priv_doc_id, bids_access[bid1_id]), {'data': { 'confidentiality': 'buyerOnly', 'confidentialityRationale': 'Only our company sells badgers with pink hair.', }}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'upload-bid-financial-document-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/financial_documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'financial_doc.pdf', '1000$')]) self.assertEqual(response.status, '201 Created') response = self.app.post( '/tenders/{}/bids/{}/financial_documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'financial_doc2.pdf', '1000$')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'bidder-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder-financial-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}/financial_documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'upload-bid-eligibility-document-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/eligibility_documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'eligibility_doc.pdf', 'content')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'upload-bid-qualification-document-proposal.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/bids/{}/qualification_documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), upload_files=[('file', 'qualification_document.pdf', 'content')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'bidder-view-financial-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {"minValue": {'amount': 501.0}}}) self.assertEqual(response.status, '200 OK') #### Bid invalidation with open(TARGET_DIR + 'bidder-after-changing-tender.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') #### Bid confirmation with open(TARGET_DIR + 'bidder-activate-after-changing-tender.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), {'data': {"status": "pending"}}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'register-2nd-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid2}) bid2_id = response.json['data']['id'] bids_access[bid2_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') bid_document2.update({ 'confidentiality': 'buyerOnly', 'confidentialityRationale': 'Only our company sells badgers with pink hair.' }) bid3["documents"] = [bid_document, bid_document2] for document in bid3['documents']: document['url'] = self.generate_docservice_url() for document in bid3['eligibilityDocuments']: document['url'] = self.generate_docservice_url() for document in bid3['financialDocuments']: document['url'] = self.generate_docservice_url() for document in bid3['qualificationDocuments']: document['url'] = self.generate_docservice_url() with open(TARGET_DIR + 'register-3rd-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid3}) bid3_id = response.json['data']['id'] bids_access[bid3_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') # Pre-qualification self.set_status( 'active.pre-qualification', {"id": self.tender_id, 'status': 'active.tendering'}) auth = self.app.authorization self.app.authorization = ('Basic', ('chronograph', '')) response = self.app.patch_json( '/tenders/{}'.format(self.tender_id), {'data': {"id": self.tender_id}}) self.app.authorization = auth with open(TARGET_DIR + 'qualifications-listing.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(self.tender_id)) self.assertEqual(response.status, "200 OK") qualifications = response.json['data']['qualifications'] self.assertEqual(len(qualifications), 3) self.assertEqual(qualifications[0]['bidID'], bid1_id) self.assertEqual(qualifications[1]['bidID'], bid2_id) self.assertEqual(qualifications[2]['bidID'], bid3_id) with open(TARGET_DIR + 'approve-qualification1.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/qualifications/{}?acc_token={}'.format( self.tender_id, qualifications[0]['id'], owner_token), {"data": { "status": "active", "qualified": True, "eligible": True }}) self.assertEqual(response.status, "200 OK") with open(TARGET_DIR + 'approve-qualification2.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/qualifications/{}?acc_token={}'.format( self.tender_id, qualifications[1]['id'], owner_token), {"data": { "status": "active", "qualified": True, "eligible": True }}) self.assertEqual(response.status, "200 OK") with open(TARGET_DIR + 'reject-qualification3.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/qualifications/{}?acc_token={}'.format( self.tender_id, qualifications[2]['id'], owner_token), {'data': {"status": "unsuccessful"}}) self.assertEqual(response.status, "200 OK") with open(TARGET_DIR + 'qualificated-bids-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids?acc_token={}'.format( self.tender_id, owner_token)) self.assertEqual(response.status, "200 OK") with open(TARGET_DIR + 'rejected-bid-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid3_id, owner_token)) self.assertEqual(response.status, "200 OK") # active.pre-qualification.stand-still with open(TARGET_DIR + 'pre-qualification-confirmation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(self.tender_id, owner_token), {"data": {"status": "active.pre-qualification.stand-still"}}) self.assertEqual(response.status, "200 OK") self.assertEqual(response.json['data']['status'], "active.pre-qualification.stand-still") #### Auction self.set_status('active.auction') self.app.authorization = ('Basic', ('auction', '')) auction_url = u'{}/tenders/{}'.format(self.auctions_url, self.tender_id) patch_data = { 'auctionUrl': auction_url, 'bids': [{ "id": bid1_id, "participationUrl": u'{}?key_for_bid={}'.format(auction_url, bid1_id) }, { "id": bid2_id, "participationUrl": u'{}?key_for_bid={}'.format(auction_url, bid2_id) }, { "id": bid3_id }] } response = self.app.patch_json( '/tenders/{}/auction?acc_token={}'.format(self.tender_id, owner_token), {'data': patch_data}) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'auction-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder2-participation-url.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}?acc_token={}'.format(self.tender_id, bid2_id, bids_access[bid2_id])) self.assertEqual(response.status, '200 OK') #### Confirming qualification self.app.authorization = ('Basic', ('auction', '')) response = self.app.get('/tenders/{}/auction'.format(self.tender_id)) auction_bids_data = response.json['data']['bids'] response = self.app.post_json( '/tenders/{}/auction'.format(self.tender_id), {'data': {'bids': auction_bids_data}}) self.app.authorization = ('Basic', ('broker', '')) response = self.app.get('/tenders/{}/awards?acc_token={}'.format(self.tender_id, owner_token)) # get pending award award_id = [i['id'] for i in response.json['data'] if i['status'] == 'pending'][0] with open(TARGET_DIR + 'confirm-qualification.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format(self.tender_id, award_id, owner_token), {"data": { "status": "active", "qualified": True, "eligible": True }}) self.assertEqual(response.status, '200 OK') response = self.app.get('/tenders/{}/contracts?acc_token={}'.format( self.tender_id, owner_token)) self.contract_id = response.json['data'][0]['id'] #### Set contract value self.tick() tender = self.db.get(self.tender_id) for i in tender.get('awards', []): i['complaintPeriod']['endDate'] = i['complaintPeriod']['startDate'] self.db.save(tender) with open(TARGET_DIR + 'tender-contract-set-contract-value.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {"data": { "contractNumber": "contract#1", "value": {"amountNet": response.json['data'][0]['value']['amount'] - 1} }}) self.assertEqual(response.status, '200 OK') self.assertEqual( response.json['data']['value']['amountNet'], response.json['data']['value']['amount'] - 1) with open(TARGET_DIR + 'tender-contract-sign-date.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {"dateSigned": get_now().isoformat()}}) self.assertEqual(response.status, '200 OK') #### Setting contract period period_dates = {"period": { "startDate": (get_now()).isoformat(), "endDate": (get_now() + timedelta(days=365)).isoformat() }} with open(TARGET_DIR + 'tender-contract-period.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': {'period': period_dates["period"]}}) self.assertEqual(response.status, '200 OK') #### Uploading contract documentation with open(TARGET_DIR + 'tender-contract-upload-document.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), upload_files=[('file', 'contract_first_document.doc', 'content')]) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'tender-contract-get-documents.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-contract-upload-second-document.http', 'w') as self.app.file_obj: response = self.app.post('/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), upload_files=[('file', 'contract_second_document.doc', 'content')]) self.assertEqual(response.status, '201 Created') self.document_id = response.json['data']['id'] with open(TARGET_DIR + 'tender-contract-patch-document.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}/documents/{}?acc_token={}'.format( self.tender_id, self.contract_id, self.document_id, owner_token), {'data': { "language": 'en', 'title_en': 'Title of Document', 'description_en': 'Description of Document' }}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-contract-get-documents-again.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') tender_contract_separate_id = response.json['data'][0]['id'] with open(TARGET_DIR + 'tender-contract-get-separate.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/contracts/{}/documents/{}?acc_token={}'.format( self.tender_id, self.contract_id, tender_contract_separate_id, owner_token)) self.assertEqual(response.status, '200 OK') #### Preparing the cancellation request with open(TARGET_DIR + 'prepare-cancellation.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/cancellations?acc_token={}'.format( self.tender_id, owner_token), {'data': {'reason': 'cancellation reason', 'reasonType': 'unFixable'}}) self.assertEqual(response.status, '201 Created') cancellation_id = response.json['data']['id'] with open(TARGET_DIR + 'update-cancellation-reasonType.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {'data': {'reasonType': 'expensesCut'}}) self.assertEqual(response.status, '200 OK') #### Filling cancellation with protocol and supplementary documentation with open(TARGET_DIR + 'upload-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.post( '/tenders/{}/cancellations/{}/documents?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), upload_files=[('file', u'Notice.pdf', 'content')]) cancellation_doc_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'patch-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), {'data': {"description": 'Changed description'}}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.put( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'.format( self.tender_id, cancellation_id, cancellation_doc_id, owner_token), upload_files=[('file', 'Notice-2.pdf', 'content2')]) self.assertEqual(response.status, '200 OK') #### Activating the request and cancelling tender with open(TARGET_DIR + 'pending-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {'data': {"status": "pending"}}) self.assertEqual(response.status, '200 OK') self.tick(delta=timedelta(days=11)) self.check_chronograph() with open(TARGET_DIR + 'active-cancellation.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-cancelled.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}?acc_token={}'.format( self.tender_id, owner_token)) self.assertEqual(response.status, '200 OK')
def next_check(self): now = get_now() checks = [] if self.status == 'active.tendering' and self.tenderPeriod.endDate: checks.append(self.tenderPeriod.endDate.astimezone(TZ)) elif self.status == 'active.pre-qualification.stand-still' and self.qualificationPeriod and self.qualificationPeriod.endDate and not any( [ i.status in self.block_complaint_status for q in self.qualifications for i in q.complaints ]): checks.append(self.qualificationPeriod.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)) return min(checks).isoformat() if checks else None
def from0to1(registry): class Request(object): def __init__(self, registry): self.registry = registry results = registry.db.iterview('auctions/all', 2**10, include_docs=True) request = Request(registry) root = Root(request) docs = [] for i in results: auction = i.doc if auction['procurementMethodType'] not in ['dgfOtherAssets', 'dgfFinancialAssets'] \ or auction['status'] not in ['active.qualification', 'active.awarded'] \ or 'awards' not in auction: continue now = get_now().isoformat() awards = auction["awards"] unique_awards = len(set([a['bid_id'] for a in awards])) if unique_awards > 2: switch_auction_to_unsuccessful(auction) else: invalidate_bids_under_threshold(auction) if all(bid['status'] == 'invalid' for bid in auction['bids']): switch_auction_to_unsuccessful(auction) if auction['status'] != 'unsuccessful': award = [ a for a in auction["awards"] if a['status'] in ['active', 'pending'] ][0] award_create_date = award['complaintPeriod']['startDate'] periods = { 'verificationPeriod': { 'startDate': award_create_date, 'endDate': award_create_date }, 'paymentPeriod': { 'startDate': award_create_date, 'endDate': calculate_business_date(parse_date(award_create_date, TZ), AWARD_PAYMENT_TIME, auction, True).isoformat() }, 'signingPeriod': { 'startDate': award_create_date, 'endDate': calculate_business_date(parse_date(award_create_date, TZ), CONTRACT_SIGNING_TIME, auction, True).isoformat() } } award.update(periods) if award['status'] == 'pending': award['status'] = 'pending.payment' elif award['status'] == 'active': award['verificationPeriod']['endDate'] = award[ 'paymentPeriod']['endDate'] = now if unique_awards == 1: bid = chef(auction['bids'], auction.get('features'), [], True)[1] award = { 'id': uuid4().hex, 'bid_id': bid['id'], 'status': 'pending.waiting', 'date': awards[0]['date'], 'value': bid['value'], 'suppliers': bid['tenderers'], 'complaintPeriod': { 'startDate': awards[0]['date'] } } if bid['status'] == 'invalid': award['status'] == 'unsuccessful' award['complaintPeriod']['endDate'] = now awards.append(award) model = registry.auction_procurementMethodTypes.get( auction['procurementMethodType']) if model: try: auction = model(auction) auction.__parent__ = root auction = auction.to_primitive() except: LOGGER.error( "Failed migration of auction {} to schema 1.".format( auction.id), extra={ 'MESSAGE_ID': 'migrate_data_failed', 'AUCTION_ID': auction.id }) else: auction['dateModified'] = get_now().isoformat() docs.append(auction) if len(docs) >= 2**7: registry.db.update(docs) docs = [] if docs: registry.db.update(docs)
def test_docs_tutorial(self): request_path = '/tenders?opt_pretty=1' # Exploring basic rules with open(TARGET_DIR + 'tender-listing.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.get('/tenders') self.assertEqual(response.status, '200 OK') self.app.file_obj.write("\n") with open(TARGET_DIR + 'tender-post-attempt.http', 'w') as self.app.file_obj: response = self.app.post(request_path, 'data', status=415) self.assertEqual(response.status, '415 Unsupported Media Type') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'tender-post-attempt-json.http', 'w') as self.app.file_obj: self.app.authorization = ('Basic', ('broker', '')) response = self.app.post(request_path, 'data', content_type='application/json', status=422) self.assertEqual(response.status, '422 Unprocessable Entity') # Creating tender agreement_id = uuid4().hex agreements = {'agreements': [{'id': agreement_id}]} test_features[0]['relatedItem'] = test_agreement['items'][0]['id'] test_agreement['features'] = test_features for contract in test_agreement['contracts']: contract['parameters'] = parameters lot = deepcopy(test_lots[0]) lot['id'] = uuid4().hex test_tender_data.update(agreements) test_tender_data['lots'] = [lot] for item in test_tender_data['items']: item['relatedLot'] = lot['id'] item['deliveryDate'] = { "startDate": (get_now() + timedelta(days=2)).isoformat(), "endDate": (get_now() + timedelta(days=5)).isoformat() } with open(TARGET_DIR + 'tender-post-attempt-json-data.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders?opt_pretty=1', {'data': test_tender_data}) self.assertEqual(response.status, '201 Created') tender = response.json['data'] owner_token = response.json['access']['token'] with open(TARGET_DIR + 'blank-tender-view.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(tender['id'])) self.assertEqual(response.status, '200 OK') test_tender_maximum_data.update(agreements) test_tender_maximum_data['lots'] = [lot] test_tender_maximum_data['items'][0]['id'] = uuid4().hex test_tender_maximum_data['items'][0]['relatedLot'] = lot['id'] with open(TARGET_DIR + 'create-tender-procuringEntity.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders?opt_pretty=1', {'data': test_tender_maximum_data}) self.assertEqual(response.status, '201 Created') response = self.app.post_json('/tenders?opt_pretty=1', {'data': test_tender_data}) self.assertEqual(response.status, '201 Created') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'tender-switch-draft-pending.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': { 'status': 'draft.pending' }}) data = response.json['data'] self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']['status'], 'draft.pending') self.app.authorization = ('Basic', (BOT_NAME, '')) agreement = deepcopy(test_agreement) agreement['features'] = test_features response = self.app.patch_json( '/tenders/{}/agreements/{}'.format(tender['id'], agreement_id), {'data': agreement}) self.assertEqual(response.status, '200 OK') response = self.app.patch_json('/tenders/{}'.format( tender['id']), {'data': { 'status': 'active.enquiries' }}) self.assertEqual(response.json['data']['status'], 'active.enquiries') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'tender-in-active-enquiries.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(tender['id'])) self.assertEqual(response.json['data']['status'], 'active.enquiries') tender = response.json['data'] # start couchdb index views response = self.app.get('/tenders') # wait until couchdb index views complete sleep(8) with open(TARGET_DIR + 'initial-tender-listing.http', 'w') as self.app.file_obj: response = self.app.get('/tenders') self.assertEqual(response.status, '200 OK') response = self.app.post_json('/tenders', {'data': data}) self.assertEqual((response.status, response.content_type), ('201 Created', 'application/json')) self.tender_id = response.json['data']['id'] self.tender_token = owner_token = response.json['access']['token'] response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(self.tender_id, self.tender_token), {'data': { 'agreements': [test_agreement] }}) self.assertEqual((response.status, response.content_type), ('200 OK', 'application/json')) response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(self.tender_id, self.tender_token), {'data': { 'status': 'draft.pending' }}) self.assertEqual((response.status, response.content_type), ('200 OK', 'application/json')) self.assertEqual(response.json['data']['status'], 'draft.pending') self.app.authorization = ('Basic', (BOT_NAME, '')) response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(self.tender_id, self.tender_token), {'data': { 'agreements': [test_agreement] }}) self.assertEqual((response.status, response.content_type), ('200 OK', 'application/json')) response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(self.tender_id, self.tender_token), {'data': { 'status': 'active.enquiries' }}) tender = response.json['data'] self.assertEqual((response.status, response.content_type), ('200 OK', 'application/json')) self.assertEqual(response.json['data']['status'], 'active.enquiries') self.app.authorization = ('Basic', ('broker', '')) # Modifying tender tender_period_end_date = get_now() + timedelta(days=15, seconds=10) with open(TARGET_DIR + 'patch-items-value-periods.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}?acc_token={}'.format(tender['id'], owner_token), { 'data': { "tenderPeriod": { "endDate": tender_period_end_date.isoformat() }, "items": [{ "quantity": 6 }] } }) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') with open(TARGET_DIR + 'tender-listing-after-patch.http', 'w') as self.app.file_obj: self.app.authorization = None response = self.app.get(request_path) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) self.tender_id = tender['id'] # Setting Bid guarantee with open(TARGET_DIR + 'set-bid-guarantee.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/lots/{}?acc_token={}'.format( self.tender_id, lot['id'], owner_token), {"data": { "guarantee": { "amount": 8, "currency": "USD" } }}) self.assertEqual(response.status, '200 OK') self.assertIn('guarantee', response.json['data']) # Uploading documentation with open(TARGET_DIR + 'upload-tender-notice.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), { 'data': { 'title': u'Notice.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents/{}'.format( self.tender_id, doc_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-document-add-documentType.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), {'data': { "documentType": "technicalSpecifications" }}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-document-edit-docType-desc.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), {'data': { "description": "document description modified" }}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'upload-award-criteria.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/documents?acc_token={}'.format( self.tender_id, owner_token), { 'data': { 'title': u'AwardCriteria.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) self.assertEqual(response.status, '201 Created') doc_id = response.json["data"]["id"] with open(TARGET_DIR + 'tender-documents-2.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-award-criteria.http', 'w') as self.app.file_obj: response = self.app.put_json( '/tenders/{}/documents/{}?acc_token={}'.format( self.tender_id, doc_id, owner_token), { 'data': { 'title': u'AwardCriteria-2.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-documents-3.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/documents'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') # Switch tender to active.tendering self.set_status('active.enquiries', start_end='end') self.app.authorization = ('Basic', ('chronograph', '')) response = self.app.patch_json('/tenders/{}'.format(tender['id']), {'data': {}}) self.assertEqual(response.json['data']['status'], 'active.tendering') # Registering bid self.app.authorization = ('Basic', ('broker', '')) bids_access = {} bid['parameters'] = parameters bid['lotValues'][0]['relatedLot'] = lot['id'] with open(TARGET_DIR + 'register-bidder-invalid.http', 'w') as self.app.file_obj: response = self.app.post_json('/tenders/{}/bids'.format( self.tender_id), {'data': bid}, status=403) self.assertEqual(response.status, '403 Forbidden') bid['tenderers'] = tender['agreements'][0]['contracts'][0]['suppliers'] with open(TARGET_DIR + 'register-bidder.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid}) bid1_id = response.json['data']['id'] bids_access[bid1_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'activate-bidder.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), {'data': { "status": "active" }}) self.assertEqual(response.status, '200 OK') # Proposal Uploading with open(TARGET_DIR + 'upload-bid-proposal.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id]), { 'data': { 'title': u'Proposal.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'bidder-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/bids/{}/documents?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') # Second bid registration with documents bid2['parameters'] = parameters bid2['lotValues'][0]['relatedLot'] = lot['id'] bid2['tenderers'] = tender['agreements'][0]['contracts'][1][ 'suppliers'] with open(TARGET_DIR + 'register-2nd-bidder.http', 'w') as self.app.file_obj: for document in bid2['documents']: document['url'] = self.generate_docservice_url() response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid2}) bid2_id = response.json['data']['id'] bids_access[bid2_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') bid3 = deepcopy(bid2) bid3['tenderers'] = tender['agreements'][0]['contracts'][3][ 'suppliers'] for document in bid3['documents']: document['url'] = self.generate_docservice_url() response = self.app.post_json( '/tenders/{}/bids'.format(self.tender_id), {'data': bid3}) bid3_id = response.json['data']['id'] bids_access[bid3_id] = response.json['access']['token'] self.assertEqual(response.status, '201 Created') # Auction self.set_status('active.auction') self.app.authorization = ('Basic', ('auction', '')) auction_url = u'{}/tenders/{}_{}'.format(self.auctions_url, self.tender_id, lot['id']) patch_data = { 'lots': [{ 'auctionUrl': auction_url, }], 'bids': [{ "id": bid1_id, "lotValues": [{ "participationUrl": u'{}?key_for_bid={}'.format(auction_url, bid1_id) }] }, { "id": bid2_id, "lotValues": [{ "participationUrl": u'{}?key_for_bid={}'.format(auction_url, bid2_id) }] }, { "id": bid3_id, "lotValues": [{ "participationUrl": u'{}?key_for_bid={}'.format(auction_url, bid3_id) }] }] } response = self.app.patch_json( '/tenders/{}/auction/{}?acc_token={}'.format( self.tender_id, lot['id'], owner_token), {'data': patch_data}) self.assertEqual(response.status, '200 OK') self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'auction-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder-participation-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid1_id, bids_access[bid1_id])) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'bidder2-participation-url.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/bids/{}?acc_token={}'.format( self.tender_id, bid2_id, bids_access[bid2_id])) self.assertEqual(response.status, '200 OK') # Confirming qualification self.app.authorization = ('Basic', ('auction', '')) response = self.app.get('/tenders/{}/auction'.format(self.tender_id)) auction_bids_data = response.json['data']['bids'] response = self.app.post_json( '/tenders/{}/auction/{}'.format(self.tender_id, lot['id']), {'data': { 'bids': auction_bids_data }}) self.app.authorization = ('Basic', ('broker', '')) with open(TARGET_DIR + 'awards-get.http', 'w') as self.app.file_obj: response = self.app.get('/tenders/{}/awards'.format( self.tender_id)) self.assertEqual(response.status, '200 OK') # get pending award award_id = [ i['id'] for i in response.json['data'] if i['status'] == 'pending' ][0] with open(TARGET_DIR + 'award-qualification-unsuccessful.http', 'w') as self.app.file_obj: self.app.patch_json('/tenders/{}/awards/{}?acc_token={}'.format( self.tender_id, award_id, owner_token), {"data": { "status": "unsuccessful" }}, status=403) with open(TARGET_DIR + 'award-qualification-active.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format( self.tender_id, award_id, owner_token), {"data": { "status": "active" }}) with open(TARGET_DIR + 'award-qualification-cancelled.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format( self.tender_id, award_id, owner_token), {"data": { "status": "cancelled" }}) # get new pending award response = self.app.get('/tenders/{}/awards'.format(self.tender_id)) award_id = [ i['id'] for i in response.json['data'] if i['status'] == 'pending' ][0] with open(TARGET_DIR + 'award-qualification-unsuccessful1.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format( self.tender_id, award_id, owner_token), {"data": { "status": "unsuccessful" }}) # post document for unsuccessful award with open( TARGET_DIR + 'award-qualification-unsuccessful1_document.http', 'w') as self.app.file_obj: self.app.post_json( '/tenders/{}/awards/{}/documents?acc_token={}'.format( self.tender_id, award_id, owner_token), { "data": { "title": u"explanation.pdf", "url": self.generate_docservice_url(), "hash": "md5:" + "0" * 32, "format": "application/pdf", } }) # get new pending award response = self.app.get('/tenders/{}/awards'.format(self.tender_id)) award_id = [ i['id'] for i in response.json['data'] if i['status'] == 'pending' ][0] with open(TARGET_DIR + 'confirm-qualification.http', 'w') as self.app.file_obj: self.app.patch_json( '/tenders/{}/awards/{}?acc_token={}'.format( self.tender_id, award_id, owner_token), {"data": { "status": "active" }}) self.assertEqual(response.status, '200 OK') response = self.app.get('/tenders/{}/contracts'.format(self.tender_id)) self.contract_id = [ c for c in response.json['data'] if c['status'] == 'pending' ][0]['id'] # Set contract value with open(TARGET_DIR + 'tender-contract-set-contract-value.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), { "data": { "contractNumber": "contract #13111", "value": { "amount": 238, "amountNet": 230 } } }) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']['value']['amount'], 238) # Setting contract signature date with open(TARGET_DIR + 'tender-contract-sign-date.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': { "dateSigned": get_now().isoformat() }}) self.assertEqual(response.status, '200 OK') # Setting contract period period_dates = { "period": { "startDate": get_now().isoformat(), "endDate": (get_now() + timedelta(days=365)).isoformat() } } with open(TARGET_DIR + 'tender-contract-period.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': { 'period': period_dates["period"] }}) self.assertEqual(response.status, '200 OK') # Uploading contract documentation with open(TARGET_DIR + 'tender-contract-upload-document.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), { 'data': { 'title': u'contract_first_document.doc', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/msword', } }) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'tender-contract-get-documents.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'tender-contract-upload-second-document.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/contracts/{}/documents?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), { 'data': { 'title': u'contract_second_document.doc', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/msword', } }) self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'tender-contract-get-documents-again.http', 'w') as self.app.file_obj: response = self.app.get( '/tenders/{}/contracts/{}/documents'.format( self.tender_id, self.contract_id)) self.assertEqual(response.status, '200 OK') # Setting contract signature date with open(TARGET_DIR + 'tender-contract-sign-date.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': { "dateSigned": get_now().isoformat() }}) self.assertEqual(response.status, '200 OK') # Contract signing with open(TARGET_DIR + 'tender-contract-sign.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/contracts/{}?acc_token={}'.format( self.tender_id, self.contract_id, owner_token), {'data': { 'status': 'active' }}) self.assertEqual(response.status, '200 OK') # Preparing the cancellation request self.set_status('active.awarded') with open(TARGET_DIR + 'prepare-cancellation.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/cancellations?acc_token={}'.format( self.tender_id, owner_token), { 'data': { 'reason': 'cancellation reason', 'reasonType': 'noDemand', } }) self.assertEqual(response.status, '201 Created') cancellation_id = response.json['data']['id'] # Filling cancellation with protocol and supplementary documentation with open(TARGET_DIR + 'upload-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.post_json( '/tenders/{}/cancellations/{}/documents?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), { 'data': { 'title': u'Notice.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) cancellation_doc_id = response.json['data']['id'] self.assertEqual(response.status, '201 Created') with open(TARGET_DIR + 'patch-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'. format(self.tender_id, cancellation_id, cancellation_doc_id, owner_token), {'data': { "description": 'Changed description' }}) self.assertEqual(response.status, '200 OK') with open(TARGET_DIR + 'update-cancellation-doc.http', 'w') as self.app.file_obj: response = self.app.put_json( '/tenders/{}/cancellations/{}/documents/{}?acc_token={}'. format(self.tender_id, cancellation_id, cancellation_doc_id, owner_token), { 'data': { 'title': u'Notice-2.pdf', 'url': self.generate_docservice_url(), 'hash': 'md5:' + '0' * 32, 'format': 'application/pdf', } }) self.assertEqual(response.status, '200 OK') # Activating the request and cancelling tender with open(TARGET_DIR + 'active-cancellation.http', 'w') as self.app.file_obj: response = self.app.patch_json( '/tenders/{}/cancellations/{}?acc_token={}'.format( self.tender_id, cancellation_id, owner_token), {"data": { "status": "active" }}) self.assertEqual(response.status, '200 OK')
def patch(self): """Post a complaint resolution for award """ tender = self.request.validated['tender'] if tender.status not in ['active.qualification', 'active.awarded']: self.request.errors.add( 'body', 'data', 'Can\'t update complaint in current ({}) tender status'.format( tender.status)) self.request.errors.status = 403 return if any([ i.status != 'active' for i in tender.lots if i.id == self.request.validated['award'].lotID ]): self.request.errors.add( 'body', 'data', 'Can update complaint only in active lot status') self.request.errors.status = 403 return if self.context.status not in [ 'draft', 'claim', 'answered', 'pending', 'accepted', 'satisfied', 'stopping' ]: self.request.errors.add( 'body', 'data', 'Can\'t update complaint in current ({}) status'.format( self.context.status)) self.request.errors.status = 403 return data = self.request.validated['data'] complaintPeriod = self.request.validated['award'].complaintPeriod is_complaintPeriod = complaintPeriod.startDate < get_now( ) and complaintPeriod.endDate > get_now( ) if complaintPeriod.endDate else complaintPeriod.startDate < get_now( ) # complaint_owner if self.request.authenticated_role == 'complaint_owner' and self.context.status in [ 'draft', 'claim', 'answered' ] and data.get('status', self.context.status) == 'cancelled': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateCanceled = get_now() elif self.request.authenticated_role == 'complaint_owner' and self.context.status in [ 'pending', 'accepted' ] and data.get('status', self.context.status) == 'stopping': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateCanceled = get_now() elif self.request.authenticated_role == 'complaint_owner' and is_complaintPeriod and self.context.status == 'draft' and data.get( 'status', self.context.status) == self.context.status: apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'complaint_owner' and is_complaintPeriod and self.context.status == 'draft' and data.get( 'status', self.context.status) == 'claim': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateSubmitted = get_now() elif self.request.authenticated_role == 'complaint_owner' and is_complaintPeriod and self.context.status == 'draft' and data.get( 'status', self.context.status) == 'pending': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.type = 'complaint' self.context.dateSubmitted = get_now() elif self.request.authenticated_role == 'complaint_owner' and self.context.status == 'answered' and data.get( 'status', self.context.status) == self.context.status: apply_patch(self.request, save=False, src=self.context.serialize()) # tender_owner elif self.request.authenticated_role == 'tender_owner' and self.context.status in [ 'pending', 'accepted' ]: apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'tender_owner' and self.context.status in [ 'claim', 'satisfied' ] and data.get('status', self.context.status) == self.context.status: apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'tender_owner' and self.context.status == 'claim' and data.get( 'resolution', self.context.resolution) and data.get( 'resolutionType', self.context.resolutionType) and data.get( 'status', self.context.status) == 'answered': if len(data.get('resolution', self.context.resolution)) < 20: self.request.errors.add( 'body', 'data', 'Can\'t update complaint: resolution too short') self.request.errors.status = 403 return apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateAnswered = get_now() elif self.request.authenticated_role == 'tender_owner' and self.context.status == 'satisfied' and data.get( 'tendererAction', self.context.tendererAction) and data.get( 'status', self.context.status) == 'resolved': apply_patch(self.request, save=False, src=self.context.serialize()) # aboveThresholdReviewers elif self.request.authenticated_role == 'aboveThresholdReviewers' and self.context.status in [ 'pending', 'accepted', 'stopping' ] and data.get('status', self.context.status) == self.context.status: apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'aboveThresholdReviewers' and self.context.status in [ 'pending', 'stopping' ] and data.get('status', self.context.status) in ['invalid', 'mistaken']: apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateDecision = get_now() self.context.acceptance = False elif self.request.authenticated_role == 'aboveThresholdReviewers' and self.context.status == 'pending' and data.get( 'status', self.context.status) == 'accepted': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateAccepted = get_now() self.context.acceptance = True elif self.request.authenticated_role == 'aboveThresholdReviewers' and self.context.status == 'accepted' and data.get( 'status', self.context.status) in ['declined', 'satisfied']: apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateDecision = get_now() elif self.request.authenticated_role == 'aboveThresholdReviewers' and self.context.status in [ 'accepted', 'stopping' ] and data.get('status', self.context.status) == 'stopped': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateDecision = get_now() self.context.dateCanceled = self.context.dateCanceled or get_now() else: self.request.errors.add('body', 'data', 'Can\'t update complaint') self.request.errors.status = 403 return if self.context.tendererAction and not self.context.tendererActionDate: self.context.tendererActionDate = get_now() if self.context.status not in [ 'draft', 'claim', 'answered', 'pending', 'accepted', 'satisfied', 'stopping' ] and tender.status in ['active.qualification', 'active.awarded']: check_tender_status(self.request) if save_tender(self.request): self.LOGGER.info( 'Updated tender award complaint {}'.format(self.context.id), extra=context_unpack( self.request, {'MESSAGE_ID': 'tender_award_complaint_patch'})) return {'data': self.context.serialize("view")}
def patch_tender_award_complaint_document(self): response = self.app.post( "/tenders/{}/awards/{}/complaints/{}/documents?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, self.complaint_owner_token), upload_files=[("file", "name.doc", "content")], ) self.assertEqual(response.status, "201 Created") self.assertEqual(response.content_type, "application/json") doc_id = response.json["data"]["id"] self.assertIn(doc_id, response.headers["Location"]) response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id), {"data": { "description": "document description" }}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update document only author") response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), {"data": { "description": "document description" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) response = self.app.get( "/tenders/{}/awards/{}/complaints/{}/documents/{}".format( self.tender_id, self.award_id, self.complaint_id, doc_id)) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(doc_id, response.json["data"]["id"]) self.assertEqual("document description", response.json["data"]["description"]) if get_now() < RELEASE_2020_04_19: response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, self.complaint_owner_token), {"data": { "status": "pending" }}, ) else: with change_auth(self.app, ("Basic", ("bot", ""))): response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}".format( self.tender_id, self.award_id, self.complaint_id), {"data": { "status": "pending" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.json["data"]["status"], "pending") response = self.app.patch_json( "/tenders/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), {"data": { "description": "document description2" }}, ) self.assertEqual(response.status, "200 OK") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["data"]["description"], "document description2") cancellation = dict(**test_cancellation) cancellation.update({ "status": "active", "cancellationOf": "lot", "relatedLot": self.lots[0]["id"], }) response = self.app.post_json( "/tenders/{}/cancellations".format(self.tender_id), {"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/{}/awards/{}/complaints/{}/documents/{}?acc_token={}".format( self.tender_id, self.award_id, self.complaint_id, doc_id, self.complaint_owner_token), {"data": { "description": "document description" }}, status=403, ) self.assertEqual(response.status, "403 Forbidden") self.assertEqual(response.content_type, "application/json") self.assertEqual(response.json["errors"][0]["description"], "Can update document only in active lot status")
def post(self): """This API request is targeted to creating new Tenders by procuring organizations. Creating new Tender ------------------- Example request to create tender: .. sourcecode:: http POST /tenders HTTP/1.1 Host: example.com Accept: application/json { "data": { "procuringEntity": { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } }, "value": { "amount": 500, "currency": "UAH", "valueAddedTaxIncluded": true }, "itemsToBeProcured": [ { "description": "футляри до державних нагород", "primaryClassification": { "scheme": "CPV", "id": "44617100-9", "description": "Cartons" }, "additionalClassification": [ { "scheme": "ДКПП", "id": "17.21.1", "description": "папір і картон гофровані, паперова й картонна тара" } ], "unitOfMeasure": "item", "quantity": 5 } ], "enquiryPeriod": { "endDate": "2014-10-31T00:00:00" }, "tenderPeriod": { "startDate": "2014-11-03T00:00:00", "endDate": "2014-11-06T10:00:00" }, "awardPeriod": { "endDate": "2014-11-13T00:00:00" }, "deliveryDate": { "endDate": "2014-11-20T00:00:00" }, "minimalStep": { "amount": 35, "currency": "UAH" } } } This is what one should expect in response: .. sourcecode:: http HTTP/1.1 201 Created Location: http://localhost/api/0.1/tenders/64e93250be76435397e8c992ed4214d1 Content-Type: application/json { "data": { "id": "64e93250be76435397e8c992ed4214d1", "tenderID": "UA-64e93250be76435397e8c992ed4214d1", "dateModified": "2014-10-27T08:06:58.158Z", "procuringEntity": { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } }, "value": { "amount": 500, "currency": "UAH", "valueAddedTaxIncluded": true }, "itemsToBeProcured": [ { "description": "футляри до державних нагород", "primaryClassification": { "scheme": "CPV", "id": "44617100-9", "description": "Cartons" }, "additionalClassification": [ { "scheme": "ДКПП", "id": "17.21.1", "description": "папір і картон гофровані, паперова й картонна тара" } ], "unitOfMeasure": "item", "quantity": 5 } ], "enquiryPeriod": { "endDate": "2014-10-31T00:00:00" }, "tenderPeriod": { "startDate": "2014-11-03T00:00:00", "endDate": "2014-11-06T10:00:00" }, "awardPeriod": { "endDate": "2014-11-13T00:00:00" }, "deliveryDate": { "endDate": "2014-11-20T00:00:00" }, "minimalStep": { "amount": 35, "currency": "UAH" } } } """ tender_id = generate_id() tender = self.request.validated['tender'] tender.id = tender_id tender.tenderID = generate_tender_id(get_now(), self.db, self.server_id) if hasattr(tender, "initialize"): tender.initialize() set_ownership(tender, self.request) self.request.validated['tender'] = tender self.request.validated['tender_src'] = {} if save_tender(self.request): self.LOGGER.info( 'Created tender {} ({})'.format(tender_id, tender.tenderID), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_create'}, { 'tender_id': tender_id, 'tenderID': tender.tenderID })) self.request.response.status = 201 self.request.response.headers['Location'] = self.request.route_url( 'Tender', tender_id=tender_id) return { 'data': tender.serialize(tender.status), 'access': { 'token': tender.owner_token } }
def check_status(request): auction = request.validated['auction'] now = get_now() for complaint in auction.complaints: check_complaint_status(request, complaint, now) for award in auction.awards: for complaint in award.complaints: check_complaint_status(request, complaint, now) if auction.status == 'active.enquiries' and not auction.tenderPeriod.startDate and auction.enquiryPeriod.endDate.astimezone( TZ) <= now: LOGGER.info('Switched auction {} to {}'.format(auction.id, 'active.tendering'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_active.tendering'})) auction.status = 'active.tendering' return elif auction.status == 'active.enquiries' and auction.tenderPeriod.startDate and auction.tenderPeriod.startDate.astimezone( TZ) <= now: LOGGER.info('Switched auction {} to {}'.format(auction.id, 'active.tendering'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_active.tendering'})) auction.status = 'active.tendering' return elif not auction.lots and auction.status == 'active.tendering' and auction.tenderPeriod.endDate <= now: LOGGER.info('Switched auction {} to {}'.format(auction['id'], 'active.auction'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_active.auction'})) auction.status = 'active.auction' remove_draft_bids(request) check_bids(request) if auction.numberOfBids < 2 and auction.auctionPeriod: auction.auctionPeriod.startDate = None return elif auction.lots and auction.status == 'active.tendering' and auction.tenderPeriod.endDate <= now: LOGGER.info('Switched auction {} to {}'.format(auction['id'], 'active.auction'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_auction_active.auction'})) auction.status = 'active.auction' remove_draft_bids(request) check_bids(request) [ setattr(i.auctionPeriod, 'startDate', None) for i in auction.lots if i.numberOfBids < 2 and i.auctionPeriod ] return elif not auction.lots and auction.status == 'active.awarded': standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in auction.awards if a.complaintPeriod.endDate ] if not standStillEnds: return standStillEnd = max(standStillEnds) if standStillEnd <= now: check_auction_status(request) elif auction.lots and auction.status in [ 'active.qualification', 'active.awarded' ]: if any([ i['status'] in auction.block_complaint_status and i.relatedLot is None for i in auction.complaints ]): return for lot in auction.lots: if lot['status'] != 'active': continue lot_awards = [i for i in auction.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: check_auction_status(request) return
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 add_next_award(request): auction = request.validated['auction'] now = get_now() if not auction.awardPeriod: auction.awardPeriod = type(auction).awardPeriod({}) if not auction.awardPeriod.startDate: auction.awardPeriod.startDate = now if auction.lots: statuses = set() for lot in auction.lots: if lot.status != 'active': continue lot_awards = [i for i in auction.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 lot_items = [i.id for i in auction.items if i.relatedLot == lot.id] features = [ i for i in (auction.features or []) if i.featureOf == 'tenderer' or i.featureOf == 'lot' and i.relatedItem == lot.id or i.featureOf == 'item' and i.relatedItem in lot_items ] codes = [i.code for i in features] bids = [{ 'id': bid.id, 'value': [i for i in bid.lotValues if lot.id == i.relatedLot][0].value, 'tenderers': bid.tenderers, 'parameters': [i for i in bid.parameters if i.code in codes], 'date': [i for i in bid.lotValues if lot.id == i.relatedLot][0].date } for bid in auction.bids if lot.id in [i.relatedLot for i in bid.lotValues]] if not bids: lot.status = 'unsuccessful' statuses.add('unsuccessful') continue unsuccessful_awards = [ i.bid_id for i in lot_awards if i.status == 'unsuccessful' ] bids = chef(bids, features, unsuccessful_awards, True) if bids: bid = bids[0] award = type(auction).awards.model_class({ 'bid_id': bid['id'], 'lotID': lot.id, 'status': 'pending', 'value': bid['value'], 'date': get_now(), 'suppliers': bid['tenderers'], 'complaintPeriod': { 'startDate': now.isoformat() } }) auction.awards.append(award) request.response.headers['Location'] = request.route_url( '{}:Auction Awards'.format(auction.procurementMethodType), auction_id=auction.id, award_id=award['id']) statuses.add('pending') else: statuses.add('unsuccessful') if statuses.difference(set(['unsuccessful', 'active'])): auction.awardPeriod.endDate = None auction.status = 'active.qualification' else: auction.awardPeriod.endDate = now auction.status = 'active.awarded' else: if not auction.awards or auction.awards[-1].status not in [ 'pending', 'active' ]: unsuccessful_awards = [ i.bid_id for i in auction.awards if i.status == 'unsuccessful' ] bids = chef(auction.bids, auction.features or [], unsuccessful_awards, True) if bids: bid = bids[0].serialize() award = type(auction).awards.model_class({ 'bid_id': bid['id'], 'status': 'pending', 'date': get_now(), 'value': bid['value'], 'suppliers': bid['tenderers'], 'complaintPeriod': { 'startDate': get_now().isoformat() } }) auction.awards.append(award) request.response.headers['Location'] = request.route_url( '{}:Auction Awards'.format(auction.procurementMethodType), auction_id=auction.id, award_id=award['id']) if auction.awards[-1].status == 'pending': auction.awardPeriod.endDate = None auction.status = 'active.qualification' else: auction.awardPeriod.endDate = now auction.status = 'active.awarded'
def test_patch_tender_contract(self): response = self.app.get('/tenders/{}/contracts'.format(self.tender_id)) contract = response.json['data'][0] fake_contractID = "myselfID" fake_items_data = [{"description": "New Description"}] fake_suppliers_data = [{"name": "New Name"}] response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), { "data": { "contractID": fake_contractID, "items": fake_items_data, "suppliers": fake_suppliers_data } }) response = self.app.get('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id'])) self.assertNotEqual(fake_contractID, response.json['data']['contractID']) self.assertNotEqual(fake_items_data, response.json['data']['items']) self.assertNotEqual(fake_suppliers_data, response.json['data']['suppliers']) response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "value": { "currency": "USD" } }}, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual(response.json['errors'][0]["description"], "Can\'t update currency for contract value") response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), {"data": { "value": { "valueAddedTaxIncluded": False } }}, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual( response.json['errors'][0]["description"], "Can\'t update valueAddedTaxIncluded for contract value") response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "value": { "amount": 501 } }}, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual( response.json['errors'][0]["description"], "Value amount should be less or equal to awarded amount (500.0)") response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), {"data": { "value": { "amount": 238 } }}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']['value']['amount'], 238) response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "status": "active" }}, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual(response.content_type, 'application/json') self.assertIn("Can't sign contract before stand-still period end (", response.json['errors'][0]["description"]) self.set_status('complete', {'status': 'active.awarded'}) response = self.app.post_json( '/tenders/{}/awards/{}/complaints'.format(self.tender_id, self.award_id), { 'data': { 'title': 'complaint title', 'description': 'complaint description', 'author': self.supplier_info } }) self.assertEqual(response.status, '201 Created') complaint = response.json['data'] owner_token = response.json['access']['token'] response = self.app.patch_json( '/tenders/{}/awards/{}/complaints/{}?acc_token={}'.format( self.tender_id, self.award_id, complaint['id'], owner_token), {"data": { "status": "pending" }}) self.assertEqual(response.status, '200 OK') tender = self.db.get(self.tender_id) for i in tender.get('awards', []): i['complaintPeriod']['endDate'] = i['complaintPeriod']['startDate'] self.db.save(tender) response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), {"data": { "dateSigned": i['complaintPeriod']['endDate'] }}, status=422) self.assertEqual(response.status, '422 Unprocessable Entity') self.assertEqual(response.json['errors'], [{ u'description': [ u'Contract signature date should be after award complaint period end date ({})' .format(i['complaintPeriod']['endDate']) ], u'location': u'body', u'name': u'dateSigned' }]) one_hour_in_furure = (get_now() + timedelta(hours=1)).isoformat() response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "dateSigned": one_hour_in_furure }}, status=422) self.assertEqual(response.status, '422 Unprocessable Entity') self.assertEqual(response.json['errors'], [{ u'description': [u"Contract signature date can't be in the future"], u'location': u'body', u'name': u'dateSigned' }]) custom_signature_date = get_now().isoformat() response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), {"data": { "dateSigned": custom_signature_date }}) self.assertEqual(response.status, '200 OK') response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "status": "active" }}, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual(response.content_type, 'application/json') self.assertEqual( response.json['errors'][0]["description"], "Can't sign contract before reviewing all complaints") response = self.app.patch_json( '/tenders/{}/awards/{}/complaints/{}?acc_token={}'.format( self.tender_id, self.award_id, complaint['id'], owner_token), {"data": { "status": "stopping", "cancellationReason": "reason" }}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data']["status"], "stopping") authorization = self.app.authorization self.app.authorization = ('Basic', ('reviewer', '')) response = self.app.patch_json( '/tenders/{}/awards/{}/complaints/{}'.format( self.tender_id, self.award_id, complaint['id']), {'data': { 'status': 'stopped' }}) self.assertEqual(response.status, '200 OK') self.assertEqual(response.json['data']["status"], "stopped") self.app.authorization = authorization response = self.app.patch_json( '/tenders/{}/contracts/{}'.format(self.tender_id, contract['id']), {"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/{}/contracts/{}'.format( self.tender_id, contract['id']), { "data": { "value": { "amount": 232 }, "contractID": "myselfID", "title": "New Title", "items": [{ "description": "New Description" }], "suppliers": [{ "name": "New Name" }] } }, status=403) self.assertEqual(response.status, '403 Forbidden') self.assertEqual( response.json['errors'][0]["description"], "Can't update contract in current (complete) tender status") response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "status": "active" }}, 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 contract in current (complete) tender status") response = self.app.patch_json('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id']), {"data": { "status": "pending" }}, 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 contract in current (complete) tender status") response = self.app.patch_json('/tenders/{}/contracts/some_id'.format( self.tender_id), {"data": { "status": "active" }}, status=404) self.assertEqual(response.status, '404 Not Found') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['status'], 'error') self.assertEqual(response.json['errors'], [{ u'description': u'Not Found', u'location': u'url', u'name': u'contract_id' }]) response = self.app.patch_json('/tenders/some_id/contracts/some_id', {"data": { "status": "active" }}, status=404) self.assertEqual(response.status, '404 Not Found') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['status'], 'error') self.assertEqual(response.json['errors'], [{ u'description': u'Not Found', u'location': u'url', u'name': u'tender_id' }]) response = self.app.get('/tenders/{}/contracts/{}'.format( self.tender_id, contract['id'])) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data']["status"], "active") self.assertEqual(response.json['data']["value"]['amount'], 238)
def check_status(request): tender = request.validated['tender'] now = get_now() for complaint in tender.complaints: check_complaint_status(request, complaint, now) for award in tender.awards: for complaint in award.complaints: check_complaint_status(request, complaint, now) if tender.status == 'active.enquiries' and not tender.tenderPeriod.startDate and tender.enquiryPeriod.endDate.astimezone( TZ) <= now: LOGGER.info('Switched tender {} to {}'.format(tender.id, 'active.tendering'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_active.tendering'})) tender.status = 'active.tendering' return elif tender.status == 'active.enquiries' and tender.tenderPeriod.startDate and tender.tenderPeriod.startDate.astimezone( TZ) <= now: LOGGER.info('Switched tender {} to {}'.format(tender.id, 'active.tendering'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_active.tendering'})) tender.status = 'active.tendering' return elif not tender.lots and tender.status == 'active.tendering' and tender.tenderPeriod.endDate <= now: 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: 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 ['claim', 'answered', 'pending'] for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in ['claim', 'answered', 'pending'] 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 ['claim', 'answered', 'pending'] 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 ['claim', 'answered', 'pending'] and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i['status'] in ['claim', 'answered', 'pending'] 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 check_status(request): tender = request.validated['tender'] now = get_now() active_lots = [lot.id for lot in tender.lots if lot.status == 'active'] if tender.lots else [None] 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 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.pre-qualification'), extra=context_unpack(request, {'MESSAGE_ID': 'switched_tender_active.pre-qualification'})) tender.status = 'active.pre-qualification' tender.qualificationPeriod = type(tender).qualificationPeriod({'startDate': now}) remove_draft_bids(request) check_initial_bids_count(request) prepare_qualifications(request) return elif tender.status == 'active.pre-qualification.stand-still' and tender.qualificationPeriod and tender.qualificationPeriod.endDate <= now and not any([ i.status in tender.block_complaint_status for q in tender.qualifications for i in q.complaints if q.lotID in active_lots ]): 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_initial_bids_count(request) 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: check_tender_status(request) 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: check_tender_status(request) return
def validate_dateSigned(self, data, value): if value and value > get_now(): raise ValidationError( u"Contract signature date can't be in the future")
def check_status(request): tender = request.validated["tender"] now = get_now() configurator = request.content_configurator check_complaint_statuses_at_complaint_period_end(tender, now) check_cancellation_status(request, CancelTenderLot) active_lots = [lot.id for lot in tender.lots if lot.status == "active"] if tender.lots else [None] 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, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) if block_tender(request): return if (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.pre-qualification"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_active.pre-qualification"}), ) tender.status = "active.pre-qualification" tender.qualificationPeriod = type(tender).qualificationPeriod( {"startDate": now}) remove_draft_bids(request) check_initial_bids_count(request) prepare_qualifications(request) elif (tender.status == "active.pre-qualification.stand-still" and tender.qualificationPeriod and tender.qualificationPeriod.endDate <= now and not any([ i.status in tender.block_complaint_status for q in tender.qualifications for i in q.complaints if q.lotID in active_lots ])): 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_initial_bids_count(request) 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 standStillEnds: standStillEnd = max(standStillEnds) if standStillEnd <= now: check_tender_status(request) elif tender.lots and tender.status in [ "active.qualification", "active.awarded" ]: 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: check_tender_status(request) break