Ejemplo n.º 1
0
class Complaint(BaseComplaint):
    author = ModelType(Organization, required=True)
    documents = ListType(ModelType(Document), default=list())
Ejemplo n.º 2
0
class Complaint(Model):
    class Options:
        roles = {
            'create':
            whitelist('author', 'title', 'description', 'status',
                      'relatedLot'),
            'draft':
            whitelist('author', 'title', 'description', 'status'),
            'cancellation':
            whitelist('cancellationReason', 'status'),
            'satisfy':
            whitelist('satisfied', 'status'),
            'answer':
            whitelist('resolution', 'resolutionType', 'status',
                      'tendererAction'),
            'action':
            whitelist('tendererAction'),
            'review':
            whitelist('decision', 'status'),
            'view':
            view_bid_role,
            'view_claim': (blacklist('author') + view_bid_role),
            'active.enquiries':
            view_bid_role,
            'active.tendering':
            view_bid_role,
            'active.auction':
            view_bid_role,
            'active.qualification':
            view_bid_role,
            'active.awarded':
            view_bid_role,
            'complete':
            view_bid_role,
            'unsuccessful':
            view_bid_role,
            'cancelled':
            view_bid_role,
        }

    # system
    id = MD5Type(required=True, default=lambda: uuid4().hex)
    complaintID = StringType()
    date = IsoDateTimeType(default=get_now)  # autogenerated date of posting
    status = StringType(choices=[
        'draft', 'claim', 'answered', 'pending', 'invalid', 'resolved',
        'declined', 'cancelled'
    ],
                        default='draft')
    documents = ListType(ModelType(Document), default=list())
    type = StringType(
        choices=['claim', 'complaint'], default='claim'
    )  # 'complaint' if status in ['pending'] or 'claim' if status in ['draft', 'claim', 'answered']
    owner_token = StringType()
    owner = StringType()
    relatedLot = MD5Type()
    # complainant
    author = ModelType(Organization, required=True)  # author of claim
    title = StringType(required=True)  # title of the claim
    description = StringType()  # description of the claim
    dateSubmitted = IsoDateTimeType()
    # tender owner
    resolution = StringType()
    resolutionType = StringType(choices=['invalid', 'resolved', 'declined'])
    dateAnswered = IsoDateTimeType()
    tendererAction = StringType()
    tendererActionDate = IsoDateTimeType()
    # complainant
    satisfied = BooleanType()
    dateEscalated = IsoDateTimeType()
    # reviewer
    decision = StringType()
    dateDecision = IsoDateTimeType()
    # complainant
    cancellationReason = StringType()
    dateCanceled = IsoDateTimeType()

    def serialize(self, role=None, context=None):
        if role == 'view' and self.type == 'claim' and get_tender(
                self).status in ['active.enquiries', 'active.tendering']:
            role = 'view_claim'
        return super(Complaint, self).serialize(role=role, context=context)

    def get_role(self):
        root = self.__parent__
        while root.__parent__ is not None:
            root = root.__parent__
        request = root.request
        data = request.json_body['data']
        if request.authenticated_role == 'complaint_owner' and data.get(
                'status', self.status) == 'cancelled':
            role = 'cancellation'
        elif request.authenticated_role == 'complaint_owner' and self.status == 'draft':
            role = 'draft'
        elif request.authenticated_role == 'tender_owner' and self.status == 'claim':
            role = 'answer'
        elif request.authenticated_role == 'tender_owner' and self.status == 'pending':
            role = 'action'
        elif request.authenticated_role == 'complaint_owner' and self.status == 'answered':
            role = 'satisfy'
        elif request.authenticated_role == 'reviewers' and self.status == 'pending':
            role = 'review'
        else:
            role = 'invalid'
        return role

    def __local_roles__(self):
        return dict([('{}_{}'.format(self.owner,
                                     self.owner_token), 'complaint_owner')])

    def __acl__(self):
        return [
            (Allow, 'g:reviewers', 'edit_complaint'),
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_complaint'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token),
             'upload_complaint_documents'),
        ]

    def validate_resolutionType(self, data, resolutionType):
        if not resolutionType and data.get('status') == 'answered':
            raise ValidationError(u'This field is required.')

    def validate_cancellationReason(self, data, cancellationReason):
        if not cancellationReason and data.get('status') == 'cancelled':
            raise ValidationError(u'This field is required.')

    def validate_relatedLot(self, data, relatedLot):
        if relatedLot and isinstance(
                data['__parent__'], Model) and relatedLot not in [
                    i.id for i in get_tender(data['__parent__']).lots
                ]:
            raise ValidationError(u"relatedLot should be one of lots")
Ejemplo n.º 3
0
class Tender(BaseTender):
    """Data regarding tender process - publicly inviting prospective contractors
       to submit bids for evaluation and selecting a winner or winners.
    """

    class Options:
        roles = {
            'plain': plain_role,
            'create': create_role,
            'edit': edit_role,
            'edit_draft': draft_role,
            'edit_active': edit_role,
            'edit_active.awarded': whitelist(),
            'edit_complete': whitelist(),
            'edit_unsuccessful': whitelist(),
            'edit_cancelled': whitelist(),
            'view': view_role,
            'listing': listing_role,
            'draft': enquiries_role,
            'active': enquiries_role,
            'active.awarded': view_role,
            'complete': view_role,
            'unsuccessful': view_role,
            'cancelled': view_role,
            'Administrator': Administrator_role,
            'chronograph': chronograph_role,  # remove after chronograph fix
            'chronograph_view': chronograph_view_role, # remove after chronograph fix
            'default': schematics_default_role,
            'contracting': whitelist('doc_id', 'owner'),
        }

    items = ListType(ModelType(Item), required=True, min_size=1, validators=[validate_cpv_group, validate_items_uniq])  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    value = ModelType(Value, required=True)  # The total estimated value of the procurement.
    procurementMethod = StringType(choices=['open', 'selective', 'limited'], default='limited')  # Specify tendering method as per GPA definitions of Open, Selective, Limited (http://www.wto.org/english/docs_e/legal_e/rev-gpr-94_01_e.htm)
    procurementMethodType = StringType(default="reporting")
    procuringEntity = ModelType(ProcuringEntity, required=True)  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    awards = ListType(ModelType(Award), default=list())
    contracts = ListType(ModelType(Contract), default=list())
    status = StringType(choices=['draft', 'active', 'complete', 'cancelled', 'unsuccessful'], default='active')
    mode = StringType(choices=['test'])
    cancellations = ListType(ModelType(Cancellation), default=list())

    create_accreditation = 1
    edit_accreditation = 2
    procuring_entity_kinds = ['general', 'special', 'defense', 'other']
    block_complaint_status = []  # BBB No complaints in reporting

    __parent__ = None

    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 == 'contracting':
            role = 'contracting'
        else:
            role = 'edit_{}'.format(request.context.status)
        return role

    def __acl__(self):
        return [
            (Allow, 'g:brokers', 'create_award_complaint'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_tender'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token), 'upload_tender_documents'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_complaint'),
        ]

    def validate_value(self, data, value):
        if value.valueAddedTaxIncluded is not False:
            raise ValidationError(u"Currently, only procedures with VAT excluded are supported")
class Award(BaseTenderEU.awards.model_class):

    items = ListType(ModelType(ItemStage2EU))
class Contract(BaseTenderUA.contracts.model_class):

    items = ListType(ModelType(ItemStage2UA))
