class Document(ConfidentialDocument): """ Document model with new feature as Description of the decision to purchase """ class Options: roles = { 'edit': blacklist('id', 'url', 'datePublished', 'dateModified', ''), 'embedded': schematics_embedded_role, 'view': (blacklist('revisions') + schematics_default_role), 'restricted_view': (blacklist('revisions', 'url') + schematics_default_role), 'revisions': whitelist('url', 'dateModified'), } isDescriptionDecision = BooleanType(default=False) def validate_confidentialityRationale(self, data, val): if data['confidentiality'] != 'public' and not data[ 'isDescriptionDecision']: if not val: raise ValidationError(u"confidentialityRationale is required") elif len(val) < 30: raise ValidationError( u"confidentialityRationale should contain at least 30 characters" )
class Bid(Bid): 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(FinantialOrganization), required=True, min_size=1, max_size=1) eligible = BooleanType(required=True, choices=[True])
class Document(EUConfidentialDocument): isDescriptionDecision = BooleanType(default=False) def validate_confidentialityRationale(self, data, val): if data["confidentiality"] != "public" and not data["isDescriptionDecision"]: if not val: raise ValidationError("confidentialityRationale is required") elif len(val) < 30: raise ValidationError("confidentialityRationale should contain at least 30 characters")
class Bid(BaseBid): class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified'), } tenderers = ListType(ModelType(Organization), required=True, min_size=1, max_size=1) documents = ListType(ModelType(Document), default=list()) 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 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(), validators=[validate_disallow_dgfPlatformLegalDetails]) qualified = BooleanType(required=True, choices=[True])
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': edit_role, 'Administrator': Administrator_role, } 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()) dgfID = StringType() dgfDecisionID = StringType() dgfDecisionDate = DateType() 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. 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]) auctionPeriod = ModelType(AuctionAuctionPeriod, required=True, default={}) procurementMethodType = StringType(default="dgfOtherAssets") 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(Item), required=True, min_size=1, validators=[validate_items_uniq]) suspended = BooleanType() 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): 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() self.tenderPeriod.startDate = self.enquiryPeriod.startDate = now pause_between_periods = self.auctionPeriod.startDate - ( self.auctionPeriod.startDate.replace( hour=20, minute=0, second=0, microsecond=0) - timedelta(days=1)) self.enquiryPeriod.endDate = self.tenderPeriod.endDate = calculate_business_date( self.auctionPeriod.startDate, -pause_between_periods, self) self.auctionPeriod.startDate = None self.auctionPeriod.endDate = None 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_tenderPeriod(self, data, period): pass 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: 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, dgfID): if not dgfID: 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, dgfID): if not dgfID: if (data.get('revisions')[0].date if data.get('revisions') else get_now()) > DGF_DECISION_REQUIRED_FROM: 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)) elif not self.lots and self.status == 'active.qualification': for award in self.awards: if award.status == 'pending.verification': checks.append( award.verificationPeriod.endDate.astimezone(TZ)) elif award.status == 'pending.payment': checks.append(award.paymentPeriod.endDate.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 ] for award in self.awards: if award.status == 'active': checks.append(award.signingPeriod.endDate.astimezone(TZ)) 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.api.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 Bid(BaseBid): class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified'), 'embedded': view_bid_role, 'view': view_bid_role, 'auction_view': whitelist('value', 'lotValues', 'id', 'date', 'parameters', 'participationUrl', 'owner'), 'active.qualification': view_bid_role, 'active.awarded': view_bid_role, 'complete': view_bid_role, 'unsuccessful': view_bid_role, 'cancelled': view_bid_role, } status = StringType(choices=['active', 'draft', 'invalid'], default='active') tenderers = ListType(ModelType(Organization), required=True, min_size=1, max_size=1) parameters = ListType(ModelType(Parameter), default=list(), validators=[validate_parameters_uniq]) lotValues = ListType(ModelType(LotValue), default=list()) documents = ListType( ModelType(Document), default=list(), validators=[validate_disallow_dgfPlatformLegalDetails]) qualified = BooleanType(required=True, choices=[True]) participationUrl = URLType() owner_token = StringType() owner = StringType() def validate_participationUrl(self, data, url): if url and isinstance(data['__parent__'], Model) and get_auction( data['__parent__']).lots: raise ValidationError(u"url should be posted for each lot of bid") def validate_lotValues(self, data, values): if isinstance(data['__parent__'], Model): auction = data['__parent__'] if auction.lots and not values: raise ValidationError(u'This field is required.') def validate_value(self, data, value): pass def validate_parameters(self, data, parameters): if isinstance(data['__parent__'], Model): auction = data['__parent__'] if auction.lots: lots = [i.relatedLot for i in data['lotValues']] items = [i.id for i in auction.items if i.relatedLot in lots] codes = dict([ (i.code, [x.value for x in i.enum]) for i in (auction.features or []) if i.featureOf == 'tenderer' or i.featureOf == 'lot' and i.relatedItem in lots or i.featureOf == 'item' and i.relatedItem in items ]) if set([i['code'] for i in parameters]) != set(codes): raise ValidationError( u"All features parameters is required.") elif not parameters and auction.features: raise ValidationError(u'This field is required.') elif set([i['code'] for i in parameters]) != set( [i.code for i in (auction.features or [])]): raise ValidationError(u"All features parameters is required.") @serializable(serialized_name="participationUrl", serialize_when_none=False) def participation_url(self): root = self.__parent__ auction_id = root.id bidder_id = self.id parents = [] while root.__parent__ is not None: parents[0:0] = [root] root = root.__parent__ request = root.request auction_url = request.registry.auction_module_url signature = quote( b64encode(request.registry.signer.signature(bidder_id))) participation_url = '{}/auctions/{}/login?bidder_id={}&signature={}'.format( auction_url, auction_id, bidder_id, signature) if not self.participationUrl: return participation_url