def test_calculate_business_date(self): date_obj = datetime(2017,10,7) delta_obj = timedelta(days=7) # Test with accelerator = 1440 context = { "procurementMethodDetails": "quick, accelerator=1440", "procurementMethodType": "negotiation" } business_date = calculate_business_date( date_obj, delta_obj, context=context, working_days=True) self.assertEqual(business_date, datetime(2017, 10, 7, 0, 7)) # Test without context and working_days business_date = calculate_business_date(date_obj, delta_obj) self.assertEqual(business_date, datetime(2017, 10, 14)) # Test with working_days and timedelta_obj > timedelta() business_date = calculate_business_date( date_obj, delta_obj, working_days=True) self.assertEqual(business_date, datetime(2017, 10, 19)) # Test with working days and timedelta_obj < timedelta() business_date = calculate_business_date( date_obj, timedelta(0), working_days=True ) self.assertEqual(business_date, datetime(2017, 10, 7)) # Another test with working days and timedelta > timedelta() date_obj = datetime(2017, 10, 15) delta_obj = timedelta(1) business_date = calculate_business_date( date_obj, delta_obj, working_days=True ) self.assertEqual(business_date, datetime(2017, 10, 18))
def check_auction_time(self): if self.auctionPeriod and self.auctionPeriod.startDate and self.auctionPeriod.shouldStartAfter \ and self.auctionPeriod.startDate > calculate_business_date(parse_date(self.auctionPeriod.shouldStartAfter), AUCTION_PERIOD_TIME, self, True): self.auctionPeriod.startDate = None for lot in self.lots: if lot.auctionPeriod and lot.auctionPeriod.startDate and lot.auctionPeriod.shouldStartAfter \ and lot.auctionPeriod.startDate > calculate_business_date(parse_date(lot.auctionPeriod.shouldStartAfter), AUCTION_PERIOD_TIME, self, True): lot.auctionPeriod.startDate = None
def check_complaint_status(request, complaint, now=None): if not now: now = get_now() if complaint.status == 'claim' and calculate_business_date(complaint.dateSubmitted, COMPLAINT_STAND_STILL_TIME, request.tender) < now: complaint.status = 'pending' complaint.type = 'complaint' complaint.dateEscalated = now elif complaint.status == 'answered' and calculate_business_date(complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME, request.tender) < now: complaint.status = complaint.resolutionType
def tender_enquiryPeriod(self): endDate = calculate_business_date(self.tenderPeriod.endDate, -ENQUIRY_PERIOD_TIME, self) return EnquiryPeriod( dict(startDate=self.tenderPeriod.startDate, endDate=endDate, invalidationDate=self.enquiryPeriod and self.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, ENQUIRY_STAND_STILL_TIME, self, True)))
def __call__(self, cls, data, period): if not data['_rev'] and calculate_business_date( get_now(), -timedelta(minutes=10)) >= period.startDate: raise ValidationError( u"tenderPeriod.startDate should be in greater than current date" ) if period and calculate_business_date( period.startDate, TENDERING_DURATION, data) > period.endDate: raise ValidationError( u"tenderPeriod should be greater than {} days".format( TENDERING_DAYS))
def validate_tenderPeriod(self, data, period): # if data['_rev'] is None when tender was created just now if not data['_rev'] and calculate_business_date( get_now(), -timedelta(minutes=10)) >= period.startDate: raise ValidationError( u"tenderPeriod.startDate should be in greater than current date" ) if period and calculate_business_date(period.startDate, TENDER_PERIOD, data) > period.endDate: raise ValidationError( u"tenderPeriod should be greater than 15 days")
def invalidate_bids_data(self): if self.auctionPeriod and self.auctionPeriod.startDate and self.auctionPeriod.shouldStartAfter \ and self.auctionPeriod.startDate > calculate_business_date(parse_date(self.auctionPeriod.shouldStartAfter), AUCTION_PERIOD_TIME, self, True): self.auctionPeriod.startDate = None for lot in self.lots: if lot.auctionPeriod and lot.auctionPeriod.startDate and lot.auctionPeriod.shouldStartAfter \ and lot.auctionPeriod.startDate > calculate_business_date(parse_date(lot.auctionPeriod.shouldStartAfter), AUCTION_PERIOD_TIME, self, True): lot.auctionPeriod.startDate = None self.enquiryPeriod.invalidationDate = get_now() for bid in self.bids: if bid.status not in ["deleted", "draft"]: bid.status = "invalid"
def __call__(self, obj, *args, **kwargs): configurator = getAdapter(obj, IContentConfigurator) enquiryPeriod_class = obj._fields['enquiryPeriod'] endDate = calculate_business_date(obj.tenderPeriod.endDate, -configurator.questions_stand_still, obj) return enquiryPeriod_class( dict(startDate=obj.tenderPeriod.startDate, endDate=endDate, invalidationDate=obj.enquiryPeriod and obj.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, configurator.enquiry_stand_still, obj, True)))
def tender_init_handler(event): """ initialization handler for openuadefence tenders """ tender = event.tender endDate = calculate_business_date(tender.tenderPeriod.endDate, -ENQUIRY_PERIOD_TIME, tender, True) tender.enquiryPeriod = EnquiryPeriod(dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date(endDate, ENQUIRY_STAND_STILL_TIME, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now
def tender_init_handler_2(event): """ initialization handler for tenders """ # import pdb; pdb.set_trace() tender = event.tender tender.tenderPeriod.endDate = calculate_business_date( tender.tenderPeriod.startDate, TENDERING_DURATION_UA, tender) tender_init_handler_ua(event)
def validate_update_tender(self): """ TODO move validators This class is inherited in openua package, but validate_update_tender function has different validators. For now, we have no way to use different validators on methods according to procedure type. """ if self.request.authenticated_role != 'auction' and self.request.validated['tender_status'] not in ['active.tendering', STAGE2_STATUS] or \ self.request.authenticated_role == 'auction' and self.request.validated['tender_status'] not in ['active.auction', 'active.qualification']: raise_operation_error( self.request, 'Can\'t {} document in current ({}) tender status'.format( OPERATIONS.get(self.request.method), self.request.validated['tender_status'])) if self.request.validated[ 'tender_status'] == 'active.tendering' and calculate_business_date( get_now(), TENDERING_EXTRA_PERIOD, self.request.validated['tender'] ) > self.request.validated['tender'].tenderPeriod.endDate: raise_operation_error( self.request, 'tenderPeriod should be extended by {0.days} days'.format( TENDERING_EXTRA_PERIOD)) if self.request.method in ['PUT', 'PATCH']: validate_tender_document_update_not_by_author_or_tender_owner( self.request) return True
def shouldStartAfter(self): if self.endDate: return tender = get_tender(self) lot = self.__parent__ if tender.status not in [ 'active.tendering', 'active.pre-qualification.stand-still', 'active.auction' ] or lot.status != 'active': return start_after = None if tender.status == 'active.tendering' and tender.tenderPeriod.endDate: start_after = calculate_business_date(tender.tenderPeriod.endDate, TENDERING_AUCTION, tender) elif self.startDate and get_now() > calc_auction_end_time( lot.numberOfBids, self.startDate): start_after = calc_auction_end_time(lot.numberOfBids, self.startDate) elif tender.qualificationPeriod and tender.qualificationPeriod.endDate: decision_dates = [ datetime.combine( complaint.dateDecision.date() + timedelta(days=3), time(0, tzinfo=complaint.dateDecision.tzinfo)) for qualification in tender.qualifications for complaint in qualification.complaints if complaint.dateDecision ] decision_dates.append(tender.qualificationPeriod.endDate) start_after = max(decision_dates) if start_after: return rounding_shouldStartAfter(start_after, tender).isoformat()
def wrapper(): return PeriodStartEndRequired({ "startDate": get_now(), "endDate": calculate_business_date(get_now(), tendering_duration) })
def validate_tender_period_extension(request): extra_period = request.content_configurator.tendering_period_extra tender = request.validated['tender'] if calculate_business_date(get_now(), extra_period, tender) > tender.tenderPeriod.endDate: raise_operation_error( request, 'tenderPeriod should be extended by {0.days} days'.format( extra_period))
def validate_update_tender(self): """ TODO move validators This class is inherited from openua package, but validate_update_tender function has different validators (check using working days). For now, we have no way to use different validators on methods according to procedure type. """ if self.request.validated['tender_status'] == 'active.tendering' and calculate_business_date(get_now(), TENDERING_EXTRA_PERIOD, self.request.validated['tender'], True) > self.request.validated['tender'].tenderPeriod.endDate: raise_operation_error(self.request, 'tenderPeriod should be extended by {0.days} working days'.format(TENDERING_EXTRA_PERIOD)) return True
def __call__(self, obj, *args, **kwargs): complaintPeriod_class = obj._fields['tenderPeriod'] normalized_end = calculate_normalized_date(obj.tenderPeriod.endDate, obj) return complaintPeriod_class( dict(startDate=obj.tenderPeriod.startDate, endDate=calculate_business_date(normalized_end, -COMPLAINT_SUBMIT_TIME, obj)))
def complaintPeriod(self): normalized_end = calculate_normalized_date(self.tenderPeriod.endDate, self) return Period( dict(startDate=self.tenderPeriod.startDate, endDate=calculate_business_date(normalized_end, -COMPLAINT_SUBMIT_TIME, self)))
def tender_init_handler(event): """ initialization handler for closeFrameworkAgreementUA tenders """ tender = event.tender config = getAdapter(tender, IContentConfigurator) endDate = calculate_business_date(tender.tenderPeriod.endDate, -config.questions_stand_still, tender) tender.enquiryPeriod = EnquiryPeriod( dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, config.enquiry_stand_still, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now
def validate_submit_claim_time(request): tender = request.context claim_submit_time = request.content_configurator.tender_claim_submit_time if get_now() > calculate_business_date(tender.tenderPeriod.endDate, -claim_submit_time, tender, True): raise_operation_error( request, 'Can submit claim not later than {0.days} days before tenderPeriod end' .format(claim_submit_time))
def validate_update_tender(self): tender = self.request.validated['tender'] if calculate_business_date(get_now(), TENDERING_EXTRA_PERIOD, tender, True) > tender.tenderPeriod.endDate: raise_operation_error( self.request, 'tenderPeriod should be extended by {0.days} working days'. format(TENDERING_EXTRA_PERIOD)) return True
def tender_init_handler(event): """ initialization handler for esco tenders """ tender = event.tender endDate = calculate_business_date(tender.tenderPeriod.endDate, -QUESTIONS_STAND_STILL, tender) tender.enquiryPeriod = EnquiryPeriod( dict(startDate=tender.tenderPeriod.startDate, endDate=endDate, invalidationDate=tender.enquiryPeriod and tender.enquiryPeriod.invalidationDate, clarificationsUntil=calculate_business_date( endDate, ENQUIRY_STAND_STILL_TIME, tender, True))) now = get_now() tender.date = now if tender.lots: for lot in tender.lots: lot.date = now check_submission_method_details(tender)
def validate_patch_tender_tenderPeriod(request): source = request.validated['data'] tender = request.validated['tender_src'] startDate = tender['tenderPeriod'].get('startDate') endDate = source['tenderPeriod'].get('endDate') if (startDate and endDate) and \ calculate_business_date(parse_date(startDate), request.content_configurator.tender_period, tender) > parse_date(endDate): raise_operation_error(request, 'tenderPeriod should last at least 3 days')
def validate_tender_period_extension(request): extra_period = request.content_configurator.tendering_period_extra tender = request.validated['tender'] if calculate_business_date(get_now(), extra_period, tender) > tender.tenderPeriod.endDate: request.errors.add( 'body', 'data', 'tenderPeriod should be extended by {0.days} days'.format( extra_period)) request.errors.status = 403 raise error_handler(request.errors)
def validate_update_tender(self): """ TODO move validators This class is inherited in openua package, but validate_update_tender function has different validators. For now, we have no way to use different validators on methods according to procedure type. """ if self.request.authenticated_role != 'auction' and self.request.validated['tender_status'] not in ['active.tendering', STAGE2_STATUS] or \ self.request.authenticated_role == 'auction' and self.request.validated['tender_status'] not in ['active.auction', 'active.qualification']: raise_operation_error(self.request, 'Can\'t {} document in current ({}) tender status'.format(OPERATIONS.get(self.request.method), self.request.validated['tender_status'])) if self.request.validated['tender_status'] == 'active.tendering' and calculate_business_date(get_now(), TENDERING_EXTRA_PERIOD, self.request.validated['tender']) > self.request.validated['tender'].tenderPeriod.endDate: raise_operation_error(self.request, 'tenderPeriod should be extended by {0.days} days'.format(TENDERING_EXTRA_PERIOD)) if self.request.method in ['PUT', 'PATCH']: validate_tender_document_update_not_by_author_or_tender_owner(self.request) return True
def shouldStartAfter(self): if self.endDate: return tender = self.__parent__ if tender.lots or tender.status not in [ 'active.tendering', 'active.pre-qualification.stand-still', 'active.auction' ]: return start_after = None if tender.status == 'active.tendering' and tender.tenderPeriod.endDate: start_after = calculate_business_date(tender.tenderPeriod.endDate, TENDERING_AUCTION, tender) elif self.startDate and get_now() > calc_auction_end_time( tender.numberOfBids, self.startDate): start_after = calc_auction_end_time(tender.numberOfBids, self.startDate) elif tender.qualificationPeriod and tender.qualificationPeriod.endDate: start_after = tender.qualificationPeriod.endDate if start_after: return rounding_shouldStartAfter(start_after, tender).isoformat()
def check_period_and_items(request, tender): agreement_items = tender.agreements[0].items if tender.agreements[ 0].items else [] agreement_items_ids = { calculate_item_identification_tuple(agreement_item) for agreement_item in agreement_items } tender_items_ids = { calculate_item_identification_tuple(tender_item) for tender_item in tender.items } if not tender_items_ids.issubset(agreement_items_ids): drop_draft_to_unsuccessful(request, tender, AGREEMENT_ITEMS) return if get_now() > calculate_business_date( tender.agreements[0].period.endDate, -request.content_configurator.agreement_expired_until, tender): drop_draft_to_unsuccessful(request, tender, AGREEMENT_EXPIRED) elif tender.agreements[0].period.startDate > tender.date: drop_draft_to_unsuccessful(request, tender, AGREEMENT_START_DATE)
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()) if award_status == 'pending' and award.status == 'active': award.complaintPeriod.endDate = calculate_business_date( get_now(), STAND_STILL_TIME, tender, True) tender.contracts.append( type(tender).contracts.model_class({ 'awardID': award.id, 'suppliers': award.suppliers, 'value': award.value, 'date': get_now(), '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': now = get_now() if award.complaintPeriod.endDate > now: award.complaintPeriod.endDate = now for j in award.complaints: if j.status not in ['invalid', 'resolved', 'declined']: j.status = 'cancelled' j.cancellationReason = 'cancelled' j.dateCanceled = 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, tender, True) add_next_award(self.request) elif award_status == 'unsuccessful' and award.status == 'cancelled' and any( [ i.status in ['claim', 'answered', 'pending', 'resolved'] 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[tender.awards.index(award):]: if i.lotID != award.lotID: continue i.complaintPeriod.endDate = now i.status = 'cancelled' for j in i.complaints: if j.status not in ['invalid', 'resolved', 'declined']: j.status = 'cancelled' j.cancellationReason = 'cancelled' j.dateCanceled = now cancelled_awards.append(i.id) for i in tender.contracts: if i.awardID in cancelled_awards: i.status = 'cancelled' add_next_award(self.request) elif self.request.authenticated_role != 'Administrator' and not ( award_status == 'pending' and award.status == 'pending'): self.request.errors.add( 'body', 'data', 'Can\'t update award in current ({}) status'.format( award_status)) self.request.errors.status = 403 raise error_handler(self.request.errors) 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_tender_status_on_active_qualification_stand_still(request): tender = request.validated['tender'] config = getAdapter(tender, IContentConfigurator) now = get_now() active_lots = [lot.id for lot in tender.lots if lot.status == 'active'] if tender.lots else [None] if not (tender.awardPeriod and tender.awardPeriod.endDate <= now and not any([ i.status in tender.block_complaint_status for a in tender.awards for i in a.complaints if a.lotID in active_lots ])): return statuses = set() if tender.lots: for lot in tender.lots: if lot.status != 'active': statuses.add(lot.status) continue active_lot_awards = [ i for i in tender.awards if i.lotID == lot.id and i.status == 'active' ] if len(active_lot_awards) < config.min_bids_number: LOGGER.info('Switched lot {} of tender {} to {}'.format( lot.id, tender.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_lot_unsuccessful'}, {'LOT_ID': lot.id})) lot.status = 'unsuccessful' statuses.add(lot.status) continue statuses.add(lot.status) else: active_awards = [i for i in tender.awards if i.status == 'active'] if len(active_awards) <= config.min_bids_count: statuses.add('unsuccessful') else: statuses.add('active.awarded') if statuses == set(['cancelled']): LOGGER.info('Switched tender {} to {}'.format(tender.id, 'cancelled'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_cancelled'})) tender.status = 'cancelled' elif not statuses.difference(set(['unsuccessful', 'cancelled'])): LOGGER.info('Switched tender {} to {}'.format(tender.id, 'unsuccessful'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_unsuccessful'})) tender.status = 'unsuccessful' else: LOGGER.info('Switched tender {} to {}'.format(tender.id, 'active.awarded'), extra=context_unpack( request, {'MESSAGE_ID': 'switched_tender_active_awarded'})) tender.status = 'active.awarded' tender.contractPeriod = {'startDate': now} tender.contractPeriod['clarificationsUntil'] = calculate_business_date( now, config.clarifications_until_period, tender, False) lots = [l for l in tender.get('lots', []) if l.status == 'active'] if lots: for lot in lots: agreement_data = generate_agreement_data(request, tender, lot) agreement = type(tender).agreements.model_class(agreement_data) agreement.__parent__ = tender tender.agreements.append(agreement) else: agreement_data = generate_agreement_data(request, tender) agreement = type(tender).agreements.model_class(agreement_data) agreement.__parent__ = tender tender.agreements.append(agreement)
def patch(self): """Tender Edit (partial) For example here is how procuring entity can change number of items to be procured and total Value of a tender: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607 HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 }, "itemsToBeProcured": [ { "quantity": 6 } ] } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "tenderID": "UA-64e93250be76435397e8c992ed4214d1", "dateModified": "2014-10-27T08:12:34.956Z", "value": { "amount": 600 }, "itemsToBeProcured": [ { "quantity": 6 } ] } } """ tender = self.context data = self.request.validated['data'] if self.request.authenticated_role == 'tender_owner' and self.request.validated['tender_status'] == 'active.tendering': if 'tenderPeriod' in data and 'endDate' in data['tenderPeriod']: self.request.validated['tender'].tenderPeriod.import_data(data['tenderPeriod']) validate_tender_period_extension(self.request) self.request.registry.notify(TenderInitializeEvent(self.request.validated['tender'])) self.request.validated['data']["enquiryPeriod"] = self.request.validated['tender'].enquiryPeriod.serialize() apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if self.request.authenticated_role == 'chronograph': check_status(self.request) elif self.request.authenticated_role == 'tender_owner' and tender.status == 'active.tendering': tender.invalidate_bids_data() elif self.request.authenticated_role == 'tender_owner' and self.request.validated['tender_status'] == 'active.pre-qualification' and tender.status == "active.pre-qualification.stand-still": active_lots = [lot.id for lot in tender.lots if lot.status == 'active'] if tender.lots else [None] if any([i['status'] in self.request.validated['tender'].block_complaint_status for q in self.request.validated['tender']['qualifications'] for i in q['complaints'] if q['lotID'] in active_lots]): raise_operation_error(self.request, 'Can\'t switch to \'active.pre-qualification.stand-still\' before resolve all complaints') if all_bids_are_reviewed(self.request): normalized_date = calculate_normalized_date(get_now(), tender, True) tender.qualificationPeriod.endDate = calculate_business_date(normalized_date, COMPLAINT_STAND_STILL, self.request.validated['tender']) tender.check_auction_time() else: raise_operation_error(self.request, 'Can\'t switch to \'active.pre-qualification.stand-still\' while not all bids are qualified') save_tender(self.request) self.LOGGER.info('Updated tender {}'.format(tender.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_patch'})) return {'data': tender.serialize(tender.status)}
def next_check(self): now = get_now() checks = [] if self.status == 'active.enquiries' and self.tenderPeriod.startDate: checks.append(self.tenderPeriod.startDate.astimezone(TZ)) elif self.status == 'active.enquiries' and self.enquiryPeriod.endDate: checks.append(self.enquiryPeriod.endDate.astimezone(TZ)) elif self.status == 'active.tendering' and self.tenderPeriod.endDate: checks.append(self.tenderPeriod.endDate.astimezone(TZ)) elif not self.lots and self.status == 'active.auction' and self.auctionPeriod and self.auctionPeriod.startDate and not self.auctionPeriod.endDate: if now < self.auctionPeriod.startDate: checks.append(self.auctionPeriod.startDate.astimezone(TZ)) elif now < calc_auction_end_time( self.numberOfBids, self.auctionPeriod.startDate).astimezone(TZ): checks.append( calc_auction_end_time( self.numberOfBids, self.auctionPeriod.startDate).astimezone(TZ)) elif self.lots and self.status == 'active.auction': for lot in self.lots: if lot.status != 'active' or not lot.auctionPeriod or not lot.auctionPeriod.startDate or lot.auctionPeriod.endDate: continue if now < lot.auctionPeriod.startDate: checks.append(lot.auctionPeriod.startDate.astimezone(TZ)) elif now < calc_auction_end_time( lot.numberOfBids, lot.auctionPeriod.startDate).astimezone(TZ): checks.append( calc_auction_end_time( lot.numberOfBids, lot.auctionPeriod.startDate).astimezone(TZ)) elif not self.lots and self.status == 'active.awarded' and not any( [i.status in self.block_complaint_status for i in self.complaints]) and not any([ i.status in self.block_complaint_status for a in self.awards for i in a.complaints ]): standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in self.awards if a.complaintPeriod.endDate ] last_award_status = self.awards[-1].status if self.awards else '' if standStillEnds and last_award_status == 'unsuccessful': checks.append(max(standStillEnds)) elif self.lots and self.status in [ 'active.qualification', 'active.awarded' ] and not any([ i.status in self.block_complaint_status and i.relatedLot is None for i in self.complaints ]): for lot in self.lots: if lot['status'] != 'active': continue lot_awards = [i for i in self.awards if i.lotID == lot.id] pending_complaints = any([ i['status'] in self.block_complaint_status and i.relatedLot == lot.id for i in self.complaints ]) pending_awards_complaints = any([ i.status in self.block_complaint_status for a in lot_awards for i in a.complaints ]) standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) for a in lot_awards if a.complaintPeriod.endDate ] last_award_status = lot_awards[-1].status if lot_awards else '' if not pending_complaints and not pending_awards_complaints and standStillEnds and last_award_status == 'unsuccessful': checks.append(max(standStillEnds)) if self.status.startswith('active'): from openprocurement.tender.core.utils import calculate_business_date for complaint in self.complaints: if complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date(complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'pending': checks.append(self.dateModified) for award in self.awards: if award.status == 'active' and not any( [i.awardID == award.id for i in self.contracts]): checks.append(award.date) for complaint in award.complaints: if complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'pending': checks.append(self.dateModified) return min(checks).isoformat() if checks else None
def tender_init_handler_2(event): """ initialization handler for tenders """ # import pdb; pdb.set_trace() tender = event.tender tender.tenderPeriod.endDate = calculate_business_date(tender.tenderPeriod.startDate, TENDERING_DURATION_UA, tender) tender_init_handler_ua(event)
def patch(self): """Post a complaint resolution """ tender = self.request.validated['tender'] data = self.request.validated['data'] # 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 tender.status == 'active.tendering' 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 tender.status == 'active.tendering' and self.context.status == 'draft' and data.get( 'status', self.context.status) == 'claim': if get_now() > calculate_business_date(tender.tenderPeriod.endDate, -CLAIM_SUBMIT_TIME, tender): raise_operation_error( self.request, 'Can submit claim not later than {0.days} days before tenderPeriod end' .format(CLAIM_SUBMIT_TIME)) apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateSubmitted = get_now() elif self.request.authenticated_role == 'complaint_owner' and tender.status == 'active.tendering' and self.context.status in [ 'draft', 'claim' ] and data.get('status', self.context.status) == 'pending': if get_now() > tender.complaintPeriod.endDate: raise_operation_error( self.request, 'Can submit complaint not later than {0.days} days before tenderPeriod end' .format(COMPLAINT_SUBMIT_TIME)) 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()) elif self.request.authenticated_role == 'complaint_owner' and self.context.status == 'answered' and data.get( 'satisfied', self.context.satisfied) is True and data.get( 'status', self.context.status) == 'resolved': apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'complaint_owner' and self.context.status == 'answered' and data.get( 'satisfied', self.context.satisfied) is False and data.get( 'status', self.context.status) == 'pending': if get_now() > tender.complaintPeriod.endDate: raise_operation_error( self.request, 'Can submit complaint not later than {0.days} days before tenderPeriod end' .format(COMPLAINT_SUBMIT_TIME)) apply_patch(self.request, save=False, src=self.context.serialize()) self.context.type = 'complaint' self.context.dateEscalated = get_now() # tender_owner elif self.request.authenticated_role == 'tender_owner' and self.context.status == 'claim' and data.get( 'status', self.context.status) == self.context.status: now = get_now() if now > tender.enquiryPeriod.clarificationsUntil: raise_operation_error( self.request, 'Can update claim only before enquiryPeriod.clarificationsUntil' ) apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'tender_owner' and self.context.status == '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': now = get_now() if now > tender.enquiryPeriod.clarificationsUntil: raise_operation_error( self.request, 'Can update claim only before enquiryPeriod.clarificationsUntil' ) if len(data.get('resolution', self.context.resolution)) < 20: raise_operation_error( self.request, 'Can\'t update complaint: resolution too short') 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 in [ 'pending', 'accepted' ]: apply_patch(self.request, save=False, src=self.context.serialize()) 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 in [ 'accepted', 'stopping' ] 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 [ 'pending', '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: raise_operation_error(self.request, 'Can\'t update complaint') 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 complaint {}'.format(self.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_complaint_patch'})) return {'data': self.context.serialize("view")}
def patch(self): """Post a complaint resolution """ tender = self.request.validated['tender'] data = self.request.validated['data'] # 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 tender.status == 'active.tendering' 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 tender.status == 'active.tendering' and self.context.status == 'draft' and data.get('status', self.context.status) == 'claim': if get_now() > calculate_business_date(tender.tenderPeriod.endDate, -CLAIM_SUBMIT_TIME, tender, True): raise_operation_error(self.request, 'Can submit claim not later than {0.days} days before tenderPeriod end'.format(CLAIM_SUBMIT_TIME)) apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateSubmitted = get_now() elif self.request.authenticated_role == 'complaint_owner' and tender.status == 'active.tendering' and self.context.status in ['draft', 'claim'] and data.get('status', self.context.status) == 'pending': if get_now() > tender.complaintPeriod.endDate: raise_operation_error(self.request, 'Can submit complaint not later than {0.days} days before tenderPeriod end'.format(COMPLAINT_SUBMIT_TIME)) 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()) elif self.request.authenticated_role == 'complaint_owner' and self.context.status == 'answered' and data.get('satisfied', self.context.satisfied) is True and data.get('status', self.context.status) == 'resolved': apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'complaint_owner' and self.context.status == 'answered' and data.get('satisfied', self.context.satisfied) is False and data.get('status', self.context.status) == 'pending': if get_now() > tender.complaintPeriod.endDate: raise_operation_error(self.request, 'Can submit complaint not later than {0.days} days before tenderPeriod end'.format(COMPLAINT_SUBMIT_TIME)) apply_patch(self.request, save=False, src=self.context.serialize()) self.context.type = 'complaint' self.context.dateEscalated = get_now() # tender_owner elif self.request.authenticated_role == 'tender_owner' and self.context.status == 'claim' and data.get('status', self.context.status) == self.context.status: now = get_now() if now > tender.enquiryPeriod.clarificationsUntil: raise_operation_error(self.request, 'Can update claim only before enquiryPeriod.clarificationsUntil') apply_patch(self.request, save=False, src=self.context.serialize()) elif self.request.authenticated_role == 'tender_owner' and self.context.status == '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': now = get_now() if now > tender.enquiryPeriod.clarificationsUntil: raise_operation_error(self.request, 'Can update claim only before enquiryPeriod.clarificationsUntil') if len(data.get('resolution', self.context.resolution)) < 20: raise_operation_error(self.request, 'Can\'t update complaint: resolution too short') 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 in ['pending', 'accepted']: apply_patch(self.request, save=False, src=self.context.serialize()) 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 in ['accepted', 'stopping'] 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 ['pending', '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: raise_operation_error(self.request, 'Can\'t update complaint') 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', 'stopping'] and tender.status in ['active.qualification', 'active.awarded']: check_tender_status(self.request) if save_tender(self.request): self.LOGGER.info('Updated tender complaint {}'.format(self.context.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_complaint_patch'})) return {'data': self.context.serialize("view")}
def validate_tender_period_extension_with_working_days(request): tender = request.context extra_period = request.content_configurator.tendering_period_extra if calculate_business_date(get_now(), extra_period, tender, True) > request.validated['tender'].tenderPeriod.endDate: raise_operation_error(request,'tenderPeriod should be extended by {0.days} working days'.format(extra_period))
def validate_update_tender(self): tender = self.request.validated['tender'] if calculate_business_date(get_now(), TENDERING_EXTRA_PERIOD, tender, True) > tender.tenderPeriod.endDate: raise_operation_error(self.request, 'tenderPeriod should be extended by {0.days} working days'.format(TENDERING_EXTRA_PERIOD)) return True
def wrapper(): return PeriodStartEndRequired({"startDate": get_now(), "endDate": calculate_business_date(get_now(), tendering_duration)})
def validate_submit_claim_time(request): tender = request.context claim_submit_time = request.content_configurator.tender_claim_submit_time if get_now() > calculate_business_date(tender.tenderPeriod.endDate, -claim_submit_time, tender, True): raise_operation_error(request,'Can submit claim not later than {0.days} days before tenderPeriod end'.format(claim_submit_time))
def patch(self): """Tender Edit (partial) For example here is how procuring entity can change number of items to be procured and total Value of a tender: .. sourcecode:: http PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607 HTTP/1.1 Host: example.com Accept: application/json { "data": { "value": { "amount": 600 }, "itemsToBeProcured": [ { "quantity": 6 } ] } } And here is the response to be expected: .. sourcecode:: http HTTP/1.0 200 OK Content-Type: application/json { "data": { "id": "4879d3f8ee2443169b5fbbc9f89fa607", "tenderID": "UA-64e93250be76435397e8c992ed4214d1", "dateModified": "2014-10-27T08:12:34.956Z", "value": { "amount": 600 }, "itemsToBeProcured": [ { "quantity": 6 } ] } } """ tender = self.context data = self.request.validated['data'] if self.request.authenticated_role == 'tender_owner' and \ self.request.validated['tender_status'] in ['active.tendering', STAGE2_STATUS]: if 'tenderPeriod' in data and 'endDate' in data['tenderPeriod']: self.request.validated['tender'].tenderPeriod.import_data(data['tenderPeriod']) validate_tender_period_extension(self.request) self.request.registry.notify(TenderInitializeEvent(self.request.validated['tender'])) self.request.validated['data']["enquiryPeriod"] = self.request.validated['tender'].enquiryPeriod.serialize() apply_patch(self.request, save=False, src=self.request.validated['tender_src']) if self.request.authenticated_role == 'chronograph': check_status_eu(self.request) elif self.request.authenticated_role == 'tender_owner' and tender.status == 'active.tendering': tender.invalidate_bids_data() elif self.request.authenticated_role == 'tender_owner' and \ self.request.validated['tender_status'] == 'active.pre-qualification' and \ tender.status == "active.pre-qualification.stand-still": if all_bids_are_reviewed(self.request): tender.qualificationPeriod.endDate = calculate_business_date(get_now(), COMPLAINT_STAND_STILL, self.request.validated['tender']) tender.check_auction_time() else: raise_operation_error(self.request, 'Can\'t switch to \'active.pre-qualification.stand-still\' while not all bids are qualified') save_tender(self.request) self.LOGGER.info('Updated tender {}'.format(tender.id), extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_patch'})) return {'data': tender.serialize(tender.status)}