Ejemplo n.º 6
0
class Tender(BaseTender):
    """ OpenEU tender model """

    class Options:
        namespace = "Tender"
        _parent_roles = BaseTender.Options.roles

        _edit_role = _parent_roles["edit"] - whitelist("enquiryPeriod")
        _read_fields = whitelist("qualifications", "qualificationPeriod", "complaintPeriod")
        _tendering_role = _parent_roles["active.tendering"] + _read_fields + whitelist("tender_enquiryPeriod")
        _view_role = _parent_roles["view"] + _read_fields
        _pre_qualifications_role = _view_role

        _all_forbidden = whitelist()
        roles = {
            "create": _parent_roles["create"] - whitelist("enquiryPeriod"),
            "edit": _edit_role,
            "edit_draft": _edit_role + whitelist("status"),
            "edit_active.tendering": _edit_role,
            "edit_active.pre-qualification": whitelist("status"),
            "edit_active.pre-qualification.stand-still": _all_forbidden,
            "edit_active.auction": _all_forbidden,
            "edit_active.qualification": _all_forbidden,
            "edit_active.awarded": _all_forbidden,
            "edit_complete": _all_forbidden,
            "edit_unsuccessful": _all_forbidden,
            "edit_cancelled": _all_forbidden,
            "active.pre-qualification": _pre_qualifications_role,
            "active.pre-qualification.stand-still": _pre_qualifications_role,
            "active.auction": _pre_qualifications_role,
            "draft": _tendering_role,
            "active.tendering": _tendering_role,
            "active.qualification": _view_role,
            "active.awarded": _view_role,
            "complete": _view_role,
            "unsuccessful": _view_role,
            "cancelled": _view_role,
            "view": _view_role,
            "auction_view": _parent_roles["auction_view"],
            "auction_post": _parent_roles["auction_post"],
            "auction_patch": _parent_roles["auction_patch"],
            "chronograph": _parent_roles["chronograph"],
            "chronograph_view": _parent_roles["chronograph_view"],
            "Administrator": _parent_roles["Administrator"],
            "default": _parent_roles["default"],
            "plain": _parent_roles["plain"],
            "contracting": _parent_roles["contracting"],
            "listing": _parent_roles["listing"],
        }

    procurementMethodType = StringType(default="aboveThresholdEU")
    title_en = StringType(required=True, min_length=1)

    enquiryPeriod = ModelType(EnquiryPeriod, required=False)
    tenderPeriod = ModelType(PeriodStartEndRequired, required=True)
    auctionPeriod = ModelType(TenderAuctionPeriod, default={})
    documents = ListType(
        ModelType(EUDocument, required=True), default=list()
    )  # All documents and attachments related to the tender.
    items = ListType(
        ModelType(Item, required=True),
        required=True,
        min_size=1,
        validators=[validate_cpv_group, validate_items_uniq, validate_classification_id],
    )  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    complaints = ListType(ComplaintModelType(Complaint, required=True), default=list())
    contracts = ListType(ModelType(Contract, required=True), default=list())
    cancellations = ListType(ModelType(Cancellation, required=True), default=list())
    awards = ListType(ModelType(Award, required=True), default=list())
    procuringEntity = ModelType(
        ProcuringEntity, required=True
    )  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    bids = SifterListType(
        BidModelType(Bid),
        default=list(),
        filter_by="status",
        filter_in_values=["invalid", "invalid.pre-qualification", "deleted"],
    )  # A list of all the companies who entered submissions for the tender.
    qualifications = ListType(ModelType(Qualification, required=True), default=list())
    qualificationPeriod = ModelType(Period)
    lots = ListType(ModelType(Lot, required=True), default=list(), validators=[validate_lots_uniq])
    status = StringType(
        choices=[
            "draft",
            "active.tendering",
            "active.pre-qualification",
            "active.pre-qualification.stand-still",
            "active.auction",
            "active.qualification",
            "active.awarded",
            "complete",
            "cancelled",
            "unsuccessful",
        ],
        default="active.tendering",
    )

    create_accreditations = (ACCR_3, ACCR_5)
    central_accreditations = (ACCR_5,)
    edit_accreditations = (ACCR_4,)

    procuring_entity_kinds = ["authority", "central", "defense", "general", "social", "special"]

    block_tender_complaint_status = OpenUATender.block_tender_complaint_status
    block_complaint_status = OpenUATender.block_complaint_status

    def __acl__(self):
        acl = [
            (Allow, "{}_{}".format(i.owner, i.owner_token), "create_qualification_complaint")
            for i in self.bids
            if i.status in ["active", "unsuccessful"]
        ]
        acl.extend(
            [
                (Allow, "{}_{}".format(i.owner, i.owner_token), "create_award_complaint")
                for i in self.bids
                if i.status == "active"
            ]
        )
        acl.extend(
            [
                (Allow, "{}_{}".format(self.owner, self.owner_token), "edit_complaint"),
                (Allow, "{}_{}".format(self.owner, self.owner_token), "edit_contract"),
                (Allow, "{}_{}".format(self.owner, self.owner_token), "upload_contract_documents"),
                (Allow, "{}_{}".format(self.owner, self.owner_token), "upload_qualification_documents"),
            ]
        )

        self._acl_cancellation_complaint(acl)
        return acl

    def validate_enquiryPeriod(self, data, period):
        # for deactivate validation to enquiryPeriod from parent class
        return

    @serializable(serialized_name="enquiryPeriod", type=ModelType(EnquiryPeriod))
    def tender_enquiryPeriod(self):
        end_date = calculate_tender_business_date(self.tenderPeriod.endDate, -QUESTIONS_STAND_STILL, self)
        clarifications_until = calculate_clarif_business_date(end_date, ENQUIRY_STAND_STILL_TIME, self, True)
        return EnquiryPeriod(
            dict(
                startDate=self.tenderPeriod.startDate,
                endDate=end_date,
                invalidationDate=self.enquiryPeriod and self.enquiryPeriod.invalidationDate,
                clarificationsUntil=clarifications_until,
            )
        )

    @serializable(type=ModelType(Period))
    def complaintPeriod(self):
        end_date = calculate_complaint_business_date(self.tenderPeriod.endDate, -COMPLAINT_SUBMIT_TIME, self)
        return Period(dict(startDate=self.tenderPeriod.startDate, endDate=end_date))

    @serializable(serialize_when_none=False)
    def next_check(self):
        now = get_now()
        checks = []

        extend_next_check_by_complaint_period_ends(self, checks)

        if cancellation_block_tender(self):
            return min(checks).isoformat() if checks else None

        if (
            self.status == "active.tendering"
            and self.tenderPeriod.endDate
            and not has_unanswered_complaints(self)
            and not has_unanswered_questions(self)
        ):
            checks.append(self.tenderPeriod.endDate.astimezone(TZ))
        elif (
            self.status == "active.pre-qualification.stand-still"
            and self.qualificationPeriod
            and self.qualificationPeriod.endDate
        ):
            active_lots = [lot.id for lot in self.lots if lot.status == "active"] if self.lots else [None]
            if not any(
                [
                    i.status in self.block_complaint_status
                    for q in self.qualifications
                    for i in q.complaints
                    if q.lotID in active_lots
                ]
            ):
                checks.append(self.qualificationPeriod.endDate.astimezone(TZ))
        elif (
            not self.lots
            and self.status == "active.auction"
            and self.auctionPeriod
            and self.auctionPeriod.startDate
            and not self.auctionPeriod.endDate
        ):
            if now < self.auctionPeriod.startDate:
                checks.append(self.auctionPeriod.startDate.astimezone(TZ))
            else:
                auction_end_time = calc_auction_end_time(
                    self.numberOfBids, self.auctionPeriod.startDate
                ).astimezone(TZ)
                if now < auction_end_time:
                    checks.append(auction_end_time)
        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))
                else:
                    auction_end_time = calc_auction_end_time(
                        lot.numberOfBids, lot.auctionPeriod.startDate
                    ).astimezone(TZ)
                    if now < auction_end_time:
                        checks.append(auction_end_time)
        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 and 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 and 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 and self.status.startswith("active"):
            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)

        return min(checks).isoformat() if checks else None

    def validate_tenderPeriod(self, data, period):
        if is_new_created(data):
            _validate_tender_period_start_date(data, period)
        _validate_tender_period_duration(data, period, TENDERING_DURATION)

    @serializable
    def numberOfBids(self):
        """A property that is serialized by schematics exports."""
        return len([bid for bid in self.bids if bid.status in ("active", "pending")])

    def check_auction_time(self):
        if check_auction_period(self.auctionPeriod, self):
            self.auctionPeriod.startDate = None
        for lot in self.lots:
            if check_auction_period(lot.auctionPeriod, self):
                lot.auctionPeriod.startDate = None

    def invalidate_bids_data(self):
        self.check_auction_time()
        self.enquiryPeriod.invalidationDate = get_now()
        for bid in self.bids:
            if bid.status not in ["deleted", "draft"]:
                bid.status = "invalid"
class Feature(BaseFeature):
    enum = ListType(ModelType(FeatureValue),
                    default=list(),
                    min_size=1,
                    validators=[validate_values_uniq])
Ejemplo n.º 8
0
class Organization(BaseOrganization):
    identifier = ModelType(Identifier, required=True)
    additionalIdentifiers = ListType(ModelType(Identifier))
Ejemplo n.º 9
0
class ProcuringEntity(BaseProcuringEntity):
    identifier = ModelType(Identifier, required=True)
    additionalIdentifiers = ListType(ModelType(Identifier))
Ejemplo n.º 10
0
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', 'auction_value',
                       'auction_minimalStep', 'auction_guarantee',
                       'eligibilityCriteria', 'eligibilityCriteria_en',
                       'eligibilityCriteria_ru', 'title', 'title_ru',
                       'title_en', 'dgfID', 'tenderAttempts',
                       'minNumberOfQualifiedBids') + edit_role),
            'Administrator': (whitelist('value', 'minimalStep', 'guarantee') +
                              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()
    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])
    minNumberOfQualifiedBids = IntType(choices=[1, 2])

    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

    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.')

    @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))
        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.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
Ejemplo n.º 11
0
class FinantialOrganization(BaseOrganization):
    identifier = ModelType(Identifier, required=True)
    additionalIdentifiers = ListType(ModelType(Identifier),
                                     required=True,
                                     validators=[validate_ua_fin])
Ejemplo n.º 12
0
class Award(BaseAward):
    suppliers = ListType(ModelType(Organization), min_size=1, max_size=1)
    complaints = ListType(ModelType(Complaint), default=list())
    documents = ListType(ModelType(Document), default=list())
    items = ListType(ModelType(Item))
Ejemplo n.º 13
0
class Contract(BaseContract):
    items = ListType(ModelType(Item))
    suppliers = ListType(ModelType(Organization), min_size=1, max_size=1)
    complaints = ListType(ModelType(Complaint), default=list())
    documents = ListType(ModelType(Document), default=list())
Ejemplo n.º 14
0
class Cancellation(BaseCancellation):
    documents = ListType(ModelType(Document), default=list())
