class DGFFinancialBid(DGFOtherBid): class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified', 'eligible'), } documents = ListType(ModelType(Document), default=list(), validators=[validate_disallow_dgfPlatformLegalDetails]) tenderers = ListType(ModelType(FinancialOrganization), required=True, min_size=1, max_size=1) eligible = BooleanType(required=True, choices=[True])
class Bid(BaseBid): class Options: roles = { 'create': whitelist('tenderers', 'status', 'qualified', 'eligible'), 'edit': whitelist('status', 'tenderers'), } status = StringType(choices=['active', 'draft', 'invalid'], default='active') qualified = BooleanType(required=True, choices=[True]) documents = ListType(ModelType(AppraisalBidDocument), default=list()) eligible = BooleanType(required=True, choices=[True]) def validate_value(self, data, value): if isinstance(data['__parent__'], Model): auction = data['__parent__'] if not value: return if auction.get('value').currency != value.currency: raise ValidationError(u"currency of bid should be identical to currency of value of auction") if auction.get('value').valueAddedTaxIncluded != value.valueAddedTaxIncluded: raise ValidationError(u"valueAddedTaxIncluded of bid should be identical to valueAddedTaxIncluded of value of auction") @serializable(serialized_name="participationUrl", serialize_when_none=False) def participation_url(self): if not self.participationUrl and self.status == "active": request = get_auction(self).__parent__.request url = generate_auction_url(request, bid_id=str(self.id)) return url
class DGFFinancialAssets(DGFOtherAssets): """Data regarding auction process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.""" _internal_type = "dgfFinancialAssets" bids = ListType(ModelType(DGFFinancialBid), default=list()) eligibilityCriteria = StringType(default=DGF_ELIGIBILITY_CRITERIA['ua']) eligibilityCriteria_en = StringType(default=DGF_ELIGIBILITY_CRITERIA['en']) eligibilityCriteria_ru = StringType(default=DGF_ELIGIBILITY_CRITERIA['ru'])
class DGFOtherBid(BaseBid): class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified'), } status = StringType(choices=['active', 'draft', 'invalid'], default='active') documents = ListType(ModelType(Document), default=list(), validators=[validate_disallow_dgfPlatformLegalDetails]) qualified = BooleanType(required=True, choices=[True])
class Bid(BaseBid): class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified'), } status = StringType(choices=['active', 'draft', 'invalid'], default='active') tenderers = ListType(ModelType(Organization), required=True, min_size=1, max_size=1) documents = ListType(ModelType(Document), default=list()) qualified = BooleanType(required=True, choices=[True]) @bids_validation_wrapper def validate_value(self, data, value): BaseBid._validator_functions['value'](self, data, value)
class SwiftsureAuction(BaseAuction): """Data regarding auction process Publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners. """ class Options: roles = swiftsure_auction_roles _internal_type = "swiftsure" awards = ListType(ModelType(Award), default=list()) # A list of all the companies who entered submissions for the auction. bids = ListType(ModelType(Bid), default=list()) cancellations = ListType(ModelType(swiftsureCancellation), default=list()) complaints = ListType(ComplaintModelType(Complaint), default=list()) contracts = ListType(ModelType(Contract), default=list()) merchandisingObject = MD5Type() # All documents and attachments related to the auction. documents = ListType(ModelType(swiftsureDocument), default=list()) # The period during which enquiries may be made and will be answered. enquiryPeriod = ModelType(Period) # The period when the auction is open for submissions. The end date is the # closing date for auction submissions. tenderPeriod = ModelType(Period) tenderAttempts = IntType(choices=[1, 2, 3, 4, 5, 6, 7, 8]) auctionPeriod = ModelType(AuctionAuctionPeriod, required=True, default={}) status = StringType(choices=SWIFTSURE_STATUSES, default=SWIFTSURE_DEFAULT_STATUS) features = ListType( ModelType(Feature), validators=[ validate_features_uniq, validate_not_available]) lots = ListType( ModelType(Lot), default=list(), validators=[ validate_lots_uniq, validate_not_available]) items = ListType( ModelType(LokiItem), default=list(), validators=[validate_items_uniq], min_size=1) suspended = BooleanType() registrationFee = ModelType(Guarantee) bankAccount = ModelType(BankAccount) auctionParameters = ModelType(AuctionParameters) minNumberOfQualifiedBids = IntType(choices=[1], default=1) procuringEntity = ModelType(SwiftsureProcuringEntity, required=True) contractTerms = ModelType( ContractTerms, validators=[validate_contract_type]) def __acl__(self): return [ (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction_award'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'upload_auction_documents'), ] def get_role(self): root = self.__parent__ request = root.request if request.authenticated_role == 'Administrator': role = 'Administrator' elif request.authenticated_role == 'chronograph': role = 'chronograph' elif request.authenticated_role == 'auction': role = 'auction_{}'.format(request.method.lower()) elif request.authenticated_role == 'convoy': role = 'convoy' else: role = 'edit_{}'.format(request.context.status) return role def initialize(self): if not self.enquiryPeriod: self.enquiryPeriod = type(self).enquiryPeriod.model_class() if not self.tenderPeriod: self.tenderPeriod = type(self).tenderPeriod.model_class() now = get_now() start_date = TZ.localize( self.auctionPeriod.startDate.replace( tzinfo=None)) self.auctionPeriod.startDate = None self.auctionPeriod.endDate = None self.tenderPeriod.startDate = self.enquiryPeriod.startDate = now pause_between_periods = start_date - ( start_date.replace(hour=20, minute=0, second=0, microsecond=0) - # set period end at 19:30-20:30 to reduce system load timedelta(days=1, minutes=randint(-30, 30)) ) end_date = calculate_business_date( start_date, -pause_between_periods, self) self.enquiryPeriod.endDate = end_date self.tenderPeriod.endDate = self.enquiryPeriod.endDate self.date = now if self.lots: for lot in self.lots: lot.date = now def validate_value(self, data, value): if value.currency != u'UAH': raise ValidationError(u"currency should be only UAH") def validate_merchandisingObject(self, data, merchandisingObject): if data['status'] == 'pending.activation' and not merchandisingObject: raise ValidationError(u'This field is required.') @serializable(serialize_when_none=False) def next_check(self): if self.suspended: return None now = get_now() checks = [] if ( self.status == 'active.tendering' and self.tenderPeriod 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)) # Use next_check part from awarding 2.0 request = get_request_from_root(self) if request is not None: awarding_check = request.registry.getAdapter( self, IAwardingNextCheck).add_awarding_checks(self) if awarding_check is not None: checks.append(awarding_check) if self.status.startswith('active'): for complaint in self.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date( complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) for award in self.awards: for complaint in award.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date( complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) return min(checks).isoformat() if checks else None
class ProcuringEntity(flashProcuringEntity): identifier = ModelType(Identifier, required=True) additionalIdentifiers = ListType(ModelType(Identifier))
class Auction(BaseAuction): """Data regarding auction process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.""" class Options: roles = { 'create': create_role, 'edit_active.tendering': (blacklist('enquiryPeriod', 'tenderPeriod', 'rectificationPeriod', 'auction_value', 'auction_minimalStep', 'auction_guarantee', 'eligibilityCriteria', 'eligibilityCriteria_en', 'eligibilityCriteria_ru', 'minNumberOfQualifiedBids') + edit_role), 'Administrator': (whitelist('rectificationPeriod') + Administrator_role), } _internal_type = "propertyLease" awards = ListType(ModelType(Award), default=list()) bids = ListType(ModelType(Bid), default=list( )) # A list of all the companies who entered submissions for the auction. cancellations = ListType(ModelType(Cancellation), default=list()) complaints = ListType(ModelType(Complaint), default=list()) contracts = ListType(ModelType(Contract), default=list()) lotIdentifier = StringType() documents = ListType(ModelType(Document), default=list( )) # All documents and attachments related to the auction. enquiryPeriod = ModelType( Period ) # The period during which enquiries may be made and will be answered. rectificationPeriod = ModelType( RectificationPeriod ) # The period during which editing of main procedure fields are allowed tenderPeriod = ModelType( Period ) # The period when the auction is open for submissions. The end date is the closing date for auction submissions. tenderAttempts = IntType(choices=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) auctionPeriod = ModelType(AuctionAuctionPeriod, required=True, default={}) procurementMethodType = StringType() procuringEntity = ModelType(ProcuringEntity, required=True) status = StringType(choices=[ 'draft', 'active.tendering', 'active.auction', 'active.qualification', 'active.awarded', 'complete', 'cancelled', 'unsuccessful' ], default='active.tendering') questions = ListType(ModelType(Question), default=list()) features = ListType( ModelType(Feature), validators=[validate_features_uniq, validate_not_available]) lots = ListType(ModelType(Lot), default=list(), validators=[validate_lots_uniq, validate_not_available]) items = ListType(ModelType(PropertyItem), required=True, min_size=1, validators=[validate_items_uniq]) minNumberOfQualifiedBids = IntType(choices=[1, 2], default=2) contractTerms = ModelType(ContractTerms, required=True) def __acl__(self): return [ (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction_award'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'upload_auction_documents'), ] def initialize(self): # TODO: get rid of this method pass def validate_tenderPeriod(self, data, period): if not (period and period.startDate and period.endDate): return if get_auction_creation_date(data) < MINIMAL_EXPOSITION_REQUIRED_FROM: return if calculate_business_date(period.startDate, MINIMAL_EXPOSITION_PERIOD, data) > period.endDate: raise ValidationError( u"tenderPeriod should be greater than 6 days") def validate_rectificationPeriod(self, data, period): if not (period and period.startDate) or not period.endDate: return if period.endDate > TZ.localize( calculate_business_date(data['tenderPeriod']['endDate'], -MINIMAL_PERIOD_FROM_RECTIFICATION_END, data).replace(tzinfo=None)): raise ValidationError( u"rectificationPeriod.endDate should come at least 5 working days earlier than tenderPeriod.endDate" ) def validate_value(self, data, value): if value.currency != u'UAH': raise ValidationError(u"currency should be only UAH") def validate_lotIdentifier(self, data, lotIdentifier): if not lotIdentifier: if get_auction_creation_date(data) > DGF_ID_REQUIRED_FROM: raise ValidationError(u'This field is required.') @serializable(serialize_when_none=False) def next_check(self): now = get_now() checks = [] if self.status == 'active.tendering' and self.tenderPeriod 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)) # Use next_check part from awarding request = get_request_from_root(self) if request is not None: awarding_check = request.registry.getAdapter( self, IAwardingNextCheck).add_awarding_checks(self) if awarding_check is not None: checks.append(awarding_check) if self.status.startswith('active'): from openprocurement.auctions.core.utils import calculate_business_date for complaint in self.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date(complaint.dateSubmitted, COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date(complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME, self)) for award in self.awards: for complaint in award.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date( complaint.dateSubmitted, COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, COMPLAINT_STAND_STILL_TIME, self)) return min(checks).isoformat() if checks else None
class LeaseTerms(Model): leaseDuration = IsoDurationType(required=True) taxHolidays = ListType(ModelType(TaxHolidays)) escalationClauses = ListType(ModelType(EscalationClauses))
class Cancellation(BaseCancellation): documents = ListType(ModelType(Document), default=list())
class AppraisalCancellation(dgfCancellation): documents = ListType( ModelType(AppraisalCancellationDocument), default=list(), )
class AppraisalAuction(BaseAuction): """Data regarding auction process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.""" class Options: roles = appraisal_auction_roles _internal_type = "appraisal" description = StringType(required=True) awards = ListType(ModelType(AppraisalAward), default=list()) cancellations = ListType(ModelType(AppraisalCancellation), default=list()) complaints = ListType(ComplaintModelType(Complaint), default=list()) contracts = ListType(ModelType(AppraisalContract), default=list()) documents = ListType(ModelType(AppraisalDocument), default=list( )) # All documents and attachments related to the auction. enquiryPeriod = ModelType( Period ) # The period during which enquiries may be made and will be answered. tenderPeriod = ModelType( Period ) # The period when the auction is open for submissions. The end date is the closing date for auction submissions. rectificationPeriod = ModelType(RectificationPeriod) tenderAttempts = IntType(choices=[1, 2, 3, 4, 5, 6, 7, 8]) status = StringType(choices=AUCTION_STATUSES, default='draft') features = ListType( ModelType(Feature), validators=[validate_features_uniq, validate_not_available]) lots = ListType(ModelType(Lot), default=list(), validators=[validate_lots_uniq, validate_not_available]) items = ListType(ModelType(AppraisalItem), required=True, min_size=1, validators=[validate_items_uniq]) suspended = BooleanType() bids = ListType(ModelType(Bid), default=list( )) # A list of all the companies who entered submissions for the auction. auctionPeriod = ModelType(AuctionAuctionPeriod, required=True, default={}) auctionParameters = ModelType(AppraisalAuctionParameters) minimalStep = ModelType(Value) registrationFee = ModelType(Guarantee, required=True) guarantee = ModelType(Guarantee, required=True) bankAccount = ModelType(BankAccount) procuringEntity = ModelType(SwiftsureProcuringEntity, required=True) lotIdentifier = StringType(required=True) def __acl__(self): return [ (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction_award'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'upload_auction_documents'), ] def get_role(self): root = self.__parent__ request = root.request if request.authenticated_role == 'Administrator': role = 'Administrator' elif request.authenticated_role == 'chronograph': role = 'chronograph' elif request.authenticated_role == 'auction': role = 'auction_{}'.format(request.method.lower()) elif request.authenticated_role == 'convoy': role = 'convoy' elif request.authenticated_role == 'concierge': role = 'concierge' else: role = 'edit_{}'.format(request.context.status) rectification_period_exists = self.rectification_period.endDate is not None rectification_period_not_finished = rectification_period_exists and get_now( ) < self.rectification_period.endDate if request.context.status == 'active.tendering' and rectification_period_not_finished: role += '_during_rectification_period' return role def initialize(self): pass def validate_value(self, data, value): if value.currency != u'UAH': raise ValidationError(u"currency should be only UAH") def validate_tenderPeriod(self, data, value): if value and value.get('startDate') and value.get('endDate'): min_end_date_limit = calculate_business_date( value['startDate'], timedelta(days=MIN_TENDER_PERIOD_DAYS_AMOUNT), data, working_days=True) if value['endDate'] < min_end_date_limit: raise ValidationError( u"tenderPeriod should be at least {} working days".format( MIN_TENDER_PERIOD_DAYS_AMOUNT)) @serializable(serialized_name="minimalStep", type=ModelType(Value)) def auction_minimalStep(self): return Value(dict(amount=0)) @serializable(serialized_name="rectificationPeriod", type=ModelType(RectificationPeriod), serialize_when_none=False) def rectification_period(self): if self.tenderPeriod: self.rectificationPeriod = RectificationPeriod( ) if not self.rectificationPeriod else self.rectificationPeriod self.rectificationPeriod.startDate = self.tenderPeriod.startDate # for a new type of procedure we should allow tenderPeriod of 1 day. # In this case rectificationPeriod does not exist. But if rectificationPeriod does not exist # procedure is editable. To make it unchangeable, # we should create rectificationPeriod with startDate == endDate == tenderPeriod.startDate five_working_days_after_start_date = calculate_business_date( self.tenderPeriod.startDate, timedelta(days=5), None, working_days=True) if self.tenderPeriod.endDate < five_working_days_after_start_date: self.rectificationPeriod.endDate = self.rectificationPeriod.startDate return self.rectificationPeriod self.rectificationPeriod.endDate = calculate_business_date( self.tenderPeriod.endDate.astimezone(TZ), -timedelta(days=5), self, working_days=True) if self.rectificationPeriod.startDate > self.rectificationPeriod.endDate: self.rectificationPeriod.startDate = self.rectificationPeriod.endDate = None return self.rectificationPeriod @serializable(serialized_name="tenderPeriod", type=ModelType(Period)) def tender_period(self): if self.tenderPeriod and self.auctionPeriod.startDate: end_date = calculate_business_date(self.auctionPeriod.startDate, DUTCH_PERIOD, self) if SANDBOX_MODE and self.submissionMethodDetails and 'quick' in self.submissionMethodDetails: end_date = self.auctionPeriod.startDate + QUICK_DUTCH_PERIOD if self.auctionPeriod.endDate and self.auctionPeriod.endDate <= self.tenderPeriod.endDate: end_date = self.auctionPeriod.endDate.astimezone(TZ) self.tenderPeriod.endDate = end_date return self.tenderPeriod @serializable(serialize_when_none=False) def next_check(self): if self.suspended: return None now = get_now() checks = [] if self.status == 'active.tendering' and self.enquiryPeriod and self.enquiryPeriod.endDate: checks.append(self.enquiryPeriod.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( NUMBER_OF_STAGES, self.auctionPeriod.startDate).astimezone(TZ): checks.append( calc_auction_end_time( NUMBER_OF_STAGES, self.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)) if self.status.startswith('active'): from openprocurement.auctions.core.utils import calculate_business_date for complaint in self.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date( complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) for award in self.awards: for complaint in award.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append( calculate_business_date( complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append( calculate_business_date( complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) return min(checks).isoformat() if checks else None
class AppraisalContract(Contract): items = ListType(ModelType(AppraisalItem))
class AppraisalAward(Award): items = ListType(ModelType(AppraisalItem)) VERIFICATION_PERIOD_PARAMS = APPRAISAL_VERIFICATION_PERIOD_PARAMS SIGNING_PERIOD_PARAMS = APPRAISAL_SIGNING_PERIOD_PARAMS
class AppraisalAward(Award): items = ListType(ModelType(AppraisalItem))
class DGFOtherAssets(BaseAuction): """Data regarding auction process publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners. """ class Options: roles = dgf_auction_roles _internal_type = "dgfOtherAssets" awards = ListType(ModelType(Award), default=list()) # A list of all the companies who entered submissions for the auction. bids = ListType(ModelType(DGFOtherBid), default=list()) cancellations = ListType(ModelType(Cancellation), default=list()) complaints = ListType(ComplaintModelType(Complaint), default=list()) contracts = ListType(ModelType(Contract), default=list()) dgfID = StringType() merchandisingObject = MD5Type() dgfDecisionID = StringType() dgfDecisionDate = DateType() # All documents and attachments related to the auction. documents = ListType(ModelType(Document), default=list()) # The period during which enquiries may be made and will be answered. enquiryPeriod = ModelType(Period) # The period when the auction is open for submissions. The end date is the closing date for auction submissions. tenderPeriod = ModelType(Period) tenderAttempts = IntType(choices=[1, 2, 3, 4, 5, 6, 7, 8]) auctionPeriod = ModelType(AuctionAuctionPeriod, required=True, default={}) status = StringType( choices=[ 'draft', 'pending.verification', 'invalid', 'active.tendering', 'active.auction', 'active.qualification', 'active.awarded', 'complete', 'cancelled', 'unsuccessful' ], default='active.tendering' ) features = ListType(ModelType(Feature), validators=[validate_features_uniq, validate_not_available]) lots = ListType(ModelType(Lot), default=list(), validators=[validate_lots_uniq, validate_not_available]) items = ListType(ModelType(Item), default=list(), validators=[validate_items_uniq]) suspended = BooleanType() rectificationPeriod = ModelType(RectificationPeriod) def __acl__(self): return [ (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_auction_award'), (Allow, '{}_{}'.format(self.owner, self.owner_token), 'upload_auction_documents'), ] def get_role(self): root = self.__parent__ request = root.request if request.authenticated_role == 'Administrator': role = 'Administrator' elif request.authenticated_role == 'chronograph': role = 'chronograph' elif request.authenticated_role == 'auction': role = 'auction_{}'.format(request.method.lower()) elif request.authenticated_role == 'convoy': role = 'convoy' else: # on PATCH of the owner now = get_now() if self.status == 'active.tendering': if now in self.rectificationPeriod: role = 'edit_active.tendering_during_rectificationPeriod' else: role = 'edit_active.tendering_after_rectificationPeriod' else: role = 'edit_{0}'.format(self.status) return role @serializable(serialized_name='rectificationPeriod', serialize_when_none=False) def generate_rectificationPeriod(self): """Generate rectificationPeriod only when it not defined""" # avoid period generation if if getattr(self, 'tenderPeriod') is None: return if ( # it's already generated ( getattr(self, 'rectificationPeriod', False) # and not just present, but actually holds some real value and self.rectificationPeriod.startDate is not None ) ): return self.rectificationPeriod.serialize() start = self.tenderPeriod.startDate end = calculate_business_date(start, RECTIFICATION_PERIOD_DURATION, self, working_days=True) period = RectificationPeriod() period.startDate = start period.endDate = end return period.serialize() def initialize(self): if not self.enquiryPeriod: self.enquiryPeriod = type(self).enquiryPeriod.model_class() if not self.tenderPeriod: self.tenderPeriod = type(self).tenderPeriod.model_class() now = get_now() start_date = TZ.localize(self.auctionPeriod.startDate.replace(tzinfo=None)) self.auctionPeriod.startDate = None self.auctionPeriod.endDate = None self.tenderPeriod.startDate = self.enquiryPeriod.startDate = now pause_between_periods = start_date - (start_date.replace(hour=20, minute=0, second=0, microsecond=0) - timedelta(days=1)) end_date = calculate_business_date(start_date, -pause_between_periods, self) self.enquiryPeriod.endDate = end_date self.tenderPeriod.endDate = self.enquiryPeriod.endDate self.date = now if self.lots: for lot in self.lots: lot.date = now self.documents.append(type(self).documents.model_class(DGF_PLATFORM_LEGAL_DETAILS)) def validate_documents(self, data, docs): if (data.get('revisions')[0].date if data.get('revisions') else get_now()) > DGF_PLATFORM_LEGAL_DETAILS_FROM and \ (docs and docs[0].documentType != 'x_dgfPlatformLegalDetails' or any([i.documentType == 'x_dgfPlatformLegalDetails' for i in docs[1:]])): raise ValidationError(u"First document should be document with x_dgfPlatformLegalDetails documentType") def validate_value(self, data, value): if value.currency != u'UAH': raise ValidationError(u"currency should be only UAH") def validate_dgfID(self, data, dgfID): if not dgfID and data['status'] not in ['draft', 'pending.verification', 'invalid']: if (data.get('revisions')[0].date if data.get('revisions') else get_now()) > DGF_ID_REQUIRED_FROM: raise ValidationError(u'This field is required.') def validate_dgfDecisionID(self, data, dgfDecisionID): if not dgfDecisionID: if (data.get('revisions')[0].date if data.get('revisions') else get_now()) > DGF_DECISION_REQUIRED_FROM: raise ValidationError(u'This field is required.') def validate_dgfDecisionDate(self, data, dgfDecisionDate): if not dgfDecisionDate: if (data.get('revisions')[0].date if data.get('revisions') else get_now()) > DGF_DECISION_REQUIRED_FROM: raise ValidationError(u'This field is required.') def validate_merchandisingObject(self, data, merchandisingObject): if data['status'] == 'pending.verification' and not merchandisingObject: raise ValidationError(u'This field is required.') def validate_items(self, data, items): if data['status'] not in ['draft', 'pending.verification', 'invalid']: if not items: raise ValidationError(u'This field is required.') elif len(items) < 1: raise ValidationError(u'Please provide at least 1 item.') @serializable(serialize_when_none=False) def next_check(self): if self.suspended: return None now = get_now() checks = [] if self.status == 'active.tendering' and self.tenderPeriod 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)) # Use next_check part from awarding 2.0 request = get_request_from_root(self) if request is not None: awarding_check = request.registry.getAdapter(self, IAwardingNextCheck).add_awarding_checks(self) if awarding_check is not None: checks.append(awarding_check) if self.status.startswith('active'): from openprocurement.auctions.core.utils import calculate_business_date for complaint in self.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append(calculate_business_date(complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append(calculate_business_date(complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) for award in self.awards: for complaint in award.complaints: if complaint.status == 'claim' and complaint.dateSubmitted: checks.append(calculate_business_date(complaint.dateSubmitted, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) elif complaint.status == 'answered' and complaint.dateAnswered: checks.append(calculate_business_date(complaint.dateAnswered, AUCTIONS_COMPLAINT_STAND_STILL_TIME, self)) return min(checks).isoformat() if checks else None