Exemple #1
0
 def post(self):
     """Report auction results for lot.
     """
     apply_patch(self.request, save=False, src=self.request.validated['tender_src'])
     if all([i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.numberOfBids > 1]):
         add_next_award(self.request)
     if save_tender(self.request):
         LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_lot_auction_post'}))
         return {'data': self.request.validated['tender'].serialize(self.request.validated['tender'].status)}
 def patch(self):
     """Post a complaint resolution for award
     """
     tender = self.request.validated["tender"]
     if tender.status not in ["active.qualification", "active.awarded"]:
         self.request.errors.add(
             "body", "data", "Can't update complaint in current ({}) tender status".format(tender.status)
         )
         self.request.errors.status = 403
         return
     if any([i.status != "active" for i in tender.lots if i.id == self.request.validated["award"].lotID]):
         self.request.errors.add("body", "data", "Can update complaint only in active lot status")
         self.request.errors.status = 403
         return
     complaint = self.request.context
     if complaint.status != "pending":
         self.request.errors.add(
             "body", "data", "Can't update complaint in current ({}) status".format(complaint.status)
         )
         self.request.errors.status = 403
         return
     if self.request.validated["data"].get("status", complaint.status) == "cancelled":
         self.request.errors.add("body", "data", "Can't cancel complaint")
         self.request.errors.status = 403
         return
     apply_patch(self.request, save=False, src=complaint.serialize())
     if complaint.status == "resolved":
         award = self.request.validated["award"]
         if tender.status == "active.awarded":
             tender.status = "active.qualification"
             tender.awardPeriod.endDate = None
         now = get_now()
         if award.status == "unsuccessful":
             for i in tender.awards[tender.awards.index(award) :]:
                 if i.lotID != award.lotID:
                     continue
                 i.complaintPeriod.endDate = now + STAND_STILL_TIME
                 i.status = "cancelled"
                 for j in i.complaints:
                     if j.status == "pending":
                         j.status = "cancelled"
         for i in tender.contracts:
             if award.id == i.awardID:
                 i.status = "cancelled"
         award.complaintPeriod.endDate = now + STAND_STILL_TIME
         award.status = "cancelled"
         add_next_award(self.request)
     elif complaint.status in ["declined", "invalid"] and tender.status == "active.awarded":
         check_tender_status(self.request)
     if save_tender(self.request):
         LOGGER.info(
             "Updated tender award complaint {}".format(self.request.context.id),
             extra=context_unpack(self.request, {"MESSAGE_ID": "tender_award_complaint_patch"}),
         )
         return {"data": complaint.serialize("view")}
 def patch(self):
     """Post a complaint resolution for award
     """
     tender = self.request.validated["tender"]
     if tender.status not in ["active.qualification", "active.awarded"]:
         self.request.errors.add(
             "body", "data", "Can't update complaint in current ({}) tender status".format(tender.status)
         )
         self.request.errors.status = 403
         return
     complaint = self.request.context
     if complaint.status != "pending":
         self.request.errors.add(
             "body", "data", "Can't update complaint in current ({}) status".format(complaint.status)
         )
         self.request.errors.status = 403
         return
     apply_patch(self.request, save=False, src=complaint.serialize())
     if complaint.status == "cancelled":
         self.request.errors.add("body", "data", "Can't cancel complaint")
         self.request.errors.status = 403
         return
     if complaint.status == "resolved":
         award = self.request.validated["award"]
         if tender.status == "active.awarded":
             tender.status = "active.qualification"
             tender.awardPeriod.endDate = None
         if award.status == "unsuccessful":
             for i in tender.awards[tender.awards.index(award) :]:
                 i.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
                 i.status = "cancelled"
                 for j in i.complaints:
                     if j.status == "pending":
                         j.status = "cancelled"
         for i in award.contracts:
             i.status = "cancelled"
         award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
         award.status = "cancelled"
         add_next_award(self.request)
     elif complaint.status in ["declined", "invalid"] and tender.status == "active.awarded":
         pending_complaints = [i for i in tender.complaints if i.status == "pending"]
         pending_awards_complaints = [i for a in tender.awards for i in a.complaints if i.status == "pending"]
         stand_still_ends = [a.complaintPeriod.endDate for a in tender.awards if a.complaintPeriod.endDate]
         stand_still_end = max(stand_still_ends) if stand_still_ends else get_now()
         stand_still_time_expired = stand_still_end < get_now()
         if not pending_complaints and not pending_awards_complaints and stand_still_time_expired:
             active_awards = [a for a in tender.awards if a.status == "active"]
             if not active_awards:
                 tender.status = "unsuccessful"
     if save_tender(self.request):
         LOGGER.info(
             "Updated tender award complaint {}".format(self.request.context.id),
             extra={"MESSAGE_ID": "tender_award_complaint_patch"},
         )
         return {"data": complaint.serialize("view")}
 def cancel_lot(self, cancellation=None):
     if not cancellation:
         cancellation = self.context
     tender = self.request.validated['tender']
     [setattr(i, 'status', 'cancelled') for i in tender.lots if i.id == cancellation.relatedLot]
     statuses = set([lot.status for lot in tender.lots])
     if statuses == set(['cancelled']):
         self.cancel_tender()
     elif not statuses.difference(set(['unsuccessful', 'cancelled'])):
         tender.status = 'unsuccessful'
     elif not statuses.difference(set(['complete', 'unsuccessful', 'cancelled'])):
         tender.status = 'complete'
     if tender.status == 'active.auction' and all([
         i.auctionPeriod and i.auctionPeriod.endDate
         for i in self.request.validated['tender'].lots
         if i.numberOfBids > 1 and i.status == 'active'
     ]):
         add_next_award(self.request)