Ejemplo n.º 15
0
class CFASelectionUATender(BaseTender):
    """Data regarding tender process - publicly inviting prospective contractors
    to submit bids for evaluation and selecting a winner or winners.
    """
    class Options:
        namespace = 'Tender'
        roles = RolesFromCsv('Tender.csv', relative_to=__file__)

    items = ListType(
        ModelType(Item), min_size=1, validators=[validate_items_uniq]
    )  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    value = ModelType(Value)  # The total estimated value of the procurement.
    enquiryPeriod = ModelType(
        PeriodEndRequired, required=False
    )  # The period during which enquiries may be made and will be answered.
    tenderPeriod = ModelType(
        PeriodEndRequired, required=False
    )  # The period when the tender is open for submissions. The end date is the closing date for tender submissions.
    hasEnquiries = BooleanType(
    )  # A Yes/No field as to whether enquiries were part of tender process.
    awardPeriod = ModelType(
        Period
    )  # The date or period on which an award is anticipated to be made.
    numberOfBidders = IntType(
    )  # The number of unique tenderers who participated in the tender
    bids = ListType(ModelType(Bid), default=list(
    ))  # A list of all the companies who entered submissions for the tender.
    procuringEntity = ModelType(
        ProcuringEntity, required=True
    )  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    awards = ListType(ModelType(Award), default=list())
    contracts = ListType(ModelType(Contract), default=list())
    auctionPeriod = ModelType(TenderAuctionPeriod, default={})
    minimalStep = ModelType(Value, required=False)
    auctionUrl = URLType()
    cancellations = ListType(ModelType(Cancellation), default=list())
    features = ListType(ModelType(Feature),
                        validators=[validate_features_uniq])
    lots = ListType(ModelType(Lot),
                    default=list(),
                    validators=[validate_lots_uniq],
                    min_size=1,
                    max_size=1)
    guarantee = ModelType(Guarantee)
    status = StringType(choices=[
        'draft', 'draft.pending', 'draft.unsuccessful', 'active.enquiries',
        'active.tendering', 'active.auction', 'active.qualification',
        'active.awarded', 'complete', 'cancelled', 'unsuccessful'
    ],
                        default='draft')  # TODO Refactoring status
    agreements = ListType(ModelType(Agreement),
                          default=list(),
                          min_size=1,
                          max_size=1)

    procurementMethod = StringType(choices=['open', 'selective', 'limited'],
                                   default='selective')
    procurementMethodType = StringType(
        default="closeFrameworkAgreementSelectionUA")
    unsuccessfulReason = ListType(StringType, serialize_when_none=False)
    procuring_entity_kinds = ['general', 'special', 'defense', 'other']

    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 == 'contracting':
            role = 'contracting'
        elif request.authenticated_role == 'agreement_selection':
            role = 'edit_{}'.format(request.authenticated_role)
        else:
            role = 'edit_{}'.format(request.context.status)
        return role

    def __acl__(self):
        acl = [(Allow, '{}_{}'.format(i.owner,
                                      i.owner_token), 'create_award_complaint')
               for i in self.bids]
        acl.extend([
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_tender'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token),
             'upload_tender_documents'),
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_complaint'),
            (Allow, 'g:agreement_selection', 'edit_agreement_selection'),
            (Allow, 'g:agreement_selection', 'edit_tender'),
        ])
        return acl

    def __local_roles__(self):
        roles = dict([('{}_{}'.format(self.owner,
                                      self.owner_token), 'tender_owner')])
        for i in self.bids:
            roles['{}_{}'.format(i.owner, i.owner_token)] = 'bid_owner'
        return roles
Ejemplo n.º 16
0
class Award(BaseEUAward):
    """ESCO award model"""

    value = ModelType(BaseESCOValue)
    items = ListType(ModelType(Item, required=True))
Ejemplo n.º 17
0
class Bid(BidResponsesMixin, BaseBid):
    class Options:
        roles = {
            "Administrator": Administrator_bid_role,
            "embedded": view_bid_role,
            "view": view_bid_role,
            "create": whitelist(
                "value",
                "tenderers",
                "parameters",
                "lotValues",
                "status",
                "selfQualified",
                "selfEligible",
                "subcontractingDetails",
                "documents",
                "financialDocuments",
                "eligibilityDocuments",
                "qualificationDocuments",
                "requirementResponses",
            ),
            "edit": whitelist(
                "value",
                "tenderers",
                "parameters",
                "lotValues",
                "status",
                "subcontractingDetails",
                "requirementResponses",
            ),
            "auction_view": whitelist("value", "lotValues", "id", "date", "parameters", "participationUrl", "status"),
            "auction_post": whitelist("value", "lotValues", "id", "date"),
            "auction_patch": whitelist("id", "lotValues", "participationUrl"),
            "active.enquiries": whitelist(),
            "active.tendering": whitelist(),
            "active.pre-qualification": whitelist(
                "id", "status", "documents", "eligibilityDocuments", "tenderers", "requirementResponses"),
            "active.pre-qualification.stand-still": whitelist(
                "id", "status", "documents", "eligibilityDocuments", "tenderers", "requirementResponses"),
            "active.auction": whitelist("id", "status", "documents", "eligibilityDocuments", "tenderers"),
            "active.qualification": view_bid_role,
            "active.awarded": view_bid_role,
            "complete": view_bid_role,
            "unsuccessful": view_bid_role,
            "bid.unsuccessful": whitelist(
                "id",
                "status",
                "tenderers",
                "documents",
                "eligibilityDocuments",
                "parameters",
                "selfQualified",
                "selfEligible",
                "subcontractingDetails",
                "requirementResponses",
            ),
            "cancelled": view_bid_role,
            "invalid": whitelist("id", "status"),
            "invalid.pre-qualification": whitelist(
                "id", "status", "documents", "eligibilityDocuments", "tenderers", "requirementResponses"),
            "deleted": whitelist("id", "status"),
        }

    documents = ListType(ConfidentialDocumentModelType(EUConfidentialDocument, required=True), default=list())
    financialDocuments = ListType(ConfidentialDocumentModelType(EUConfidentialDocument, required=True), default=list())
    eligibilityDocuments = ListType(ConfidentialDocumentModelType(EUConfidentialDocument, required=True), default=list())
    qualificationDocuments = ListType(ConfidentialDocumentModelType(EUConfidentialDocument, required=True), default=list())
    lotValues = ListType(ModelType(LotValue, required=True), default=list())
    selfQualified = BooleanType(required=True, choices=[True])
    selfEligible = BooleanType(choices=[True])
    subcontractingDetails = StringType()
    parameters = ListType(ModelType(Parameter, required=True), default=list(), validators=[validate_parameters_uniq])
    status = StringType(
        choices=["draft", "pending", "active", "invalid", "invalid.pre-qualification", "unsuccessful", "deleted"],
        default="pending",
    )

    def serialize(self, role=None):
        if role and role != "create" and self.status in ["invalid", "invalid.pre-qualification", "deleted"]:
            role = self.status
        elif role and role != "create" and self.status == "unsuccessful":
            role = "bid.unsuccessful"
        return super(Bid, self).serialize(role)

    @serializable(serialized_name="status")
    def serialize_status(self):
        if self.status in [
            "draft",
            "invalid",
            "invalid.pre-qualification",
            "unsuccessful",
            "deleted",
        ] or self.__parent__.status in ["active.tendering", "cancelled"]:
            return self.status
        if self.__parent__.lots:
            active_lots = [lot.id for lot in self.__parent__.lots if lot.status in ("active", "complete")]
            if not self.lotValues:
                return "invalid"
            elif [i.relatedLot for i in self.lotValues if i.status == "pending" and i.relatedLot in active_lots]:
                return "pending"
            elif [i.relatedLot for i in self.lotValues if i.status == "active" and i.relatedLot in active_lots]:
                return "active"
            else:
                return "unsuccessful"
        return self.status

    @bids_validation_wrapper
    def validate_value(self, data, value):
        BaseBid._validator_functions["value"](self, data, value)

    @bids_validation_wrapper
    def validate_lotValues(self, data, lotValues):
        BaseBid._validator_functions["lotValues"](self, data, lotValues)

    @bids_validation_wrapper
    def validate_participationUrl(self, data, participationUrl):
        BaseBid._validator_functions["participationUrl"](self, data, participationUrl)

    @bids_validation_wrapper
    def validate_parameters(self, data, parameters):
        BaseBid._validator_functions["parameters"](self, data, parameters)
