Skip to content

Commit

Permalink
Added switch to qualification if one bid
Browse files Browse the repository at this point in the history
  • Loading branch information
kroman0 committed Jan 12, 2015
1 parent b723856 commit 3f23c8e
Show file tree
Hide file tree
Showing 12 changed files with 61 additions and 97 deletions.
1 change: 0 additions & 1 deletion src/openprocurement/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import os
import pkg_resources
from logging import getLogger
from urlparse import urlparse
from pyramid.config import Configurator
from openprocurement.api.auth import AuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy as AuthorizationPolicy
Expand Down
2 changes: 2 additions & 0 deletions src/openprocurement/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -560,3 +560,5 @@ def validate_auctionPeriod(self, data, period):
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")
2 changes: 1 addition & 1 deletion src/openprocurement/api/tests/award.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ def test_create_tender_award(self):
response = self.app.get('/tenders/{}/awards'.format(self.tender_id))
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(response.json['data'][0], award)
self.assertEqual(response.json['data'][-1], award)

response = self.app.patch_json('/tenders/{}/awards/{}'.format(self.tender_id, award['id']), {"data": {"status": "active"}})
self.assertEqual(response.status, '200 OK')
Expand Down
2 changes: 1 addition & 1 deletion src/openprocurement/api/tests/bidder.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@


class TenderBidderResourceTest(BaseTenderWebTest):
initial_status = 'active.tendering'
initial_status = 'active.tendering'

def test_create_tender_bidder_invalid(self):
response = self.app.post_json('/tenders/some_id/bids', {
Expand Down
61 changes: 18 additions & 43 deletions src/openprocurement/api/tests/tender.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import unittest

from openprocurement.api.models import Tender, get_now
from openprocurement.api.tests.base import test_tender_data, BaseWebTest
from openprocurement.api.tests.base import test_tender_data, BaseWebTest, BaseTenderWebTest


class TenderTest(BaseWebTest):
Expand Down Expand Up @@ -543,7 +543,9 @@ def test_tender_not_found(self):
])


class TenderProcessTest(BaseWebTest):
class TenderProcessTest(BaseTenderWebTest):
setUp = BaseWebTest.setUp