Exemple #5
0
 def cancel_lot(self, cancellation=None):
     if not cancellation:
         cancellation = self.context
     tender = self.request.validated['tender']
     [
         setattr(i, 'status', 'cancelled') for i in tender.lots
         if i.id == cancellation.relatedLot
     ]
     statuses = set([lot.status for lot in tender.lots])
     if statuses == set(['cancelled']):
         self.cancel_tender()
     elif not statuses.difference(set(['unsuccessful', 'cancelled'])):
         tender.status = 'unsuccessful'
     elif not statuses.difference(
             set(['complete', 'unsuccessful', 'cancelled'])):
         tender.status = 'complete'
     if tender.status == 'active.auction' and all([
             i.auctionPeriod and i.auctionPeriod.endDate
             for i in self.request.validated['tender'].lots
             if i.numberOfBids > 1 and i.status == 'active'
     ]):
         add_next_award(self.request)
Exemple #6
0
    def collection_post(self):
        """Report auction results.

        Report auction results
        ----------------------

        Example request to report auction results:

        .. sourcecode:: http

            POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/auction HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "dateModified": "2014-10-27T08:06:58.158Z",
                    "bids": [
                        {
                            "value": {
                                "amount": 400,
                                "currency": "UAH"
                            }
                        },
                        {
                            "value": {
                                "amount": 385,
                                "currency": "UAH"
                            }
                        }
                    ]
                }
            }

        This is what one should expect in response:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: application/json

            {
                "data": {
                    "dateModified": "2014-10-27T08:06:58.158Z",
                    "bids": [
                        {
                            "value": {
                                "amount": 400,
                                "currency": "UAH",
                                "valueAddedTaxIncluded": true
                            }
                        },
                        {
                            "value": {
                                "amount": 385,
                                "currency": "UAH",
                                "valueAddedTaxIncluded": true
                            }
                        }
                    ],
                    "minimalStep":{
                        "amount": 35,
                        "currency": "UAH"
                    },
                    "tenderPeriod":{
                        "startDate": "2014-11-04T08:00:00"
                    }
                }
            }

        """
        apply_patch(self.request,
                    save=False,
                    src=self.request.validated['tender_src'])
        if all([
                i.auctionPeriod and i.auctionPeriod.endDate
                for i in self.request.validated['tender'].lots
                if i.numberOfBids > 1 and i.status == 'active'
        ]):
            add_next_award(self.request)
        if save_tender(self.request):
            self.LOGGER.info(
                'Report auction results',
                extra=context_unpack(self.request,
                                     {'MESSAGE_ID': 'tender_auction_post'}))
            return {
                'data':
                self.request.validated['tender'].serialize(
                    self.request.validated['tender'].status)
            }
    def patch(self):
        """Update of award

        Example request to change the award:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    }
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "date": "2014-10-28T11:44:17.947Z",
                    "status": "active",
                    "suppliers": [
                        {
                            "id": {
                                "name": "Державне управління справами",
                                "scheme": "https://ns.openprocurement.org/ua/edrpou",
                                "uid": "00037256",
                                "uri": "http://www.dus.gov.ua/"
                            },
                            "address": {
                                "countryName": "Україна",
                                "postalCode": "01220",
                                "region": "м. Київ",
                                "locality": "м. Київ",
                                "streetAddress": "вул. Банкова, 11, корпус 1"
                            }
                        }
                    ],
                    "value": {
                        "amount": 600,
                        "currency": "UAH",
                        "valueAddedTaxIncluded": true
                    }
                }
            }

        """
        tender = self.request.validated['tender']
        if tender.status not in ['active.qualification', 'active.awarded']:
            self.request.errors.add('body', 'data', 'Can\'t update award in current ({}) tender status'.format(tender.status))
            self.request.errors.status = 403
            return
        award = self.request.context
        if any([i.status != 'active' for i in tender.lots if i.id == award.lotID]):
            self.request.errors.add('body', 'data', 'Can update award only in active lot status')
            self.request.errors.status = 403
            return
        award_status = award.status
        apply_patch(self.request, save=False, src=self.request.context.serialize())
        if award_status == 'pending' and award.status == 'active':
            award.complaintPeriod.endDate = calculate_business_date(get_now(), STAND_STILL_TIME, tender, True)
            tender.contracts.append(type(tender).contracts.model_class({
                'awardID': award.id,
                'suppliers': award.suppliers,
                'value': award.value,
                'items': [i for i in tender.items if i.relatedLot == award.lotID ],
                'contractID': '{}-{}{}'.format(tender.tenderID, self.server_id, len(tender.contracts) + 1) }))
            add_next_award(self.request)
        elif award_status == 'active' and award.status == 'cancelled':
            now = get_now()
            if award.complaintPeriod.endDate > now:
                award.complaintPeriod.endDate = now
            for j in award.complaints:
                if j.status not in ['invalid', 'resolved', 'declined']:
                    j.status = 'cancelled'
                    j.cancellationReason = 'cancelled'
                    j.dateCanceled = now
            for i in tender.contracts:
                if i.awardID == award.id:
                    i.status = 'cancelled'
            add_next_award(self.request)
        elif award_status == 'pending' and award.status == 'unsuccessful':
            award.complaintPeriod.endDate = calculate_business_date(get_now(), STAND_STILL_TIME, tender, True)
            add_next_award(self.request)
        elif award_status == 'unsuccessful' and award.status == 'cancelled' and any([i.status in ['claim', 'answered', 'pending', 'resolved'] for i in award.complaints]):
            if tender.status == 'active.awarded':
                tender.status = 'active.qualification'
                tender.awardPeriod.endDate = None
            now = get_now()
            award.complaintPeriod.endDate = now
            cancelled_awards = []
            for i in tender.awards[tender.awards.index(award):]:
                if i.lotID != award.lotID:
                    continue
                i.complaintPeriod.endDate = now
                i.status = 'cancelled'
                for j in i.complaints:
                    if j.status not in ['invalid', 'resolved', 'declined']:
                        j.status = 'cancelled'
                        j.cancellationReason = 'cancelled'
                        j.dateCanceled = now
                cancelled_awards.append(i.id)
            for i in tender.contracts:
                if i.awardID in cancelled_awards:
                    i.status = 'cancelled'
            add_next_award(self.request)
        elif self.request.authenticated_role != 'Administrator' and not(award_status == 'pending' and award.status == 'pending'):
            self.request.errors.add('body', 'data', 'Can\'t update award in current ({}) status'.format(award_status))
            self.request.errors.status = 403
            return
        if save_tender(self.request):
            self.LOGGER.info('Updated tender award {}'.format(self.request.context.id),
                        extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_award_patch'}))
            return {'data': award.serialize("view")}
Exemple #8
0
    def patch(self):
        """Update of award

        Example request to change the award:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    }
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "date": "2014-10-28T11:44:17.947Z",
                    "status": "active",
                    "suppliers": [
                        {
                            "id": {
                                "name": "Державне управління справами",
                                "scheme": "https://ns.openprocurement.org/ua/edrpou",
                                "uid": "00037256",
                                "uri": "http://www.dus.gov.ua/"
                            },
                            "address": {
                                "countryName": "Україна",
                                "postalCode": "01220",
                                "region": "м. Київ",
                                "locality": "м. Київ",
                                "streetAddress": "вул. Банкова, 11, корпус 1"
                            }
                        }
                    ],
                    "value": {
                        "amount": 600,
                        "currency": "UAH",
                        "valueAddedTaxIncluded": true
                    }
                }
            }

        """
        tender = self.request.validated['tender']
        if tender.status not in ['active.qualification', 'active.awarded']:
            self.request.errors.add(
                'body', 'data',
                'Can\'t update award in current ({}) tender status'.format(
                    tender.status))
            self.request.errors.status = 403
            return
        award = self.request.context
        if any(
            [i.status != 'active' for i in tender.lots
             if i.id == award.lotID]):
            self.request.errors.add(
                'body', 'data', 'Can update award only in active lot status')
            self.request.errors.status = 403
            return
        award_status = award.status
        apply_patch(self.request,
                    save=False,
                    src=self.request.context.serialize())
        if award_status == 'pending' and award.status == 'active':
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            tender.contracts.append(
                type(tender).contracts.model_class({'awardID': award.id}))
            add_next_award(self.request)
        elif award_status == 'active' and award.status == 'cancelled':
            award.complaintPeriod.endDate = get_now()
            for i in tender.contracts:
                if i.awardID == award.id:
                    i.status = 'cancelled'
            add_next_award(self.request)
        elif award_status == 'pending' and award.status == 'unsuccessful':
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            add_next_award(self.request)
        elif award_status == 'unsuccessful' and award.status == 'cancelled' and any(
            [
                i.status in ['claim', 'answered', 'pending', 'resolved']
                for i in award.complaints
            ]):
            if tender.status == 'active.awarded':
                tender.status = 'active.qualification'
                tender.awardPeriod.endDate = None
            now = get_now()
            award.complaintPeriod.endDate = now
            cancelled_awards = []
            for i in tender.awards[tender.awards.index(award):]:
                if i.lotID != award.lotID:
                    continue
                i.complaintPeriod.endDate = now
                i.status = 'cancelled'
                for j in i.complaints:
                    if j.status not in ['invalid', 'resolved', 'declined']:
                        j.status = 'cancelled'
                        j.cancellationReason = 'cancelled'
                        j.dateCanceled = now
                cancelled_awards.append(i.id)
            for i in tender.contracts:
                if i.awardID in cancelled_awards:
                    i.status = 'cancelled'
            add_next_award(self.request)
        else:
            self.request.errors.add(
                'body', 'data',
                'Can\'t update award in current ({}) status'.format(
                    award_status))
            self.request.errors.status = 403
            return
        if save_tender(self.request):
            LOGGER.info(
                'Updated tender award {}'.format(self.request.context.id),
                extra=context_unpack(self.request,
                                     {'MESSAGE_ID': 'tender_award_patch'}))
            return {'data': award.serialize("view")}
Exemple #9
0
    def collection_post(self):
        """Report auction results.

        Report auction results
        ----------------------

        Example request to report auction results:

        .. sourcecode:: http

            POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/auction HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "dateModified": "2014-10-27T08:06:58.158Z",
                    "bids": [
                        {
                            "value": {
                                "amount": 400,
                                "currency": "UAH"
                            }
                        },
                        {
                            "value": {
                                "amount": 385,
                                "currency": "UAH"
                            }
                        }
                    ]
                }
            }

        This is what one should expect in response:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: application/json

            {
                "data": {
                    "dateModified": "2014-10-27T08:06:58.158Z",
                    "bids": [
                        {
                            "value": {
                                "amount": 400,
                                "currency": "UAH",
                                "valueAddedTaxIncluded": true
                            }
                        },
                        {
                            "value": {
                                "amount": 385,
                                "currency": "UAH",
                                "valueAddedTaxIncluded": true
                            }
                        }
                    ],
                    "minimalStep":{
                        "amount": 35,
                        "currency": "UAH"
                    },
                    "tenderPeriod":{
                        "startDate": "2014-11-04T08:00:00"
                    }
                }
            }

        """
        apply_patch(self.request, save=False, src=self.request.validated['tender_src'])
        if all([i.auctionPeriod and i.auctionPeriod.endDate for i in self.request.validated['tender'].lots if i.numberOfBids > 1]):
            add_next_award(self.request)
        if save_tender(self.request):
            LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_auction_post'}))
            return {'data': self.request.validated['tender'].serialize(self.request.validated['tender'].status)}
 def patch(self):
     """Post a complaint resolution for award
     """
     tender = self.request.validated['tender']
     if tender.status not in ['active.qualification', 'active.awarded']:
         self.request.errors.add(
             'body', 'data',
             'Can\'t update complaint in current ({}) tender status'.format(
                 tender.status))
         self.request.errors.status = 403
         return
     complaint = self.request.context
     if complaint.status != 'pending':
         self.request.errors.add(
             'body', 'data',
             'Can\'t update complaint in current ({}) status'.format(
                 complaint.status))
         self.request.errors.status = 403
         return
     apply_patch(self.request, save=False, src=complaint.serialize())
     if complaint.status == 'cancelled':
         self.request.errors.add('body', 'data', 'Can\'t cancel complaint')
         self.request.errors.status = 403
         return
     if complaint.status == 'resolved':
         award = self.request.validated['award']
         if tender.status == 'active.awarded':
             tender.status = 'active.qualification'
             tender.awardPeriod.endDate = None
         if award.status == 'unsuccessful':
             for i in tender.awards[tender.awards.index(award):]:
                 i.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
                 i.status = 'cancelled'
                 for j in i.complaints:
                     if j.status == 'pending':
                         j.status = 'cancelled'
         for i in award.contracts:
             i.status = 'cancelled'
         award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
         award.status = 'cancelled'
         add_next_award(self.request)
     elif complaint.status in ['declined', 'invalid'
                               ] and tender.status == 'active.awarded':
         pending_complaints = [
             i for i in tender.complaints if i.status == 'pending'
         ]
         pending_awards_complaints = [
             i for a in tender.awards for i in a.complaints
             if i.status == 'pending'
         ]
         stand_still_ends = [
             a.complaintPeriod.endDate for a in tender.awards
             if a.complaintPeriod.endDate
         ]
         stand_still_end = max(
             stand_still_ends) if stand_still_ends else get_now()
         stand_still_time_expired = stand_still_end < get_now()
         if not pending_complaints and not pending_awards_complaints and stand_still_time_expired:
             active_awards = [
                 a for a in tender.awards if a.status == 'active'
             ]
             if not active_awards:
                 tender.status = 'unsuccessful'
     if save_tender(self.request):
         LOGGER.info('Updated tender award complaint {}'.format(
             self.request.context.id),
                     extra={'MESSAGE_ID': 'tender_award_complaint_patch'})
         return {'data': complaint.serialize("view")}
Exemple #11
0
def post_auction(request):
    """Report auction results.

    Report auction results
    ----------------------

    Example request to report auction results:

    .. sourcecode:: http

        POST /tenders/4879d3f8ee2443169b5fbbc9f89fa607/auction HTTP/1.1
        Host: example.com
        Accept: application/json

        {
            "data": {
                "dateModified": "2014-10-27T08:06:58.158Z",
                "bids": [
                    {
                        "value": {
                            "amount": 400,
                            "currency": "UAH"
                        }
                    },
                    {
                        "value": {
                            "amount": 385,
                            "currency": "UAH"
                        }
                    }
                ]
            }
        }

    This is what one should expect in response:

    .. sourcecode:: http

        HTTP/1.1 200 OK
        Content-Type: application/json

        {
            "data": {
                "dateModified": "2014-10-27T08:06:58.158Z",
                "bids": [
                    {
                        "value": {
                            "amount": 400,
                            "currency": "UAH",
                            "valueAddedTaxIncluded": true
                        }
                    },
                    {
                        "value": {
                            "amount": 385,
                            "currency": "UAH",
                            "valueAddedTaxIncluded": true
                        }
                    }
                ],
                "minimalStep":{
                    "amount": 35,
                    "currency": "UAH"
                },
                "tenderPeriod":{
                    "startDate": "2014-11-04T08:00:00"
                }
            }
        }

    """
    apply_patch(request, save=False, src=request.validated['tender_src'])
    add_next_award(request)
    if save_tender(request):
        LOGGER.info('Report auction results',
                    extra={'MESSAGE_ID': 'tender_auction_post'})
        return {'data': request.context.serialize(request.context.status)}
    def patch(self):
        """Tender Edit (partial)

        For example here is how procuring entity can change number of items to be procured and total Value of a tender:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607 HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "tenderID": "UA-64e93250be76435397e8c992ed4214d1",
                    "dateModified": "2014-10-27T08:12:34.956Z",
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        """
        tender = self.request.validated['tender']
        if tender.status in ['complete', 'unsuccessful', 'cancelled']:
            self.request.errors.add(
                'body', 'data',
                'Can\'t update tender in current ({}) status'.format(
                    tender.status))
            self.request.errors.status = 403
            return
        data = self.request.validated['data']
        if self.request.authenticated_role == 'tender_owner' and 'status' in data and data[
                'status'] not in ['cancelled', tender.status]:
            self.request.errors.add('body', 'data',
                                    'Can\'t update tender status')
            self.request.errors.status = 403
            return
        if self.request.authenticated_role == 'chronograph' and tender.status == 'active.tendering' and data.get(
                'status', tender.status
        ) == 'active.qualification' and tender.numberOfBids == 1:
            apply_patch(self.request,
                        save=False,
                        src=self.request.validated['tender_src'])
            add_next_award(self.request)
            save_tender(self.request)
        else:
            apply_patch(self.request, src=self.request.validated['tender_src'])
        LOGGER.info('Updated tender {}'.format(tender.id),
                    extra={'MESSAGE_ID': 'tender_patch'})
        return {'data': tender.serialize(tender.status)}
    def patch(self):
        """Tender Edit (partial)

        For example here is how procuring entity can change number of items to be procured and total Value of a tender:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607 HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "tenderID": "UA-64e93250be76435397e8c992ed4214d1",
                    "dateModified": "2014-10-27T08:12:34.956Z",
                    "value": {
                        "amount": 600
                    },
                    "itemsToBeProcured": [
                        {
                            "quantity": 6
                        }
                    ]
                }
            }

        """
        tender = self.request.validated['tender']
        if self.request.authenticated_role != 'Administrator' and tender.status in ['complete', 'unsuccessful', 'cancelled']:
            self.request.errors.add('body', 'data', 'Can\'t update tender in current ({}) status'.format(tender.status))
            self.request.errors.status = 403
            return
        data = self.request.validated['data']
        if self.request.authenticated_role == 'tender_owner' and 'status' in data and data['status'] not in ['cancelled', tender.status]:
            self.request.errors.add('body', 'data', 'Can\'t update tender status')
            self.request.errors.status = 403
            return
        if self.request.authenticated_role == 'chronograph' and tender.status == 'active.tendering' and data.get('status', tender.status) == 'active.qualification' and tender.numberOfBids == 1:
            apply_patch(self.request, save=False, src=self.request.validated['tender_src'])
            add_next_award(self.request)
            save_tender(self.request)
        else:
            apply_patch(self.request, src=self.request.validated['tender_src'])
        LOGGER.info('Updated tender {}'.format(tender.id), extra={'MESSAGE_ID': 'tender_patch'})
        return {'data': tender.serialize(tender.status)}
    def patch(self):
        """Update of award

        Example request to change the award:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    }
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "date": "2014-10-28T11:44:17.947Z",
                    "status": "active",
                    "suppliers": [
                        {
                            "id": {
                                "name": "Державне управління справами",
                                "scheme": "https://ns.openprocurement.org/ua/edrpou",
                                "uid": "00037256",
                                "uri": "http://www.dus.gov.ua/"
                            },
                            "address": {
                                "countryName": "Україна",
                                "postalCode": "01220",
                                "region": "м. Київ",
                                "locality": "м. Київ",
                                "streetAddress": "вул. Банкова, 11, корпус 1"
                            }
                        }
                    ],
                    "value": {
                        "amount": 600,
                        "currency": "UAH",
                        "valueAddedTaxIncluded": true
                    }
                }
            }

        """
        tender = self.request.validated["tender"]
        if tender.status not in ["active.qualification", "active.awarded"]:
            self.request.errors.add(
                "body", "data", "Can't update award in current ({}) tender status".format(tender.status)
            )
            self.request.errors.status = 403
            return
        award = self.request.context
        award_status = award.status
        apply_patch(self.request, save=False, src=self.request.context.serialize())
        if award_status == "pending" and award.status == "active":
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            award.contracts.append(Contract({"awardID": award.id}))
            tender.awardPeriod.endDate = get_now()
            tender.status = "active.awarded"
        elif award_status == "active" and award.status == "cancelled":
            award.complaintPeriod.endDate = get_now()
            for i in award.contracts:
                i.status = "cancelled"
            add_next_award(self.request)
            tender.status = "active.qualification"
            tender.awardPeriod.endDate = None
        elif award_status == "pending" and award.status == "unsuccessful":
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            add_next_award(self.request)
        else:
            self.request.errors.add("body", "data", "Can't update award in current ({}) status".format(award_status))
            self.request.errors.status = 403
            return
        if save_tender(self.request):
            LOGGER.info(
                "Updated tender award {}".format(self.request.context.id), extra={"MESSAGE_ID": "tender_award_patch"}
            )
            return {"data": award.serialize("view")}
    def patch(self):
        """Update of award

        Example request to change the award:

        .. sourcecode:: http

            PATCH /tenders/4879d3f8ee2443169b5fbbc9f89fa607/awards/71b6c23ed8944d688e92a31ec8c3f61a HTTP/1.1
            Host: example.com
            Accept: application/json

            {
                "data": {
                    "value": {
                        "amount": 600
                    }
                }
            }

        And here is the response to be expected:

        .. sourcecode:: http

            HTTP/1.0 200 OK
            Content-Type: application/json

            {
                "data": {
                    "id": "4879d3f8ee2443169b5fbbc9f89fa607",
                    "date": "2014-10-28T11:44:17.947Z",
                    "status": "active",
                    "suppliers": [
                        {
                            "id": {
                                "name": "Державне управління справами",
                                "scheme": "https://ns.openprocurement.org/ua/edrpou",
                                "uid": "00037256",
                                "uri": "http://www.dus.gov.ua/"
                            },
                            "address": {
                                "countryName": "Україна",
                                "postalCode": "01220",
                                "region": "м. Київ",
                                "locality": "м. Київ",
                                "streetAddress": "вул. Банкова, 11, корпус 1"
                            }
                        }
                    ],
                    "value": {
                        "amount": 600,
                        "currency": "UAH",
                        "valueAddedTaxIncluded": true
                    }
                }
            }

        """
        tender = self.request.validated['tender']
        if tender.status not in ['active.qualification', 'active.awarded']:
            self.request.errors.add('body', 'data', 'Can\'t update award in current ({}) tender status'.format(tender.status))
            self.request.errors.status = 403
            return
        award = self.request.context
        if any([i.status != 'active' for i in tender.lots if i.id == award.lotID]):
            self.request.errors.add('body', 'data', 'Can update award only in active lot status')
            self.request.errors.status = 403
            return
        award_status = award.status
        apply_patch(self.request, save=False, src=self.request.context.serialize())
        if award_status == 'pending' and award.status == 'active':
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            tender.contracts.append(Contract({'awardID': award.id}))
            add_next_award(self.request)
        elif award_status == 'active' and award.status == 'cancelled':
            award.complaintPeriod.endDate = get_now()
            for i in tender.contracts:
                if i.awardID == award.id:
                    i.status = 'cancelled'
            add_next_award(self.request)
        elif award_status == 'pending' and award.status == 'unsuccessful':
            award.complaintPeriod.endDate = get_now() + STAND_STILL_TIME
            add_next_award(self.request)
        else:
            self.request.errors.add('body', 'data', 'Can\'t update award in current ({}) status'.format(award_status))
            self.request.errors.status = 403
            return
        if save_tender(self.request):
            LOGGER.info('Updated tender award {}'.format(self.request.context.id),
                        extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_award_patch'}, {'TENDER_REV': tender.rev}))
            return {'data': award.serialize("view")}