Ejemplo n.º 18
0
class Tender(BaseTender):
    """ ESCO Tender model """
    class Options:
        namespace = "Tender"
        _parent_roles = BaseTender.Options.roles
        _all_forbidden = whitelist()

        _serializable_fields = whitelist(
            "tender_enquiryPeriod",
            "complaintPeriod",
            "next_check",
            "tender_minValue",
            "tender_guarantee",
            "tender_minimalStepPercentage",
            "tender_yearlyPaymentsPercentageRange",
            "tender_noticePublicationDate",
        )
        _edit_fields = _serializable_fields + whitelist(
            "procuringEntity",
            "tenderPeriod",
            "NBUdiscountRate",
            "items",
            "features",
            "yearlyPaymentsPercentageRange",
            "fundingKind",
            # fields below are not covered
            "hasEnquiries",
            "numberOfBidders",
            "minimalStepPercentage",
        )
        _read_only_fields = whitelist(
            "awards",
            "lots",
            "contracts",
            "auctionPeriod",
            "complaints",
            # fields below are not covered
            "auctionUrl",
            "awardPeriod",
            "questions",
            "cancellations",
            "qualifications",
            "qualificationPeriod",
        )

        _tendering_role = _parent_roles[
            "view"] + _edit_fields + _read_only_fields
        _view_role = _tendering_role + whitelist("bids", "numberOfBids")
        _pre_qualifications_role = _view_role

        _esco_edit_forbidden = whitelist(
            "minValue",
            "tender_minValue",
            "minimalStep",
            "tender_minimalStep",
            "noticePublicationDate",
            "tender_noticePublicationDate",
        )
        _edit_role = _parent_roles["edit"] + _edit_fields - _esco_edit_forbidden

        roles = {
            "create":
            _parent_roles["create"] + _edit_fields + whitelist("lots") -
            _esco_edit_forbidden,
            "edit":
            _edit_role,
            "edit_draft":
            _edit_role,
            "edit_active.tendering":
            _edit_role,
            "edit_active.pre-qualification":
            whitelist("status"),
            "edit_active.pre-qualification.stand-still":
            _all_forbidden,
            "edit_active.auction":
            _all_forbidden,
            "edit_active.qualification":
            _all_forbidden,
            "edit_active.awarded":
            _all_forbidden,
            "edit_complete":
            _all_forbidden,
            "edit_unsuccessful":
            _all_forbidden,
            "edit_cancelled":
            _all_forbidden,
            "draft":
            _tendering_role,
            "active.tendering":
            _tendering_role,
            "active.qualification":
            _view_role,
            "active.awarded":
            _view_role,
            "complete":
            _view_role,
            "unsuccessful":
            _view_role,
            "cancelled":
            _view_role,
            "view":
            _view_role,
            "active.pre-qualification":
            _pre_qualifications_role,
            "active.pre-qualification.stand-still":
            _pre_qualifications_role,
            "active.auction":
            _pre_qualifications_role,
            "auction_view":
            _parent_roles["auction_view"] + whitelist(
                "NBUdiscountRate",
                "minimalStepPercentage",
                "yearlyPaymentsPercentageRange",
                "fundingKind",
                "procurementMethodType",
                "noticePublicationDate",
            ),
            "auction_post":
            _parent_roles["auction_post"],
            "auction_patch":
            _parent_roles["auction_patch"],
            "chronograph":
            _parent_roles["chronograph"],
            "chronograph_view":
            _parent_roles["chronograph_view"],
            "Administrator":
            _parent_roles["Administrator"] +
            whitelist("yearlyPaymentsPercentageRange"),
            "plain":
            _parent_roles["plain"],
            "listing":
            _parent_roles["listing"],
            "contracting":
            _parent_roles["contracting"],
            "default":
            _parent_roles["default"],
        }

    procurementMethodType = StringType(default="esco")
    title_en = StringType(required=True, min_length=1)

    items = ListType(
        ModelType(Item, required=True),
        required=True,
        min_size=1,
        validators=[validate_cpv_group, validate_items_uniq]
    )  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    minValue = ModelType(Value,
                         required=False,
                         default={
                             "amount": 0,
                             "currency": "UAH",
                             "valueAddedTaxIncluded": True
                         })  # The total estimated value of the procurement.

    enquiryPeriod = ModelType(EnquiryPeriod, required=False)
    tenderPeriod = ModelType(PeriodStartEndRequired, required=True)
    auctionPeriod = ModelType(TenderAuctionPeriod, default={})
    hasEnquiries = BooleanType(
    )  # A Yes/No field as to whether enquiries were part of tender process.
    awardCriteria = StringType(default="ratedCriteria")
    awardPeriod = ModelType(
        Period
    )  # The date or period on which an award is anticipated to be made.
    numberOfBidders = IntType(
    )  # The number of unique tenderers who participated in the tender
    bids = SifterListType(
        BidModelType(Bid),
        default=list(),
        filter_by="status",
        filter_in_values=["invalid", "invalid.pre-qualification", "deleted"],
    )  # A list of all the companies who entered submissions for the tender.
    procuringEntity = ModelType(
        ProcuringEntity, required=True
    )  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    awards = ListType(ModelType(Award, required=True), default=list())
    contracts = ListType(ModelType(Contract, required=True), default=list())
    minimalStep = ModelType(
        Value, required=False
    )  # Not required, blocked for create/edit, since we have minimalStepPercentage in esco
    minimalStepPercentage = DecimalType(required=True,
                                        min_value=Decimal("0.005"),
                                        max_value=Decimal("0.03"),
                                        precision=-5)
    questions = ListType(ModelType(Question, required=True), default=list())
    complaints = ListType(ComplaintModelType(Complaint, required=True),
                          default=list())
    auctionUrl = URLType()
    cancellations = ListType(ModelType(Cancellation, required=True),
                             default=list())
    features = ListType(ModelType(Feature, required=True),
                        validators=[validate_features_uniq])
    lots = ListType(ModelType(Lot, required=True),
                    default=list(),
                    validators=[validate_lots_uniq])
    guarantee = ModelType(Guarantee)
    documents = ListType(
        ModelType(Document, required=True),
        default=list())  # All documents and attachments related to the tender.
    qualifications = ListType(ModelType(Qualification, required=True),
                              default=list())
    qualificationPeriod = ModelType(Period)
    status = StringType(
        choices=[
            "draft",
            "active.tendering",
            "active.pre-qualification",
            "active.pre-qualification.stand-still",
            "active.auction",
            "active.qualification",
            "active.awarded",
            "complete",
            "cancelled",
            "unsuccessful",
        ],
        default="active.tendering",
    )
    NBUdiscountRate = DecimalType(required=True,
                                  min_value=Decimal("0"),
                                  max_value=Decimal("0.99"),
                                  precision=-5)
    fundingKind = StringType(choices=["budget", "other"],
                             required=True,
                             default="other")
    yearlyPaymentsPercentageRange = DecimalType(required=True,
                                                default=Decimal("0.8"),
                                                min_value=Decimal("0"),
                                                max_value=Decimal("1"),
                                                precision=-5)
    noticePublicationDate = IsoDateTimeType()

    create_accreditations = (ACCR_3, ACCR_5)
    central_accreditations = (ACCR_5, )
    edit_accreditations = (ACCR_4, )

    special_fields = ["fundingKind", "yearlyPaymentsPercentageRange"]
    procuring_entity_kinds = ["general", "special", "defense", "central"]

    block_tender_complaint_status = OpenUATender.block_tender_complaint_status
    block_complaint_status = OpenUATender.block_complaint_status

    def import_data(self, raw_data, **kw):
        """
        Converts and imports the raw data into the instance of the model
        according to the fields in the model.
        :param raw_data:
            The data to be imported.
        """
        data = self.convert(raw_data, **kw)
        del_keys = [
            k for k in data.keys()
            if data[k] == self.__class__.fields[k].default
            or data[k] == getattr(self, k)
        ]
        for k in del_keys:
            if k in self.special_fields:
                # skip special fields :)
                continue
            del data[k]
        self._data.update(data)
        return self

    def __local_roles__(self):
        roles = dict([("{}_{}".format(self.owner,
                                      self.owner_token), "tender_owner")])
        for i in self.bids:
            roles["{}_{}".format(i.owner, i.owner_token)] = "bid_owner"
        return roles

    def __acl__(self):
        acl = [(Allow, "{}_{}".format(i.owner, i.owner_token),
                "create_qualification_complaint") for i in self.bids
               if i.status in ["active", "unsuccessful"]]
        acl.extend([(Allow, "{}_{}".format(i.owner, i.owner_token),
                     "create_award_complaint") for i in self.bids
                    if i.status == "active"])
        acl.extend([
            (Allow, "{}_{}".format(self.owner,
                                   self.owner_token), "edit_complaint"),
        ])

        self._acl_cancellation_complaint(acl)
        return acl

    @serializable(serialized_name="enquiryPeriod",
                  type=ModelType(EnquiryPeriod))
    def tender_enquiryPeriod(self):
        endDate = calculate_tender_business_date(self.tenderPeriod.endDate,
                                                 -QUESTIONS_STAND_STILL, self)
        clarificationsUntil = calculate_clarifications_business_date(
            endDate, ENQUIRY_STAND_STILL_TIME, self, True)
        return EnquiryPeriod(
            dict(
                startDate=self.tenderPeriod.startDate,
                endDate=endDate,
                invalidationDate=self.enquiryPeriod
                and self.enquiryPeriod.invalidationDate,
                clarificationsUntil=clarificationsUntil,
            ))

    @serializable(type=ModelType(Period))
    def complaintPeriod(self):
        endDate = calculate_complaint_business_date(self.tenderPeriod.endDate,
                                                    -COMPLAINT_SUBMIT_TIME,
                                                    self)
        return Period(
            dict(startDate=self.tenderPeriod.startDate, endDate=endDate))

    @serializable(serialize_when_none=False)
    def next_check(self):
        now = get_now()
        checks = []
        if (self.status == "active.tendering" and self.tenderPeriod.endDate
                and not has_unanswered_complaints(self)
                and not has_unanswered_questions(self)):
            checks.append(self.tenderPeriod.endDate.astimezone(TZ))
        elif (self.status == "active.pre-qualification.stand-still"
              and self.qualificationPeriod
              and self.qualificationPeriod.endDate):
            active_lots = [
                lot.id for lot in self.lots if lot.status == "active"
            ] if self.lots else [None]
            if not any([
                    i.status in self.block_complaint_status
                    for q in self.qualifications
                    for i in q.complaints if q.lotID in active_lots
            ]):
                checks.append(self.qualificationPeriod.endDate.astimezone(TZ))
        elif (not self.lots and self.status == "active.auction"
              and self.auctionPeriod and self.auctionPeriod.startDate
              and not self.auctionPeriod.endDate):
            if now < self.auctionPeriod.startDate:
                checks.append(self.auctionPeriod.startDate.astimezone(TZ))
            elif now < calc_auction_end_time(
                    self.numberOfBids,
                    self.auctionPeriod.startDate).astimezone(TZ):
                checks.append(
                    calc_auction_end_time(
                        self.numberOfBids,
                        self.auctionPeriod.startDate).astimezone(TZ))
        elif self.lots and self.status == "active.auction":
            for lot in self.lots:
                if (lot.status != "active" or not lot.auctionPeriod
                        or not lot.auctionPeriod.startDate
                        or lot.auctionPeriod.endDate):
                    continue
                if now < lot.auctionPeriod.startDate:
                    checks.append(lot.auctionPeriod.startDate.astimezone(TZ))
                elif now < calc_auction_end_time(
                        lot.numberOfBids,
                        lot.auctionPeriod.startDate).astimezone(TZ):
                    checks.append(
                        calc_auction_end_time(
                            lot.numberOfBids,
                            lot.auctionPeriod.startDate).astimezone(TZ))
        elif (not self.lots and self.status == "active.awarded" and not any(
            [i.status in self.block_complaint_status for i in self.complaints])
              and not any([
                  i.status in self.block_complaint_status for a in self.awards
                  for i in a.complaints
              ])):
            standStillEnds = [
                a.complaintPeriod.endDate.astimezone(TZ) for a in self.awards
                if a.complaintPeriod.endDate
            ]
            last_award_status = self.awards[-1].status if self.awards else ""
            if standStillEnds and last_award_status == "unsuccessful":
                checks.append(max(standStillEnds))
        elif (self.lots
              and self.status in ["active.qualification", "active.awarded"]
              and not any([
                  i.status in self.block_complaint_status
                  and i.relatedLot is None for i in self.complaints
              ])):
            for lot in self.lots:
                if lot["status"] != "active":
                    continue
                lot_awards = [i for i in self.awards if i.lotID == lot.id]
                pending_complaints = any([
                    i["status"] in self.block_complaint_status
                    and i.relatedLot == lot.id for i in self.complaints
                ])
                pending_awards_complaints = any([
                    i.status in self.block_complaint_status for a in lot_awards
                    for i in a.complaints
                ])
                standStillEnds = [
                    a.complaintPeriod.endDate.astimezone(TZ)
                    for a in lot_awards if a.complaintPeriod.endDate
                ]
                last_award_status = lot_awards[-1].status if lot_awards else ""
                if (not pending_complaints and not pending_awards_complaints
                        and standStillEnds
                        and last_award_status == "unsuccessful"):
                    checks.append(max(standStillEnds))
        if self.status.startswith("active"):
            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)

        extend_next_check_by_complaint_period_ends(self, checks)

        return min(checks).isoformat() if checks else None

    @serializable
    def numberOfBids(self):
        """A property that is serialized by schematics exports."""
        return len(
            [bid for bid in self.bids if bid.status in ("active", "pending")])

    @serializable(serialized_name="minValue", type=ModelType(Value))
    def tender_minValue(self):
        return (Value(
            dict(
                amount=sum([i.minValue.amount for i in self.lots]),
                currency=self.minValue.currency,
                valueAddedTaxIncluded=self.minValue.valueAddedTaxIncluded,
            )) if self.lots else self.minValue)

    @serializable(serialized_name="guarantee",
                  serialize_when_none=False,
                  type=ModelType(Guarantee))
    def tender_guarantee(self):
        if self.lots:
            lots_amount = [
                i.guarantee.amount for i in self.lots if i.guarantee
            ]
            if not lots_amount:
                return self.guarantee
            guarantee = {"amount": sum(lots_amount)}
            lots_currency = [
                i.guarantee.currency for i in self.lots if i.guarantee
            ]
            guarantee["currency"] = lots_currency[0] if lots_currency else None
            if self.guarantee:
                guarantee["currency"] = self.guarantee.currency
            return Guarantee(guarantee)
        else:
            return self.guarantee

    @serializable(serialized_name="minimalStep",
                  type=ModelType(Value),
                  serialize_when_none=False)
    def tender_minimalStep(self):
        pass

    @serializable(serialized_name="minimalStepPercentage")
    def tender_minimalStepPercentage(self):
        return min([i.minimalStepPercentage for i in self.lots
                    ]) if self.lots else self.minimalStepPercentage

    @serializable(serialized_name="yearlyPaymentsPercentageRange")
    def tender_yearlyPaymentsPercentageRange(self):
        return (min([i.yearlyPaymentsPercentageRange for i in self.lots])
                if self.lots else self.yearlyPaymentsPercentageRange)

    @serializable(serialized_name="noticePublicationDate",
                  serialize_when_none=False,
                  type=IsoDateTimeType())
    def tender_noticePublicationDate(self):
        if not self.noticePublicationDate and self.status == "active.tendering":
            return self.get_root().request.now
        else:
            return self.noticePublicationDate

    def validate_items(self, data, items):
        cpv_336_group = items[
            0].classification.id[:3] == "336" if items else False
        if (not cpv_336_group
                and (data.get("revisions")[0].date if data.get("revisions")
                     else get_now()) > CPV_ITEMS_CLASS_FROM and items
                and len(set([i.classification.id[:4] for i in items])) != 1):
            raise ValidationError(u"CPV class of items should be identical")
        else:
            validate_cpv_group(items)

    def validate_features(self, data, features):
        if (features and data["lots"] and any([
                round(
                    vnmax([
                        i for i in features if i.featureOf == "tenderer"
                        or i.featureOf == "lot" and i.relatedItem == lot["id"]
                        or i.featureOf == "item" and i.relatedItem in [
                            j.id
                            for j in data["items"] if j.relatedLot == lot["id"]
                        ]
                    ]),
                    15,
                ) > 0.25 for lot in data["lots"]
        ])):
            raise ValidationError(
                u"Sum of max value of all features for lot should be less then or equal to 25%"
            )
        elif features and not data["lots"] and round(vnmax(features),
                                                     15) > 0.25:
            raise ValidationError(
                u"Sum of max value of all features should be less then or equal to 25%"
            )

    def validate_auctionUrl(self, data, url):
        if url and data["lots"]:
            raise ValidationError(u"url should be posted for each lot")

    def validate_minimalStep(self, data, value):
        pass

    def validate_tenderPeriod(self, data, period):
        # if data['_rev'] is None when tender was created just now
        if not data["_rev"] and calculate_tender_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_tender_business_date(
                period.startDate, TENDERING_DURATION, data) > period.endDate:
            raise ValidationError(
                u"tenderPeriod should be greater than {} days".format(
                    TENDERING_DAYS))

    def validate_awardPeriod(self, data, period):
        if (period and period.startDate and data.get("auctionPeriod")
                and data.get("auctionPeriod").endDate
                and period.startDate < data.get("auctionPeriod").endDate):
            raise ValidationError(u"period should begin after auctionPeriod")
        if (period and period.startDate and data.get("tenderPeriod")
                and data.get("tenderPeriod").endDate
                and period.startDate < data.get("tenderPeriod").endDate):
            raise ValidationError(u"period should begin after tenderPeriod")

    def validate_lots(self, data, value):
        if len(set([lot.guarantee.currency
                    for lot in value if lot.guarantee])) > 1:
            raise ValidationError(
                u"lot guarantee currency should be identical to tender guarantee currency"
            )
        if len(set([lot.fundingKind for lot in value])) > 1:
            raise ValidationError(
                u"lot funding kind should be identical to tender funding kind")

    def validate_yearlyPaymentsPercentageRange(self, data, value):
        if data["fundingKind"] == "other" and value != Decimal("0.8"):
            raise ValidationError(
                "when fundingKind is other, yearlyPaymentsPercentageRange should be equal 0.8"
            )
        if data["fundingKind"] == "budget" and (value > Decimal("0.8")
                                                or value < Decimal("0")):
            raise ValidationError(
                "when fundingKind is budget, yearlyPaymentsPercentageRange should be less or equal 0.8, and more or equal 0"
            )

    def check_auction_time(self):
        if (self.auctionPeriod and self.auctionPeriod.startDate
                and self.auctionPeriod.shouldStartAfter and
                self.auctionPeriod.startDate > calculate_tender_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_tender_business_date(
                        parse_date(lot.auctionPeriod.shouldStartAfter),
                        AUCTION_PERIOD_TIME, self, True)):
                lot.auctionPeriod.startDate = None

    def invalidate_bids_data(self):
        self.check_auction_time()
        self.enquiryPeriod.invalidationDate = get_now()
        for bid in self.bids:
            if bid.status not in ["deleted", "draft"]:
                bid.status = "invalid"

    # Not required milestones
    def validate_milestones(self, data, value):
        pass
