Skip to content

Commit

Permalink
Disable changing of enquiry period, support new awarding
Browse files Browse the repository at this point in the history
  • Loading branch information
ktarasz committed Jan 25, 2016
1 parent 7f2fe3e commit a0e7143
Show file tree
Hide file tree
Showing 5 changed files with 347 additions and 9 deletions.
11 changes: 3 additions & 8 deletions openprocurement/tender/openua/tests/tender.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,15 +829,10 @@ def test_patch_tender(self):
self.assertEqual(response.content_type, 'application/json')

response = self.app.patch_json('/tenders/{}'.format(
tender['id']), {'data': {'enquiryPeriod': {'endDate': new_dateModified2}}}, status=422)
self.assertEqual(response.status, '422 Unprocessable Entity')
tender['id']), {'data': {'enquiryPeriod': {'endDate': new_dateModified2}}}, status=403)
self.assertEqual(response.status, '403 Forbidden')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json['errors'], [{
"location": "body",
"name": "enquiryPeriod",
"description": [
"enquiryPeriod should be greater than 12 days"
]}])
self.assertEqual(response.json['errors'][0]["description"], "Can't change enquiryPeriod")

response = self.app.patch_json('/tenders/{}?acc_token={}'.format(tender['id'], owner_token), {'data': {'status': 'active.auction'}}, status=403)
self.assertEqual(response.status, '403 Forbidden')
Expand Down
92 changes: 92 additions & 0 deletions openprocurement/tender/openua/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
check_tender_status,
context_unpack,
)
from barbecue import chef

PKG = get_distribution(__package__)
LOGGER = getLogger(PKG.project_name)
Expand Down Expand Up @@ -122,3 +123,94 @@ def check_status(request):
lots_ends.append(standStillEnd)
if lots_ends:
return

from openprocurement.api.models import Period


def add_next_award(request):
tender = request.validated['tender']
now = get_now()
if not tender.awardPeriod:
tender.awardPeriod = Period({})
if not tender.awardPeriod.startDate:
tender.awardPeriod.startDate = now
if tender.lots:
statuses = set()
for lot in tender.lots:
if lot.status != 'active':
continue
lot_awards = [i for i in tender.awards if i.lotID == lot.id]
if lot_awards and lot_awards[-1].status in ['pending', 'active']:
statuses.add(lot_awards[-1].status if lot_awards else 'unsuccessful')
continue
lot_items = [i.id for i in tender.items if i.relatedLot == lot.id]
features = [
i
for i in (tender.features or [])
if i.featureOf == 'tenderer' or i.featureOf == 'lot' and i.relatedItem == lot.id or i.featureOf == 'item' and i.relatedItem in lot_items
]
codes = [i.code for i in features]
bids = [
{
'id': bid.id,
'value': [i for i in bid.lotValues if lot.id == i.relatedLot][0].value,
'tenderers': bid.tenderers,
'parameters': [i for i in bid.parameters if i.code in codes],
'date': [i for i in bid.lotValues if lot.id == i.relatedLot][0].date
}
for bid in tender.bids
if lot.id in [i.relatedLot for i in bid.lotValues] and bid.status == "active"
]
if not bids:
lot.status = 'unsuccessful'
statuses.add('unsuccessful')
continue
unsuccessful_awards = [i.bid_id for i in lot_awards if i.status == 'unsuccessful']
bids = chef(bids, features, unsuccessful_awards)
if bids:
bid = bids[0]
award = tender.__class__.awards.model_class({
'bid_id': bid['id'],
'lotID': lot.id,
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
'complaintPeriod': {
'startDate': now.isoformat()
}
})
tender.awards.append(award)
request.response.headers['Location'] = request.route_url('Tender Awards', tender_id=tender.id, award_id=award['id'])
statuses.add('pending')
else:
statuses.add('unsuccessful')
if statuses.difference(set(['unsuccessful', 'active'])):
tender.awardPeriod.endDate = None
tender.status = 'active.qualification'
else:
tender.awardPeriod.endDate = now
tender.status = 'active.awarded'
else:
if not tender.awards or tender.awards[-1].status not in ['pending', 'active']:
unsuccessful_awards = [i.bid_id for i in tender.awards if i.status == 'unsuccessful']
active_bids = [bid for bid in tender.bids if bid.status == "active"]
bids = chef(active_bids, tender.features or [], unsuccessful_awards)
if bids:
bid = bids[0].serialize()
award = tender.__class__.awards.model_class({
'bid_id': bid['id'],
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
'complaintPeriod': {
'startDate': get_now().isoformat()
}
})
tender.awards.append(award)
request.response.headers['Location'] = request.route_url('Tender Awards', tender_id=tender.id, award_id=award['id'])
if tender.awards[-1].status == 'pending':
tender.awardPeriod.endDate = None
tender.status = 'active.qualification'
else:
tender.awardPeriod.endDate = now
tender.status = 'active.awarded'
4 changes: 4 additions & 0 deletions openprocurement/tender/openua/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def validate_patch_tender_ua_data(request):
request.errors.add('body', 'item', 'Can\'t change classification')
request.errors.status = 403
return None
if 'enquiryPeriod' in data:
request.errors.add('body', 'item', 'Can\'t change enquiryPeriod')
request.errors.status = 403
return None
if 'tenderPeriod' in data:
data["auctionPeriod"] = {'startDate': None}
if len(request.context.lots) > 0:
Expand Down
123 changes: 123 additions & 0 deletions openprocurement/tender/openua/views/auction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from openprocurement.api.utils import (
save_tender,
apply_patch,
opresource,
json_view,
context_unpack,
)
from openprocurement.api.validation import (
validate_tender_auction_data,
)
from openprocurement.api.views.auction import TenderAuctionResource