def test_invalid_tender_conditions(self):
self.app.authorization = ('Basic', ('broker', ''))
# empty tenders listing
Expand All @@ -552,7 +554,7 @@ def test_invalid_tender_conditions(self):
# create tender
response = self.app.post_json('/tenders',
{"data": test_tender_data})
tender_id = response.json['data']['id']
tender_id = self.tender_id = response.json['data']['id']
owner_token = response.json['access']['token']
# create compaint
self.app.authorization = ('Basic', ('broker', ''))
Expand All @@ -563,9 +565,7 @@ def test_invalid_tender_conditions(self):
response = self.app.post_json('/tenders/{}/complaints'.format(tender_id),
{'data': {'title': 'invalid conditions', 'description': 'description', 'author': test_tender_data["procuringEntity"]}})
# switch to active.tendering
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.tendering'}})
self.set_status('active.tendering')
# satisfying tender conditions complaint
# XXX correct auth
self.app.authorization = ('Basic', ('broker', ''))
Expand All @@ -583,27 +583,16 @@ def test_one_valid_bid_tender(self):
# create tender
response = self.app.post_json('/tenders',
{"data": test_tender_data})
tender_id = response.json['data']['id']
tender_id = self.tender_id = response.json['data']['id']
owner_token = response.json['access']['token']
# switch to active.tendering
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.tendering'}})
self.set_status('active.tendering')
# create bid
self.app.authorization = ('Basic', ('broker', ''))
response = self.app.post_json('/tenders/{}/bids'.format(tender_id),
{'data': {'tenderers': [test_tender_data["procuringEntity"]], "value": {"amount": 500}}})
# switch to active.auction
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.auction'}})
# get auction info
self.app.authorization = ('Basic', ('auction', ''))
response = self.app.get('/tenders/{}/auction'.format(tender_id))
auction_bids_data = response.json['data']['bids']
# posting auction results
response = self.app.post_json('/tenders/{}/auction'.format(tender_id),
{'data': {'bids': auction_bids_data}})
# switch to active.qualification
self.set_status('active.qualification')
# get awards
self.app.authorization = ('Basic', ('broker', ''))
response = self.app.get('/tenders/{}/awards?acc_token={}'.format(tender_id, owner_token))
Expand All @@ -629,27 +618,16 @@ def test_one_invalid_bid_tender(self):
# create tender
response = self.app.post_json('/tenders',
{"data": test_tender_data})
tender_id = response.json['data']['id']
tender_id = self.tender_id = response.json['data']['id']
owner_token = response.json['access']['token']
# switch to active.tendering
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.tendering'}})
self.set_status('active.tendering')
# create bid
self.app.authorization = ('Basic', ('broker', ''))
response = self.app.post_json('/tenders/{}/bids'.format(tender_id),
{'data': {'tenderers': [test_tender_data["procuringEntity"]], "value": {"amount": 500}}})
# switch to active.auction
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.auction'}})
# get auction info
self.app.authorization = ('Basic', ('auction', ''))
response = self.app.get('/tenders/{}/auction'.format(tender_id))
auction_bids_data = response.json['data']['bids']
# posting auction results
response = self.app.post_json('/tenders/{}/auction'.format(tender_id),
{'data': {'bids': auction_bids_data}})
# switch to active.qualification
self.set_status('active.qualification')
# get awards
self.app.authorization = ('Basic', ('broker', ''))
response = self.app.get('/tenders/{}/awards?acc_token={}'.format(tender_id, owner_token))
Expand All @@ -675,12 +653,10 @@ def test_first_bid_tender(self):
# create tender
response = self.app.post_json('/tenders',
{"data": test_tender_data})
tender_id = response.json['data']['id']
tender_id = self.tender_id = response.json['data']['id']
owner_token = response.json['access']['token']
# switch to active.tendering
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.tendering'}})
self.set_status('active.tendering')
# create bid
self.app.authorization = ('Basic', ('broker', ''))
response = self.app.post_json('/tenders/{}/bids'.format(tender_id),
Expand All @@ -692,9 +668,8 @@ def test_first_bid_tender(self):
response = self.app.post_json('/tenders/{}/bids'.format(tender_id),
{'data': {'tenderers': [test_tender_data["procuringEntity"]], "value": {"amount": 475}}})
# switch to active.auction
self.app.authorization = ('Basic', ('chronograph', ''))
response = self.app.patch_json('/tenders/{}'.format(tender_id),
{'data': {'status': 'active.auction'}})
self.set_status('active.auction')

# get auction info
self.app.authorization = ('Basic', ('auction', ''))
response = self.app.get('/tenders/{}/auction'.format(tender_id))
Expand Down
22 changes: 21 additions & 1 deletion src/openprocurement/api/utils.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from base64 import b64encode
from jsonpatch import make_patch, apply_patch as _apply_patch
from openprocurement.api.models import Document, Revision, get_now
from openprocurement.api.models import Document, Revision, Award, get_now
from urllib import quote
from uuid import uuid4
from schematics.exceptions import ModelValidationError
Expand Down Expand Up @@ -164,3 +164,23 @@ def apply_patch(request, data=None, save=True, src=None):
request.context.import_data(patch)
if save:
save_tender(request)


def add_next_award(request):
tender = request.validated['tender']
unsuccessful_awards = [i.bid_id for i in tender.awards if i.status == 'unsuccessful']
bids = [i for i in sorted(tender.bids, key=lambda i: (i.value.amount, i.date)) if i.id not in unsuccessful_awards]
if bids:
bid = bids[0].serialize()
award_data = {
'bid_id': bid['id'],
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
}
award = Award(award_data)
tender.awards.append(award)
request.response.headers['Location'] = request.route_url('Tender Awards', tender_id=tender.id, award_id=award['id'])
else:
tender.awardPeriod.endDate = get_now()
tender.status = 'active.awarded'
4 changes: 2 additions & 2 deletions src/openprocurement/api/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ def validate_data(request, model, partial=False):
m = model(request.context.serialize())
m.import_data(new_patch)
m.validate()
if request.authenticated_userid == 'chronograph':
if request.authenticated_role == 'chronograph':
data = m.serialize('chronograph')
elif request.authenticated_userid == 'auction':
elif request.authenticated_role == 'auction':
data = m.serialize('auction_{}'.format(request.method.lower()))
elif isinstance(request.context, Tender):
data = m.serialize('edit_{}'.format(request.context.status))
Expand Down
16 changes: 3 additions & 13 deletions src/openprocurement/api/views/auction.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from cornice.service import Service
from openprocurement.api.models import Award
from openprocurement.api.utils import (
save_tender,
apply_patch,
add_next_award,
)
from openprocurement.api.validation import (
validate_tender_auction_data,
Expand Down Expand Up @@ -159,17 +159,7 @@ def post_auction(request):
"""
apply_patch(request, save=False, src=request.validated['tender_src'])
tender = request.validated['tender']
bids = sorted(tender.bids, key=lambda i: (i.value.amount, i.date))
bid = bids[0].serialize()
award_data = {
'bid_id': bid['id'],
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
}
award = Award(award_data)
tender.awards.append(award)
add_next_award(request)
save_tender(request)
LOGGER.info('Report auction results')
return {'data': tender.serialize(tender.status)}
return {'data': request.context.serialize(request.context.status)}
18 changes: 2 additions & 16 deletions src/openprocurement/api/views/award.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from openprocurement.api.utils import (
apply_data_patch,
save_tender,
add_next_award,
)
from openprocurement.api.validation import (
validate_award_data,
Expand Down Expand Up @@ -303,22 +304,7 @@ def patch(self):
tender.awardPeriod.endDate = get_now()
tender.status = 'active.awarded'
elif award.status == 'unsuccessful':
unsuccessful_awards = [i.bid_id for i in tender.awards if i.status == 'unsuccessful']
bids = [i for i in sorted(tender.bids, key=lambda i: (i.value.amount, i.date)) if i.id not in unsuccessful_awards]
if bids:
bid = bids[0].serialize()
award_data = {
'bid_id': bid['id'],
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
}
award = Award(award_data)
tender.awards.append(award)
self.request.response.headers['Location'] = self.request.route_url('Tender Awards', tender_id=tender.id, award_id=award['id'])
else:
tender.awardPeriod.endDate = get_now()
tender.status = 'active.awarded'
add_next_award(self.request)
save_tender(self.request)
LOGGER.info('Updated tender award {}'.format(self.request.context.id))
return {'data': award.serialize("view")}
19 changes: 3 additions & 16 deletions src/openprocurement/api/views/award_complaint.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
from logging import getLogger
from cornice.resource import resource, view
from openprocurement.api.models import Award, Complaint, STAND_STILL_TIME, get_now
from openprocurement.api.models import Complaint, STAND_STILL_TIME, get_now
from openprocurement.api.utils import (
apply_data_patch,
save_tender,
add_next_award,
)
from openprocurement.api.validation import (
validate_complaint_data,
Expand Down Expand Up @@ -90,21 +91,7 @@ def patch(self):
for i in award.contracts:
i.status = 'cancelled'
award.status = 'cancelled'
unsuccessful_awards = [i.bid_id for i in tender.awards if i.status == 'unsuccessful']
bids = [i for i in sorted(tender.bids, key=lambda i: (i.value.amount, i.date)) if i.id not in unsuccessful_awards]
if bids:
bid = bids[0].serialize()
award_data = {
'bid_id': bid['id'],
'status': 'pending',
'value': bid['value'],
'suppliers': bid['tenderers'],
}
award = Award(award_data)
tender.awards.append(award)
else:
tender.awardPeriod.endDate = get_now()
tender.status = 'active.awarded'
add_next_award(self.request)
elif complaint.status in ['declined', 'invalid'] and tender.status == 'active.awarded':
pending_complaints = [
i
Expand Down
1 change: 0 additions & 1 deletion src/openprocurement/api/views/question.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
LOGGER = getLogger(__name__)



@resource(name='Tender Questions',
collection_path='/tenders/{tender_id}/questions',
path='/tenders/{tender_id}/questions/{question_id}',
Expand Down
10 changes: 8 additions & 2 deletions src/openprocurement/api/views/tender.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
set_ownership,
tender_serialize,
apply_patch,
add_next_award,
)
from openprocurement.api.validation import (
validate_patch_tender_data,
Expand Down Expand Up @@ -363,7 +364,7 @@ def get(self):
"""
tender = self.request.validated['tender']
tender_data = tender.serialize('view' if self.request.authenticated_userid == 'chronograph' else tender.status)
tender_data = tender.serialize('view' if self.request.authenticated_role == 'chronograph' else tender.status)
return {'data': tender_data}

#@view(content_type="application/json", validators=(validate_tender_data, ), permission='edit_tender', renderer='json')
Expand Down Expand Up @@ -436,6 +437,11 @@ def patch(self):
self.request.errors.add('body', 'data', 'Can\'t update tender status')
self.request.errors.status = 403
return
apply_patch(self.request, src=self.request.validated['tender_src'])
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))
return {'data': tender.serialize(tender.status)}

0 comments on commit 3f23c8e

Please sign in to comment.