Ejemplo n.º 19
0
class Tender(BaseTenderEU):
    procurementMethodType = StringType(default=CD_EU_TYPE)
    status = StringType(choices=[
        'draft', 'active.tendering', 'active.pre-qualification',
        'active.pre-qualification.stand-still', 'active.stage2.pending',
        'active.stage2.waiting', 'complete', 'cancelled', 'unsuccessful'
    ],
                        default='active.tendering')
    # A list of all the companies who entered submissions for the tender.
    bids = SifterListType(ModelType(Bid),
                          default=list(),
                          filter_by='status',
                          filter_in_values=['invalid', 'deleted'])
    TenderID = StringType(required=False)
    stage2TenderID = StringType(required=False)
    features = ListType(ModelType(Feature),
                        validators=[validate_features_uniq])
    lots = ListType(ModelType(Lot),
                    default=list(),
                    validators=[validate_lots_uniq])
    items = ListType(ModelType(BaseEUItem),
                     required=True,
                     min_size=1,
                     validators=[validate_cpv_group, validate_items_uniq])

    class Options:
        roles = roles.copy()

    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 == 'competitive_dialogue':
            role = 'competitive_dialogue'
        else:
            role = 'edit_{}'.format(request.context.status)
        return role

    def __acl__(self):
        acl = [(Allow, '{}_{}'.format(i.owner, i.owner_token),
                'create_qualification_complaint') for i in self.bids
               if i.status in ['active', 'unsuccessful']]
        acl.extend([(Allow, '{}_{}'.format(i.owner, i.owner_token),
                     'create_award_complaint') for i in self.bids
                    if i.status == 'active'])
        acl.extend([
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_tender'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token),
             'upload_tender_documents'),
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_complaint'),
            (Allow, 'g:competitive_dialogue', 'extract_credentials'),
            (Allow, 'g:competitive_dialogue', 'edit_tender'),
        ])
        return acl

    def validate_features(self, data, features):
        validate_features_custom_weight(self, data, features, FEATURES_MAX_SUM)
Ejemplo n.º 20
0
class Cancellation(BaseCancellation):
    class Options:
        roles = RolesFromCsv("Cancellation.csv", relative_to=__file__)

    documents = ListType(ModelType(EUDocument, required=True), default=list())
class Firms(Model):
    identifier = ModelType(Identifier, required=True)
    name = StringType(required=True)
    lots = ListType(ModelType(LotId), default=list())