from openprocurement.tender.openua.utils import add_next_award

LOGGER = getLogger(__name__)


@opresource(name='Tender UA Auction',
collection_path='/tenders/{tender_id}/auction',
path='/tenders/{tender_id}/auction/{auction_lot_id}',
procurementMethodType='aboveThresholdUA',
description="Tender UA auction data")
class TenderUaAuctionResource(TenderAuctionResource):

@json_view(content_type="application/json", permission='auction', validators=(validate_tender_auction_data))
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)}

@json_view(content_type="application/json", permission='auction', validators=(validate_tender_auction_data))
def patch(self):
"""Set urls for access to auction for lot.
"""
if apply_patch(self.request, src=self.request.validated['tender_src']):
LOGGER.info('Updated auction urls', extra=context_unpack(self.request, {'MESSAGE_ID': 'tender_lot_auction_patch'}))
return {'data': self.request.validated['tender'].serialize("auction_view")}

@json_view(content_type="application/json", permission='auction', validators=(validate_tender_auction_data))
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)}
126 changes: 125 additions & 1 deletion openprocurement/tender/openua/views/award.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
from logging import getLogger
from openprocurement.api.utils import opresource
from openprocurement.api.views.award import TenderAwardResource
from openprocurement.api.models import Contract, STAND_STILL_TIME, get_now
from openprocurement.api.utils import (
apply_patch,
save_tender,
json_view,
context_unpack,
)
from openprocurement.api.validation import (
validate_patch_award_data,
)
from openprocurement.tender.openua.utils import add_next_award

LOGGER = getLogger(__name__)

Expand All @@ -12,4 +23,117 @@
description="Tender awards",
procurementMethodType='aboveThresholdUA')
class TenderUaAwardResource(TenderAwardResource):
pass

@json_view(content_type="application/json", permission='edit_tender', validators=(validate_patch_award_data,))
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)
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 != ['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'}, {'TENDER_REV': tender.rev}))
return {'data': award.serialize("view")}

0 comments on commit a0e7143

Please sign in to comment.