def cancel_lot(self, cancellation=None): if not cancellation: cancellation = self.context tender = self.request.validated['tender'] [ setattr(i, 'status', 'cancelled') for i in tender.lots if i.id == cancellation.relatedLot ] statuses = set([lot.status for lot in tender.lots]) if statuses == set(['cancelled']): self.cancel_tender() elif not statuses.difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' elif not statuses.difference( set(['complete', 'unsuccessful', 'cancelled'])): tender.status = 'complete' if tender.status == 'active.auction' and all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active' ]): configurator = self.request.content_configurator add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key)
def add_next_award_method(request): configurator = request.content_configurator add_next_award( request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, )
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated["tender_src"]) if all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated["tender"].lots if i.status == "active" ]): configurator = self.request.content_configurator add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) if save_tender(self.request): self.LOGGER.info("Report auction results", extra=context_unpack( self.request, {"MESSAGE_ID": "tender_lot_auction_post"})) return { "data": self.request.validated["tender"].serialize( self.request.validated["tender"].status) }
def cancel_lot(self, cancellation=None): if not cancellation: cancellation = self.context tender = self.request.validated['tender'] [ setattr(i, 'status', 'cancelled') for i in tender.lots if i.id == cancellation.relatedLot ] cancelled_lots = [i.id for i in tender.lots if i.status == 'cancelled'] cancelled_items = [ i.id for i in tender.items if i.relatedLot in cancelled_lots ] cancelled_features = [ i.code for i in (tender.features or []) if i.featureOf == 'lot' and i.relatedItem in cancelled_lots or i.featureOf == 'item' and i.relatedItem in cancelled_items ] if tender.status in [ 'active.tendering', 'active.pre-qualification', 'active.pre-qualification.stand-still', 'active.auction' ]: for bid in tender.bids: if tender.status == "active.tendering": bid.documents = [ i for i in bid.documents if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots ] bid.financialDocuments = [ i for i in bid.financialDocuments if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots ] bid.parameters = [ i for i in bid.parameters if i.code not in cancelled_features ] bid.lotValues = [ i for i in bid.lotValues if i.relatedLot not in cancelled_lots ] if not bid.lotValues: bid.status = 'invalid' if tender.status == 'active.tendering' and bid.status in [ 'pending', 'active' ] else 'invalid.pre-qualification' for qualification in tender.qualifications: if qualification.lotID in cancelled_lots: qualification.status = 'cancelled' statuses = set([lot.status for lot in tender.lots]) if statuses == set(['cancelled']): self.cancel_tender() elif not statuses.difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' elif not statuses.difference( set(['complete', 'unsuccessful', 'cancelled'])): tender.status = 'complete' if tender.status == 'active.auction' and all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active' ]): add_next_award(self.request)
def check_bids(request): tender = request.validated['tender'] if tender.lots: [ setattr(i.auctionPeriod, 'startDate', None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod and i.auctionPeriod.startDate ] [ setattr(i, 'status', 'unsuccessful') for i in tender.lots if i.numberOfBids == 0 and i.status == 'active' ] numberOfBids_in_active_lots = [ i.numberOfBids for i in tender.lots if i.status == 'active' ] if numberOfBids_in_active_lots and max( numberOfBids_in_active_lots) < 2: add_next_award(request) if not set([i.status for i in tender.lots]).difference( set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' else: if tender.numberOfBids < 2 and tender.auctionPeriod and tender.auctionPeriod.startDate: tender.auctionPeriod.startDate = None if tender.numberOfBids == 0: tender.status = 'unsuccessful' if tender.numberOfBids == 1: add_next_award(request)
def check_bids(request): tender = request.validated["tender"] if tender.lots: [ setattr(i.auctionPeriod, "startDate", None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod and i.auctionPeriod.startDate ] [ setattr(i, "status", "unsuccessful") for i in tender.lots if i.numberOfBids == 0 and i.status == "active" ] numberOfBids_in_active_lots = [ i.numberOfBids for i in tender.lots if i.status == "active" ] if numberOfBids_in_active_lots and max( numberOfBids_in_active_lots) < 2: add_next_award(request) if not set([i.status for i in tender.lots]).difference( set(["unsuccessful", "cancelled"])): tender.status = "unsuccessful" else: if tender.numberOfBids < 2 and tender.auctionPeriod and tender.auctionPeriod.startDate: tender.auctionPeriod.startDate = None if tender.numberOfBids == 0: tender.status = "unsuccessful" if tender.numberOfBids == 1: add_next_award(request)
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if all([i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.numberOfBids > 1 and i.status == 'active']): add_next_award(self.request) if save_tender(self.request): self.LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_lot_auction_post'})) return {'data': self.request.validated['tender'].serialize(self.request.validated['tender'].status)}
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if all([i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active']): configurator = self.request.content_configurator add_next_award(self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key) if save_tender(self.request): self.LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_lot_auction_post'})) return {'data': self.request.validated['tender'].serialize(self.request.validated['tender'].status)}
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated["tender_src"]) if all( [ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated["tender"].lots if i.status == "active" ] ): add_next_award(self.request) if save_tender(self.request): self.LOGGER.info( "Report auction results", extra=context_unpack(self.request, {"MESSAGE_ID": "tender_lot_auction_post"}) ) return {"data": self.request.validated["tender"].serialize(self.request.validated["tender"].status)}
def check_bids(request): tender = request.validated['tender'] if tender.lots: [setattr(i.auctionPeriod, 'startDate', None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod and i.auctionPeriod.startDate] [setattr(i, 'status', 'unsuccessful') for i in tender.lots if i.numberOfBids == 0 and i.status == 'active'] numberOfBids_in_active_lots = [i.numberOfBids for i in tender.lots if i.status == 'active'] if numberOfBids_in_active_lots and max(numberOfBids_in_active_lots) < 2: add_next_award(request) if not set([i.status for i in tender.lots]).difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' else: if tender.numberOfBids < 2 and tender.auctionPeriod and tender.auctionPeriod.startDate: tender.auctionPeriod.startDate = None if tender.numberOfBids == 0: tender.status = 'unsuccessful' if tender.numberOfBids == 1: add_next_award(request)
def cancel_lot(self, cancellation=None): if not cancellation: cancellation = self.context tender = self.request.validated['tender'] [setattr(i, 'status', 'cancelled') for i in tender.lots if i.id == cancellation.relatedLot] statuses = set([lot.status for lot in tender.lots]) if statuses == set(['cancelled']): self.cancel_tender() elif not statuses.difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' elif not statuses.difference(set(['complete', 'unsuccessful', 'cancelled'])): tender.status = 'complete' if tender.status == 'active.auction' and all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active' ]): add_next_award(self.request)
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated["tender_src"]) if all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated["tender"].lots if i.numberOfBids > 1 and i.status == "active" ]): add_next_award(self.request) if save_tender(self.request): self.LOGGER.info("Report auction results", extra=context_unpack( self.request, {"MESSAGE_ID": "tender_lot_auction_post"})) return { "data": self.request.validated["tender"].serialize( self.request.validated["tender"].status) }
def post(self): """Report auction results for lot. """ apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.numberOfBids > 1 and i.status == 'active' ]): add_next_award(self.request) if save_tender(self.request): self.LOGGER.info('Report auction results', extra=context_unpack( self.request, {'MESSAGE_ID': 'tender_lot_auction_post'})) return { 'data': self.request.validated['tender'].serialize( self.request.validated['tender'].status) }
def cancel_lot(self, cancellation=None): if not cancellation: cancellation = self.context tender = self.request.validated['tender'] [setattr(i, 'status', 'cancelled') for i in tender.lots if i.id == cancellation.relatedLot] cancelled_lots = [i.id for i in tender.lots if i.status == 'cancelled'] cancelled_items = [i.id for i in tender.items if i.relatedLot in cancelled_lots] cancelled_features = [ i.code for i in (tender.features or []) if i.featureOf == 'lot' and i.relatedItem in cancelled_lots or i.featureOf == 'item' and i.relatedItem in cancelled_items ] if tender.status in ['active.tendering', 'active.pre-qualification', 'active.pre-qualification.stand-still', 'active.auction']: for bid in tender.bids: if tender.status == "active.tendering": bid.documents = [i for i in bid.documents if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots] bid.financialDocuments = [i for i in bid.financialDocuments if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots] bid.eligibilityDocuments = [i for i in bid.eligibilityDocuments if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots] bid.qualificationDocuments = [i for i in bid.qualificationDocuments if i.documentOf != 'lot' or i.relatedItem not in cancelled_lots] bid.parameters = [i for i in bid.parameters if i.code not in cancelled_features] bid.lotValues = [i for i in bid.lotValues if i.relatedLot not in cancelled_lots] if not bid.lotValues: bid.status = 'invalid' if tender.status == 'active.tendering' and bid.status in ['pending', 'active'] else 'invalid.pre-qualification' for qualification in tender.qualifications: if qualification.lotID in cancelled_lots: qualification.status = 'cancelled' statuses = set([lot.status for lot in tender.lots]) if statuses == set(['cancelled']): self.cancel_tender() elif not statuses.difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' elif not statuses.difference(set(['complete', 'unsuccessful', 'cancelled'])): tender.status = 'complete' if tender.status == 'active.auction' and all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active' ]): add_next_award(self.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 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 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 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
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 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 check_status(request): tender = request.validated["tender"] now = get_now() check_complaint_statuses_at_complaint_period_end(tender, now) check_cancellation_status(request) if cancellation_block_tender(tender): return for award in tender.awards: if award.status == "active" and not any( [i.awardID == award.id for i in tender.contracts]): add_contract(request, award, now) add_next_award(request) if (not tender.lots and tender.status == "active.tendering" and tender.tenderPeriod.endDate <= now and not has_unanswered_complaints(tender) and not has_unanswered_questions(tender)): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info( "Switched tender {} to {}".format(tender["id"], "active.auction"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_active.auction"}), ) tender.status = "active.auction" check_bids(request) if tender.numberOfBids < 2 and tender.auctionPeriod: tender.auctionPeriod.startDate = None return elif (tender.lots and tender.status == "active.tendering" and tender.tenderPeriod.endDate <= now and not has_unanswered_complaints(tender) and not has_unanswered_questions(tender)): for complaint in tender.complaints: check_complaint_status(request, complaint) LOGGER.info( "Switched tender {} to {}".format(tender["id"], "active.auction"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_active.auction"}), ) tender.status = "active.auction" check_bids(request) [ setattr(i.auctionPeriod, "startDate", None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod ] return elif not tender.lots and tender.status == "active.awarded": standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in tender.awards if a.complaintPeriod and a.complaintPeriod.endDate ] if not standStillEnds: return standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i["status"] in tender.block_complaint_status for i in tender.complaints ]) pending_awards_complaints = any([ i["status"] in tender.block_complaint_status for a in tender.awards for i in a.complaints ]) awarded = any([i["status"] == "active" for i in tender.awards]) if not pending_complaints and not pending_awards_complaints and not awarded: LOGGER.info( "Switched tender {} to {}".format(tender.id, "unsuccessful"), extra=context_unpack( request, {"MESSAGE_ID": "switched_tender_unsuccessful"}), ) check_tender_status(request) return elif tender.lots and tender.status in [ "active.qualification", "active.awarded" ]: if any([ i["status"] in tender.block_complaint_status and i.relatedLot is None for i in tender.complaints ]): return for lot in tender.lots: if lot["status"] != "active": continue lot_awards = [i for i in tender.awards if i.lotID == lot.id] standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in lot_awards if a.complaintPeriod and a.complaintPeriod.endDate ] if not standStillEnds: continue standStillEnd = max(standStillEnds) if standStillEnd <= now: pending_complaints = any([ i["status"] in tender.block_complaint_status and i.relatedLot == lot.id for i in tender.complaints ]) pending_awards_complaints = any([ i["status"] in tender.block_complaint_status for a in lot_awards for i in a.complaints ]) awarded = any([i["status"] == "active" for i in lot_awards]) if not pending_complaints and not pending_awards_complaints and not awarded: LOGGER.info( "Switched lot {} of tender {} to {}".format( lot["id"], tender.id, "unsuccessful"), extra=context_unpack( request, {"MESSAGE_ID": "switched_lot_unsuccessful"}, {"LOT_ID": lot["id"]}), ) check_tender_status(request)
def collection_post(self): """Report auction results. Report auction results ---------------------- Example request to report auction results: .. sourcecode:: http POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/auction HTTP/1.1 Host: example.com Accept: application/json { "data": { "dateModified": "2014-10-27T08:06:58.158Z", "bids": [ { "value": { "amount": 400, "currency": "UAH" } }, { "value": { "amount": 385, "currency": "UAH" } } ] } } This is what one should expect in response: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "dateModified": "2014-10-27T08:06:58.158Z", "bids": [ { "value": { "amount": 400, "currency": "UAH", "valueAddedTaxIncluded": true } }, { "value": { "amount": 385, "currency": "UAH", "valueAddedTaxIncluded": true } } ], "minimalStep":{ "amount": 35, "currency": "UAH" }, "tenderPeriod":{ "startDate": "2014-11-04T08:00:00" } } } """ apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if all([ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.status == 'active' ]): add_next_award(self.request, reverse=self.request.content_configurator. reverse_awarding_criteria) if save_tender(self.request): self.LOGGER.info( 'Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_auction_post'})) return { 'data': self.request.validated['tender'].serialize( self.request.validated['tender'].status) }
def patch(self): """Update of award Example request to change the award: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 } } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "date": "2014-10-28T11:44:17.947Z", "status": "active", "suppliers": [ { "id": { "name": "Державне управління справами", "scheme": "https://ns.openprocurement.org/ua/edrpou", "uid": "00037256", "uri": "http://www.dus.gov.ua/" }, "address": { "countryName": "Україна", "postalCode": "01220", "region": "м. Київ", "locality": "м. Київ", "streetAddress": "вул. Банкова, 11, корпус 1" } } ], "value": { "amount": 600, "currency": "UAH", "valueAddedTaxIncluded": true } } } """ tender = self.request.validated["tender"] award = self.request.context award_status = award.status apply_patch(self.request, save=False, src=self.request.context.serialize()) configurator = self.request.content_configurator now = get_now() if award_status != award.status and award.status in [ "active", "unsuccessful" ]: if award.complaintPeriod: award.complaintPeriod.startDate = now else: award.complaintPeriod = {"startDate": now.isoformat()} if award_status == "pending" and award.status == "active": award.complaintPeriod.endDate = calculate_complaint_business_date( now, STAND_STILL_TIME, tender) add_contract(self.request, award, now) add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif (award_status == "active" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints])): cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif award_status == "active" and award.status == "cancelled": if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now for i in tender.contracts: if i.awardID == award.id: i.status = "cancelled" add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif award_status == "pending" and award.status == "unsuccessful": award.complaintPeriod.endDate = calculate_complaint_business_date( get_now(), STAND_STILL_TIME, tender) add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif (award_status == "unsuccessful" and award.status == "cancelled" and any([i.status == "satisfied" for i in award.complaints])): if tender.status == "active.awarded": tender.status = "active.qualification" tender.awardPeriod.endDate = None if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now cancelled_awards = [] for i in tender.awards: if i.lotID != award.lotID: continue if not i.complaintPeriod.endDate or i.complaintPeriod.endDate > now: i.complaintPeriod.endDate = now i.status = "cancelled" cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = "cancelled" add_next_award( self.request, reverse=configurator.reverse_awarding_criteria, awarding_criteria_key=configurator.awarding_criteria_key, ) elif self.request.authenticated_role != "Administrator" and not ( award_status == "pending" and award.status == "pending"): raise_operation_error( self.request, "Can't update award in current ({}) status".format( award_status)) if save_tender(self.request): self.LOGGER.info( "Updated tender award {}".format(self.request.context.id), extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_patch"}), ) return {"data": award.serialize("view")}
def collection_post(self): """Report auction results. Report auction results ---------------------- Example request to report auction results: .. sourcecode:: http POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/auction HTTP/1.1 Host: example.com Accept: application/json { "data": { "dateModified": "2014-10-27T08:06:58.158Z", "bids": [ { "value": { "amount": 400, "currency": "UAH" } }, { "value": { "amount": 385, "currency": "UAH" } } ] } } This is what one should expect in response: .. sourcecode:: http HTTP/1.1 200 OK Content-Type: application/json { "data": { "dateModified": "2014-10-27T08:06:58.158Z", "bids": [ { "value": { "amount": 400, "currency": "UAH", "valueAddedTaxIncluded": true } }, { "value": { "amount": 385, "currency": "UAH", "valueAddedTaxIncluded": true } } ], "minimalStep":{ "amount": 35, "currency": "UAH" }, "tenderPeriod":{ "startDate": "2014-11-04T08:00:00" } } } """ apply_patch(self.request, save=False, src=self.request.validated["tender_src"]) if all( [ i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated["tender"].lots if i.status == "active" ] ): add_next_award(self.request) if save_tender(self.request): self.LOGGER.info( "Report auction results", extra=context_unpack(self.request, {"MESSAGE_ID": "tender_auction_post"}) ) return {"data": self.request.validated["tender"].serialize(self.request.validated["tender"].status)}