class Bid(BaseBid):
    class Options:
        roles = {
            'Administrator':
            Administrator_bid_role,
            'embedded':
            view_bid_role,
            'view':
            view_bid_role,
            'create':
            whitelist('value', 'tenderers', 'parameters', 'lotValues',
                      'status'),
            'edit':
            whitelist('value', 'tenderers', 'parameters', 'lotValues',
                      'status'),
            'auction_view':
            whitelist('value', 'lotValues', 'id', 'date', 'parameters',
                      'participationUrl', 'status'),
            'auction_post':
            whitelist('value', 'lotValues', 'id', 'date'),
            'auction_patch':
            whitelist('id', 'lotValues', 'participationUrl'),
            'active.enquiries':
            whitelist(),
            'active.tendering':
            whitelist(),
            'active.pre-qualification':
            whitelist('id', 'status', 'documents', 'tenderers'),
            'active.pre-qualification.stand-still':
            whitelist('id', 'status', 'documents', 'tenderers'),
            'active.auction':
            whitelist('id', 'status', 'documents', 'tenderers'),
            'active.qualification':
            view_bid_role,
            'active.awarded':
            view_bid_role,
            'complete':
            view_bid_role,
            'unsuccessful':
            view_bid_role,
            'bid.unsuccessful':
            whitelist('id', 'status', 'tenderers', 'documents', 'parameters'),
            'cancelled':
            view_bid_role,
            'invalid':
            whitelist('id', 'status'),
            'invalid.pre-qualification':
            whitelist('id', 'status', 'documents', 'tenderers'),
            'deleted':
            whitelist('id', 'status'),
        }

    documents = ListType(ModelType(Document), default=list())
    financialDocuments = ListType(ModelType(Document), default=list())
    lotValues = ListType(ModelType(LotValue), default=list())
    parameters = ListType(ModelType(Parameter),
                          default=list(),
                          validators=[validate_parameters_uniq])
    status = StringType(choices=[
        'draft', 'pending', 'active', 'invalid', 'invalid.pre-qualification',
        'unsuccessful', 'deleted'
    ],
                        default='pending')

    def serialize(self, role=None):
        if role and role != 'create' and self.status in [
                'invalid', 'invalid.pre-qualification', 'deleted'
        ]:
            role = self.status
        elif role and role != 'create' and self.status == 'unsuccessful':
            role = 'bid.unsuccessful'
        return super(Bid, self).serialize(role)

    @serializable(serialized_name="status")
    def serialize_status(self):
        if self.status in [
                'draft', 'invalid', 'deleted'
        ] or self.__parent__.status in ['active.tendering', 'cancelled']:
            return self.status
        if self.__parent__.lots:
            active_lots = [
                lot.id for lot in self.__parent__.lots if lot.status in (
                    'active',
                    'complete',
                )
            ]
            if not self.lotValues:
                return 'invalid'
            elif [
                    i.relatedLot for i in self.lotValues
                    if i.status == 'pending' and i.relatedLot in active_lots
            ]:
                return 'pending'
            elif [
                    i.relatedLot for i in self.lotValues
                    if i.status == 'active' and i.relatedLot in active_lots
            ]:
                return 'active'
            else:
                return 'unsuccessful'
        return self.status

    @bids_validation_wrapper
    def validate_value(self, data, value):
        BaseBid._validator_functions['value'](self, data, value)

    @bids_validation_wrapper
    def validate_lotValues(self, data, lotValues):
        BaseBid._validator_functions['lotValues'](self, data, lotValues)

    @bids_validation_wrapper
    def validate_participationUrl(self, data, participationUrl):
        BaseBid._validator_functions['participationUrl'](self, data,
                                                         participationUrl)

    @bids_validation_wrapper
    def validate_parameters(self, data, parameters):
        BaseBid._validator_functions['parameters'](self, data, parameters)
class Award(BaseTenderUA.awards.model_class):

    items = ListType(ModelType(ItemStage2UA))
class Tender(BaseTender):
    """ Open two stage tender model """
    class Options:
        roles = {
            'plain': plain_role,
            'create': create_role_ts,
            'edit': edit_role_ts,
            'edit_draft': edit_role_ts,
            'edit_active.tendering': edit_role_ts,
            'edit_active.pre-qualification': whitelist('status'),
            'edit_active.pre-qualification.stand-still': whitelist(),
            'edit_active.auction': whitelist(),
            'edit_active.qualification': whitelist(),
            'edit_active.awarded': whitelist(),
            'edit_complete': whitelist(),
            'edit_unsuccessful': whitelist(),
            'edit_cancelled': whitelist(),
            'view': view_role,
            'listing': listing_role,
            'auction_view': auction_view_role,
            'auction_post': auction_post_role,
            'auction_patch': auction_patch_role,
            'draft': enquiries_role,
            'active.tendering': enquiries_role,
            'active.pre-qualification': pre_qualifications_role,
            'active.pre-qualification.stand-still': pre_qualifications_role,
            'active.auction': pre_qualifications_role,
            'active.qualification': view_role,
            'active.awarded': view_role,
            'complete': view_role,
            'unsuccessful': view_role,
            'cancelled': view_role,
            'chronograph': chronograph_role,
            'chronograph_view': chronograph_view_role,
            'Administrator': Administrator_role,
            'default': schematics_default_role,
            'contracting': whitelist('doc_id', 'owner'),
        }

    procurementMethodType = StringType(default="aboveThresholdTS")
    title_en = StringType()

    enquiryPeriod = ModelType(EnquiryPeriod, required=False)
    tenderPeriod = ModelType(PeriodStartEndRequired, required=True)
    auctionPeriod = ModelType(TenderAuctionPeriod, default={})
    documents = ListType(ModelType(Document), default=list())  # All
    items = ListType(
        ModelType(Item),
        required=True,
        min_size=1,
        validators=[validate_cpv_group, validate_items_uniq]
    )  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    complaints = ListType(ComplaintModelType(Complaint), default=list())
    contracts = ListType(ModelType(Contract), default=list())
    cancellations = ListType(ModelType(Cancellation), default=list())
    awards = ListType(ModelType(Award), default=list())
    procuringEntity = ModelType(
        ProcuringEntity, required=True
    )  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    bids = SifterListType(
        BidModelType(Bid),
        default=list(),
        filter_by='status',
        filter_in_values=['invalid', 'invalid.pre-qualification', 'deleted']
    )  # A list of all the companies who entered submissions for the tender.
    qualifications = ListType(ModelType(Qualification), default=list())
    qualificationPeriod = ModelType(Period)
    lots = ListType(ModelType(Lot),
                    default=list(),
                    validators=[validate_lots_uniq])
    status = StringType(choices=[
        'draft', 'active.tendering', 'active.pre-qualification',
        'active.pre-qualification.stand-still', 'active.auction',
        'active.qualification', 'active.awarded', 'complete', 'cancelled',
        'unsuccessful'
    ],
                        default='active.tendering')

    create_accreditation = 3
    edit_accreditation = 4
    procuring_entity_kinds = ['general', 'special', 'defense']
    block_tender_complaint_status = OpenUATender.block_tender_complaint_status

    #block_complaint_status = OpenUATender.block_complaint_status

    def __acl__(self):
        acl = [(Allow, '{}_{}'.format(i.owner, i.owner_token),
                'create_qualification_complaint') for i in self.bids
               if i.status in ['active', 'unsuccessful']]
        acl.extend([(Allow, '{}_{}'.format(i.owner, i.owner_token),
                     'create_award_complaint') for i in self.bids
                    if i.status == 'active'])
        acl.extend([
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_tender'),
            (Allow, '{}_{}'.format(self.owner, self.owner_token),
             'upload_tender_documents'),
            (Allow, '{}_{}'.format(self.owner,
                                   self.owner_token), 'edit_complaint'),
        ])
        return acl

    def initialize(self):
        self.tenderPeriod.startDate = get_now()
        endDate = calculate_business_date(self.tenderPeriod.endDate,
                                          -QUESTIONS_STAND_STILL, self)
        self.enquiryPeriod = EnquiryPeriod(
            dict(startDate=self.tenderPeriod.startDate,
                 endDate=endDate,
                 invalidationDate=self.enquiryPeriod
                 and self.enquiryPeriod.invalidationDate,
                 clarificationsUntil=endDate))
        now = get_now()
        self.date = now
        if self.lots:
            for lot in self.lots:
                lot.date = now

    @serializable(serialized_name="enquiryPeriod",
                  type=ModelType(EnquiryPeriod))
    def tender_enquiryPeriod(self):
        endDate = calculate_business_date(self.tenderPeriod.endDate,
                                          -QUESTIONS_STAND_STILL, self)
        return EnquiryPeriod(
            dict(startDate=self.tenderPeriod.startDate,
                 endDate=endDate,
                 invalidationDate=self.enquiryPeriod
                 and self.enquiryPeriod.invalidationDate,
                 clarificationsUntil=endDate))

    @serializable(type=ModelType(Period))
    def complaintPeriod(self):
        return Period(
            dict(startDate=self.tenderPeriod.startDate,
                 endDate=calculate_business_date(self.tenderPeriod.startDate,
                                                 COMPLAINT_SUBMIT_TIME, self)))

    @serializable(serialize_when_none=False)
    def next_check(self):
        now = get_now()
        checks = []
        if self.status == 'active.tendering' and self.tenderPeriod.endDate:
            checks.append(self.tenderPeriod.endDate.astimezone(TZ))
        elif self.status == 'active.pre-qualification.stand-still' and self.qualificationPeriod and self.qualificationPeriod.endDate and not any(
            [
                i.status in self.block_complaint_status
                for q in self.qualifications for i in q.complaints
            ]):
            checks.append(self.qualificationPeriod.endDate.astimezone(TZ))
        elif not self.lots and self.status == 'active.auction' and self.auctionPeriod and self.auctionPeriod.startDate and not self.auctionPeriod.endDate:
            if now < self.auctionPeriod.startDate:
                checks.append(self.auctionPeriod.startDate.astimezone(TZ))
            elif now < calc_auction_end_time(
                    self.numberOfBids,
                    self.auctionPeriod.startDate).astimezone(TZ):
                checks.append(
                    calc_auction_end_time(
                        self.numberOfBids,
                        self.auctionPeriod.startDate).astimezone(TZ))
        elif self.lots and self.status == 'active.auction':
            for lot in self.lots:
                if lot.status != 'active' or not lot.auctionPeriod or not lot.auctionPeriod.startDate or lot.auctionPeriod.endDate:
                    continue
                if now < lot.auctionPeriod.startDate:
                    checks.append(lot.auctionPeriod.startDate.astimezone(TZ))
                elif now < calc_auction_end_time(
                        lot.numberOfBids,
                        lot.auctionPeriod.startDate).astimezone(TZ):
                    checks.append(
                        calc_auction_end_time(
                            lot.numberOfBids,
                            lot.auctionPeriod.startDate).astimezone(TZ))
        elif not self.lots and self.status == 'active.awarded' and not any(
            [i.status in self.block_complaint_status
             for i in self.complaints]) and not any([
                 i.status in self.block_complaint_status for a in self.awards
                 for i in a.complaints
             ]):
            standStillEnds = [
                a.complaintPeriod.endDate.astimezone(TZ) for a in self.awards
                if a.complaintPeriod.endDate
            ]
            last_award_status = self.awards[-1].status if self.awards else ''
            if standStillEnds and last_award_status == 'unsuccessful':
                checks.append(max(standStillEnds))
        elif self.lots and self.status in [
                'active.qualification', 'active.awarded'
        ] and not any([
                i.status in self.block_complaint_status
                and i.relatedLot is None for i in self.complaints
        ]):
            for lot in self.lots:
                if lot['status'] != 'active':
                    continue
                lot_awards = [i for i in self.awards if i.lotID == lot.id]
                pending_complaints = any([
                    i['status'] in self.block_complaint_status
                    and i.relatedLot == lot.id for i in self.complaints
                ])
                pending_awards_complaints = any([
                    i.status in self.block_complaint_status for a in lot_awards
                    for i in a.complaints
                ])
                standStillEnds = [
                    a.complaintPeriod.endDate.astimezone(TZ)
                    for a in lot_awards if a.complaintPeriod.endDate
                ]
                last_award_status = lot_awards[-1].status if lot_awards else ''
                if not pending_complaints and not pending_awards_complaints and standStillEnds and last_award_status == 'unsuccessful':
                    checks.append(max(standStillEnds))
        return min(checks).isoformat() if checks else None

    def 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, TENDERING_DURATION, data) > period.endDate:
            raise ValidationError(
                u"tenderPeriod should be greater than {} days".format(
                    TENDERING_DAYS))

    @serializable
    def numberOfBids(self):
        """A property that is serialized by schematics exports."""
        return len([
            bid for bid in self.bids if bid.status in (
                "active",
                "pending",
            )
        ])

    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 invalidate_bids_data(self):
        self.check_auction_time()
        self.enquiryPeriod.invalidationDate = get_now()
        for bid in self.bids:
            if bid.status not in ["deleted", "draft"]:
                bid.status = "invalid"
Ejemplo n.º 25
0
class Bid(Model):
    class Options:
        roles = {
            'Administrator':
            Administrator_bid_role,
            'embedded':
            view_bid_role,
            'view':
            view_bid_role,
            'create':
            whitelist('value', 'status', 'tenderers', 'parameters',
                      'lotValues', 'documents'),
            'edit':
            whitelist('value', 'status', 'tenderers', 'parameters',
                      'lotValues'),
            'auction_view':
            whitelist('value', 'lotValues', 'id', 'date', 'parameters',
                      'participationUrl'),
            'auction_post':
            whitelist('value', 'lotValues', 'id', 'date'),
            'auction_patch':
            whitelist('id', 'lotValues', 'participationUrl'),
            'active.enquiries':
            whitelist(),
            'active.tendering':
            whitelist(),
            'active.auction':
            whitelist(),
            'active.qualification':
            view_bid_role,
            'active.awarded':
            view_bid_role,
            'complete':
            view_bid_role,
            'unsuccessful':
            view_bid_role,
            'cancelled':
            view_bid_role,
        }

    def __local_roles__(self):
        return dict([('{}_{}'.format(self.owner,
                                     self.owner_token), 'bid_owner')])

    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())
    date = IsoDateTimeType(default=get_now)
    id = MD5Type(required=True, default=lambda: uuid4().hex)
    status = StringType(choices=['active', 'draft'], default='active')
    value = ModelType(Value)
    documents = ListType(ModelType(Document), default=list())
    participationUrl = URLType()
    owner_token = StringType()
    owner = StringType()

    __name__ = ''

    def import_data(self, raw_data, **kw):
        """
        Converts and imports the raw data into the instance of the model
        according to the fields in the model.

        :param raw_data:
            The data to be imported.
        """
        data = self.convert(raw_data, **kw)
        del_keys = [k for k in data.keys() if k != "value" and data[k] is None]
        for k in del_keys:
            del data[k]

        self._data.update(data)
        return self

    def __acl__(self):
        return [
            (Allow, '{}_{}'.format(self.owner, self.owner_token), 'edit_bid'),
        ]

    def validate_participationUrl(self, data, url):
        if url and isinstance(data['__parent__'], Model) and get_tender(
                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):
            tender = data['__parent__']
            if tender.lots and not values:
                raise ValidationError(u'This field is required.')
            if tender.get('revisions') and tender['revisions'][
                    0].date > BID_LOTVALUES_VALIDATION_FROM and values:
                lots = [i.relatedLot for i in values]
                if len(lots) != len(set(lots)):
                    raise ValidationError(
                        u'bids don\'t allow duplicated proposals')

    def validate_value(self, data, value):
        if isinstance(data['__parent__'], Model):
            tender = data['__parent__']
            if tender.lots:
                if value:
                    raise ValidationError(
                        u"value should be posted for each lot of bid")
            else:
                if not value:
                    raise ValidationError(u'This field is required.')
                if tender.value.amount < value.amount:
                    raise ValidationError(
                        u"value of bid should be less than value of tender")
                if tender.get('value').currency != value.currency:
                    raise ValidationError(
                        u"currency of bid should be identical to currency of value of tender"
                    )
                if tender.get(
                        'value'
                ).valueAddedTaxIncluded != value.valueAddedTaxIncluded:
                    raise ValidationError(
                        u"valueAddedTaxIncluded of bid should be identical to valueAddedTaxIncluded of value of tender"
                    )

    def validate_parameters(self, data, parameters):
        if isinstance(data['__parent__'], Model):
            tender = data['__parent__']
            if tender.lots:
                lots = [i.relatedLot for i in data['lotValues']]
                items = [i.id for i in tender.items if i.relatedLot in lots]
                codes = dict([
                    (i.code, [x.value for x in i.enum])
                    for i in (tender.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 tender.features:
                raise ValidationError(u'This field is required.')
            elif set([i['code'] for i in parameters]) != set(
                [i.code for i in (tender.features or [])]):
                raise ValidationError(u"All features parameters is required.")
Ejemplo n.º 26
0
class ProcuringEntity(BaseProcuringEntity):

    contactPoint = ModelType(ContactPoint, required=True)
    additionalContactPoints = ListType(ModelType(ContactPoint, required=True),
                                       required=False)
Ejemplo n.º 27
0
class BaseTender(SchematicsDocument, Model):
    title = StringType(required=True)
    title_en = StringType()
    title_ru = StringType()
    documents = ListType(
        ModelType(Document),
        default=list())  # All documents and attachments related to the tender.
    description = StringType()
    description_en = StringType()
    description_ru = StringType()
    date = IsoDateTimeType()
    dateModified = IsoDateTimeType()
    tenderID = StringType(
    )  # TenderID should always be the same as the OCID. It is included to make the flattened data structure more convenient.
    owner = StringType()
    owner_token = StringType()
    mode = StringType(choices=['test'])
    procurementMethodRationale = StringType(
    )  # Justification of procurement method, especially in the case of Limited tendering.
    procurementMethodRationale_en = StringType()
    procurementMethodRationale_ru = StringType()
    if SANDBOX_MODE:
        procurementMethodDetails = StringType()

    _attachments = DictType(DictType(BaseType),
                            default=dict())  # couchdb attachments
    revisions = ListType(ModelType(Revision), default=list())

    def __repr__(self):
        return '<%s:%r@%r>' % (type(self).__name__, self.id, self.rev)

    def __local_roles__(self):
        roles = dict([('{}_{}'.format(self.owner,
                                      self.owner_token), 'tender_owner')])
        return roles

    @serializable(serialized_name='id')
    def doc_id(self):
        """A property that is serialized by schematics exports."""
        return self._id

    def import_data(self, raw_data, **kw):
        """
        Converts and imports the raw data into the instance of the model
        according to the fields in the model.
        :param raw_data:
            The data to be imported.
        """
        data = self.convert(raw_data, **kw)
        del_keys = [
            k for k in data.keys()
            if data[k] == self.__class__.fields[k].default
            or data[k] == getattr(self, k)
        ]
        for k in del_keys:
            del data[k]

        self._data.update(data)
        return self

    def validate_procurementMethodDetails(self, *args, **kw):
        if self.mode and self.mode == 'test' and self.procurementMethodDetails and self.procurementMethodDetails != '':
            raise ValidationError(
                u"procurementMethodDetails should be used with mode test")
class Tender(BaseTender):
    """Data regarding tender process - publicly inviting prospective contractors
    to submit bids for evaluation and selecting a winner or winners.
    """
    class Options:
        roles = {
            'plain': plain_role,
            'create': create_role,
            'edit': edit_role,
            'edit_draft': draft_role,
            'edit_active.enquiries': edit_role,
            'edit_active.tendering': whitelist(),
            'edit_active.auction': whitelist(),
            'edit_active.qualification': whitelist(),
            'edit_active.awarded': whitelist(),
            'edit_complete': whitelist(),
            'edit_unsuccessful': whitelist(),
            'edit_cancelled': whitelist(),
            'view': view_role,
            'listing': listing_role,
            'auction_view': auction_view_role,
            'auction_post': auction_post_role,
            'auction_patch': auction_patch_role,
            'draft': enquiries_role,
            'active.enquiries': enquiries_role,
            'active.tendering': enquiries_role,
            'active.auction': auction_role,
            'active.qualification': view_role,
            'active.awarded': view_role,
            'complete': view_role,
            'unsuccessful': view_role,
            'cancelled': view_role,
            'chronograph': chronograph_role,
            'chronograph_view': chronograph_view_role,
            'Administrator': Administrator_role,
            'default': schematics_default_role,
            'contracting': whitelist('doc_id', 'owner'),
        }

    items = ListType(
        ModelType(Item),
        required=True,
        min_size=1,
        validators=[validate_items_uniq]
    )  # The goods and services to be purchased, broken into line items wherever possible. Items should not be duplicated, but a quantity of 2 specified instead.
    value = ModelType(
        Value, required=True)  # The total estimated value of the procurement.
    enquiryPeriod = ModelType(
        PeriodEndRequired, required=True
    )  # The period during which enquiries may be made and will be answered.
    tenderPeriod = ModelType(
        PeriodEndRequired, required=True
    )  # The period when the tender is open for submissions. The end date is the closing date for tender submissions.
    hasEnquiries = BooleanType(
    )  # A Yes/No field as to whether enquiries were part of tender process.
    awardPeriod = ModelType(
        Period
    )  # The date or period on which an award is anticipated to be made.
    numberOfBidders = IntType(
    )  # The number of unique tenderers who participated in the tender
    bids = ListType(ModelType(Bid), default=list(
    ))  # A list of all the companies who entered submissions for the tender.
    procuringEntity = ModelType(
        ProcuringEntity, required=True
    )  # The entity managing the procurement, which may be different from the buyer who is paying / using the items being procured.
    awards = ListType(ModelType(Award), default=list())
    contracts = ListType(ModelType(Contract), default=list())
    auctionPeriod = ModelType(TenderAuctionPeriod, default={})
    minimalStep = ModelType(Value, required=True)
    questions = ListType(ModelType(Question), default=list())
    complaints = ListType(ComplaintModelType(Complaint), default=list())
    auctionUrl = URLType()
    cancellations = ListType(ModelType(Cancellation), default=list())
    features = ListType(ModelType(Feature),
                        validators=[validate_features_uniq])
    lots = ListType(ModelType(Lot),
                    default=list(),
                    validators=[validate_lots_uniq])
    guarantee = ModelType(Guarantee)

    procurementMethodType = StringType(default="belowThreshold")

    procuring_entity_kinds = ['general', 'special', 'defense', 'other']
    block_complaint_status = ['answered', 'pending']

    def __local_roles__(self):
        roles = dict([('{}_{}'.format(self.owner,
                                      self.owner_token), 'tender_owner')])
        for i in self.bids:
            roles['{}_{}'.format(i.owner, i.owner_token)] = 'bid_owner'
        return roles

    @serializable(serialize_when_none=False)
    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

    @serializable
    def numberOfBids(self):
        """A property that is serialized by schematics exports."""
        return len(self.bids)

    @serializable(serialized_name="value", type=ModelType(Value))
    def tender_value(self):
        return Value(
            dict(amount=sum([i.value.amount for i in self.lots]),
                 currency=self.value.currency,
                 valueAddedTaxIncluded=self.value.valueAddedTaxIncluded)
        ) if self.lots else self.value

    @serializable(serialized_name="guarantee",
                  serialize_when_none=False,
                  type=ModelType(Guarantee))
    def tender_guarantee(self):
        if self.lots:
            lots_amount = [
                i.guarantee.amount for i in self.lots if i.guarantee
            ]
            if not lots_amount:
                return self.guarantee
            guarantee = {'amount': sum(lots_amount)}
            lots_currency = [
                i.guarantee.currency for i in self.lots if i.guarantee
            ]
            guarantee['currency'] = lots_currency[0] if lots_currency else None
            if self.guarantee:
                guarantee['currency'] = self.guarantee.currency
            return Guarantee(guarantee)
        else:
            return self.guarantee

    @serializable(serialized_name="minimalStep", type=ModelType(Value))
    def tender_minimalStep(self):
        return Value(
            dict(amount=min([i.minimalStep.amount for i in self.lots]),
                 currency=self.minimalStep.currency,
                 valueAddedTaxIncluded=self.minimalStep.valueAddedTaxIncluded)
        ) if self.lots else self.minimalStep

    def validate_items(self, data, items):
        cpv_336_group = items[
            0].classification.id[:3] == '336' if items else False
        if not cpv_336_group and (
                data.get('revisions')[0].date if data.get('revisions') else
                get_now()) > CPV_ITEMS_CLASS_FROM and items and len(
                    set([i.classification.id[:4] for i in items])) != 1:
            raise ValidationError(u"CPV class of items should be identical")
        else:
            validate_cpv_group(items)

    def validate_features(self, data, features):
        if features and data['lots'] and any([
                round(
                    vnmax([
                        i for i in features if i.featureOf == 'tenderer'
                        or i.featureOf == 'lot' and i.relatedItem == lot['id']
                        or i.featureOf == 'item' and i.relatedItem in [
                            j.id
                            for j in data['items'] if j.relatedLot == lot['id']
                        ]
                    ]), 15) > 0.3 for lot in data['lots']
        ]):
            raise ValidationError(
                u"Sum of max value of all features for lot should be less then or equal to 30%"
            )
        elif features and not data['lots'] and round(vnmax(features),
                                                     15) > 0.3:
            raise ValidationError(
                u"Sum of max value of all features should be less then or equal to 30%"
            )

    def validate_auctionUrl(self, data, url):
        if url and data['lots']:
            raise ValidationError(u"url should be posted for each lot")

    def validate_minimalStep(self, data, value):
        if value and value.amount and data.get('value'):
            if data.get('value').amount < value.amount:
                raise ValidationError(
                    u"value should be less than value of tender")
            if data.get('value').currency != value.currency:
                raise ValidationError(
                    u"currency should be identical to currency of value of tender"
                )
            if data.get('value'
                        ).valueAddedTaxIncluded != value.valueAddedTaxIncluded:
                raise ValidationError(
                    u"valueAddedTaxIncluded should be identical to valueAddedTaxIncluded of value of tender"
                )

    def validate_tenderPeriod(self, data, period):
        if period and period.startDate and data.get(
                'enquiryPeriod') and data.get(
                    'enquiryPeriod').endDate and period.startDate < data.get(
                        'enquiryPeriod').endDate:
            raise ValidationError(u"period should begin after enquiryPeriod")

    def validate_awardPeriod(self, data, period):
        if period and period.startDate and data.get(
                'auctionPeriod') and data.get(
                    'auctionPeriod').endDate and period.startDate < data.get(
                        'auctionPeriod').endDate:
            raise ValidationError(u"period should begin after auctionPeriod")
        if period and period.startDate and data.get(
                'tenderPeriod') and data.get(
                    'tenderPeriod').endDate and period.startDate < data.get(
                        'tenderPeriod').endDate:
            raise ValidationError(u"period should begin after tenderPeriod")

    def validate_lots(self, data, value):
        if len(set([lot.guarantee.currency
                    for lot in value if lot.guarantee])) > 1:
            raise ValidationError(
                u"lot guarantee currency should be identical to tender guarantee currency"
            )
Ejemplo n.º 29
0
class Contract(BaseContract):
    documents = ListType(ModelType(EUDocument, required=True), default=list())
    items = ListType(ModelType(Item, required=True))
Ejemplo n.º 30
0
class ElectronicCatalogueFramework(Framework):
    class Options:
        namespace = "Framework"
        _status_view_role = blacklist(
            "doc_type",
            "successful",
            "transfer_token",
            "owner_token",
            "revisions",
            "_id",
            "_rev",
            "__parent__",
        )
        _edit_role = _status_view_role + blacklist(
            "frameworkType", "prettyID", "period", "enquiryPeriod",
            "dateModified", "date", "doc_id")
        _create_role = _edit_role + blacklist("status")

        roles = {
            "create":
            _create_role,
            "edit_draft":
            _edit_role + blacklist("owner", "old_date"),
            "edit_active":
            whitelist("status", "procuringEntity", "qualificationPeriod",
                      "description", "description_en", "description_ru",
                      "documents", "frameworkDetails"),
            "draft":
            _status_view_role,
            "active":
            _status_view_role,
            "complete":
            _status_view_role,
            "unsuccessful":
            _status_view_role,
            "view":
            _edit_role + whitelist(
                "date",
                "period",
                "enquiryPeriod",
                "prettyID",
                "documents",
                "doc_id",
                "dateModified",
                "status",
                "owner",
                "next_check",
            ),
            "chronograph":
            whitelist("next_check"),
            "chronograph_view":
            _status_view_role,
            "Administrator":
            whitelist("status", "mode"),
            "default":
            blacklist("doc_id", "__parent__"),  # obj.store() use default role
            "plain":
            blacklist(  # is used for getting patches
                "_attachments", "revisions", "dateModified", "_id", "_rev",
                "doc_type", "__parent__"),
            "listing":
            whitelist("dateModified", "doc_id"),
            "embedded":
            blacklist("_id", "_rev", "doc_type", "__parent__"),
        }

    status = StringType(
        choices=[
            "draft",
            "active",
            "deleted",
            "complete",
            "unsuccessful",
        ],
        default="draft",
    )
    period = ModelType(BasePeriodEndRequired)
    qualificationPeriod = ModelType(BasePeriodEndRequired, required=True)
    enquiryPeriod = ModelType(BasePeriodEndRequired)
    frameworkType = StringType(default="electronicCatalogue")
    procuringEntity = ModelType(CentralProcuringEntity, required=True)
    classification = ModelType(DKClassification, required=True)
    additionalClassifications = ListType(ModelType(BaseClassification))
    documents = ListType(ModelType(Document, required=True), default=list())

    successful = BooleanType(required=True, default=False)

    procuring_entity_kinds = ["central"]
    central_accreditations = (ACCR_5, )
    edit_accreditations = (ACCR_5, )

    @serializable(serialize_when_none=False)
    def next_check(self):
        checks = []
        if self.status == "active":
            if not self.successful:
                unsuccessful_status_check = get_framework_unsuccessful_status_check_date(
                    self)
                if unsuccessful_status_check:
                    checks.append(unsuccessful_status_check)
            checks.append(self.qualificationPeriod.endDate)
        return min(checks).isoformat() if checks else None

    def __acl__(self):
        acl = super(ElectronicCatalogueFramework, self).__acl__()
        acl.append((Allow, "{}_{}".format(self.owner, self.owner_token),
                    "upload_framework_documents"))
        return acl