Skip to content

Commit

Permalink
A495973709603869 bids to awards (#59)
Browse files Browse the repository at this point in the history
* Prepare openprocurement.auctions.dgf 1.1.0-sale.

* add bids invalidation and number_of_bids_to_qualify

* invalidate bids in auction view

* update tests with new awarding

* NUMBER_OF_BIDS_TO_BE_QUALIFIED as threshold

* remove extra functions

* fix typo in tests

* Update bidder.py

* Update bidder.py
  • Loading branch information
yarsanich committed Feb 6, 2018
1 parent a56f95b commit 1a393f7
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 27 deletions.
4 changes: 3 additions & 1 deletion openprocurement/auctions/dgf/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ def read_json(name):
CPVS_CODES = read_json('cpvs.json')
ORA_CODES[0:0] = ["UA-IPN", "UA-FIN"]

NUMBER_OF_BIDS_TO_BE_QUALIFIED = 2

#code units
CPV_NON_SPECIFIC_LOCATION_UNITS = ('71', '72', '73', '75', '76', '77', '79', '80', '85', '90', '92', '98')
CAV_NON_SPECIFIC_LOCATION_UNITS = ('07', '08')
CAV_NON_SPECIFIC_LOCATION_UNITS = ('07', '08')
21 changes: 7 additions & 14 deletions openprocurement/auctions/dgf/tests/award.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def test_patch_auction_award(self):
response = self.app.get(request_path)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)

response = self.app.patch_json('/auctions/{}/awards/some_id'.format(self.auction_id), {"data": {"status": "unsuccessful"}}, status=404)
self.assertEqual(response.status, '404 Not Found')
Expand Down Expand Up @@ -299,7 +299,7 @@ def test_patch_auction_award(self):
response = self.app.get(request_path)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)
self.assertIn(response.json['data'][1]['id'], new_award_location)

response = self.app.patch_json('/auctions/{}/awards/{}?acc_token={}'.format(self.auction_id, self.second_award_id, self.auction_token),
Expand Down Expand Up @@ -333,7 +333,7 @@ def test_patch_auction_award_admin(self):
response = self.app.get(request_path)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)
authorization = self.app.authorization

self.app.authorization = ('Basic', ('administrator', ''))
Expand Down Expand Up @@ -463,7 +463,7 @@ def test_successful_second_auction_award(self):
response = self.app.get(request_path)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)
self.assertIn(response.json['data'][1]['id'], new_award_location)
new_award = response.json['data'][-1]

Expand Down Expand Up @@ -497,7 +497,6 @@ def test_unsuccessful_auction1(self):

self.patch_award(self.first_award_id, "unsuccessful")

self.patch_award(self.third_award_id, "unsuccessful")

response = self.app.get('/auctions/{}'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
Expand All @@ -514,7 +513,6 @@ def test_unsuccessful_auction2(self):

self.patch_award(self.first_award_id, "unsuccessful")

self.patch_award(self.third_award_id, "unsuccessful")

response = self.app.get('/auctions/{}'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
Expand All @@ -533,7 +531,6 @@ def test_unsuccessful_auction3(self):

self.patch_award(self.first_award_id, "unsuccessful")

self.patch_award(self.third_award_id, "unsuccessful")

response = self.app.get('/auctions/{}'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
Expand All @@ -552,8 +549,6 @@ def test_unsuccessful_auction4(self):

self.patch_award(self.first_award_id, "unsuccessful")

self.patch_award(self.third_award_id, "unsuccessful")

response = self.app.get('/auctions/{}'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
Expand All @@ -571,8 +566,6 @@ def test_unsuccessful_auction5(self):

self.patch_award(self.first_award_id, "unsuccessful")

self.patch_award(self.third_award_id, "unsuccessful")

response = self.app.get('/auctions/{}'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
Expand All @@ -582,7 +575,7 @@ def test_get_auction_awards(self):
response = self.app.get('/auctions/{}/awards'.format(self.auction_id))
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)
fist_award = response.json['data'][0]
second_award = response.json['data'][1]

Expand Down Expand Up @@ -893,7 +886,7 @@ def test_patch_auction_award(self):
response = self.app.get(request_path)
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
self.assertEqual(len(response.json['data']), 3)
self.assertEqual(len(response.json['data']), 2)
new_award = response.json['data'][-1]
response = self.app.post_json('/auctions/{}/cancellations'.format(self.auction_id), {'data': {
'reason': 'cancellation reason',
Expand Down Expand Up @@ -1196,7 +1189,7 @@ def test_patch_auction_award_complaint(self):

def test_review_auction_award_complaint(self):
complaints = []
for i in range(3):
for i in range(2):
response = self.app.post_json('/auctions/{}/awards/{}/complaints'.format(self.auction_id, self.award_id), {'data': {
'title': 'complaint title',
'description': 'complaint description',
Expand Down
2 changes: 0 additions & 2 deletions openprocurement/auctions/dgf/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,8 @@ def post_auction_results(self):
self.assertEqual('active.qualification', auction["status"])
self.first_award = auction['awards'][0]
self.second_award = auction['awards'][1]
self.third_award = auction['awards'][2]
self.first_award_id = self.first_award['id']
self.second_award_id = self.second_award['id']
self.third_award_id = self.third_award['id']
self.app.authorization = authorization

def generate_docservice_url(self):
Expand Down
127 changes: 126 additions & 1 deletion openprocurement/auctions/dgf/tests/bidder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
import unittest
from copy import deepcopy

from openprocurement.auctions.dgf.tests.base import BaseAuctionWebTest, test_auction_data, test_features_auction_data, test_financial_organization, test_financial_auction_data, test_bids, test_financial_bids
from openprocurement.auctions.dgf.tests.base import (
BaseAuctionWebTest,
test_auction_data,
test_features_auction_data,
test_financial_organization,
test_financial_auction_data,
test_bids,
test_financial_bids,
test_organization
)


class AuctionBidderResourceTest(BaseAuctionWebTest):
Expand Down Expand Up @@ -406,6 +415,122 @@ def test_bid_Administrator_change(self):
self.assertEqual(response.json['data']["tenderers"][0]["identifier"]["id"], "00000000")


class AuctionBidInvalidationAuctionResourceTest(BaseAuctionWebTest):
initial_data = test_auction_data
initial_status = 'active.auction'
initial_bids = [
{
"tenderers": [
test_organization
],
"value": {
"amount": (initial_data['value']['amount'] + initial_data['minimalStep']['amount']/2),
"currency": "UAH",
"valueAddedTaxIncluded": True
},
'qualified': True
}
for i in range(3)
]

def test_post_auction_all_invalid_bids(self):
self.app.authorization = ('Basic', ('auction', ''))

response = self.app.post_json('/auctions/{}/auction'.format(self.auction_id),
{'data': {'bids': self.initial_bids}})
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
auction = response.json['data']

self.assertEqual(auction["bids"][0]['value']['amount'], self.initial_bids[0]['value']['amount'])
self.assertEqual(auction["bids"][1]['value']['amount'], self.initial_bids[1]['value']['amount'])
self.assertEqual(auction["bids"][2]['value']['amount'], self.initial_bids[2]['value']['amount'])

value_threshold = auction['value']['amount'] + auction['minimalStep']['amount']
self.assertLess(auction["bids"][0]['value']['amount'], value_threshold)
self.assertLess(auction["bids"][1]['value']['amount'], value_threshold)
self.assertLess(auction["bids"][2]['value']['amount'], value_threshold)
self.assertEqual(auction["bids"][0]['status'], 'invalid')
self.assertEqual(auction["bids"][1]['status'], 'invalid')
self.assertEqual(auction["bids"][2]['status'], 'invalid')
self.assertEqual('unsuccessful', auction["status"])

def test_post_auction_one_invalid_bid(self):
self.app.authorization = ('Basic', ('auction', ''))

bids = deepcopy(self.initial_bids)
bids[0]['value']['amount'] = bids[0]['value']['amount'] * 3
bids[1]['value']['amount'] = bids[1]['value']['amount'] * 2
response = self.app.post_json('/auctions/{}/auction'.format(self.auction_id), {'data': {'bids': bids}})
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
auction = response.json['data']

self.assertEqual(auction["bids"][0]['value']['amount'], bids[0]['value']['amount'])
self.assertEqual(auction["bids"][1]['value']['amount'], bids[1]['value']['amount'])
self.assertEqual(auction["bids"][2]['value']['amount'], bids[2]['value']['amount'])

value_threshold = auction['value']['amount'] + auction['minimalStep']['amount']

self.assertGreater(auction["bids"][0]['value']['amount'], value_threshold)
self.assertGreater(auction["bids"][1]['value']['amount'], value_threshold)
self.assertLess(auction["bids"][2]['value']['amount'], value_threshold)

self.assertEqual(auction["bids"][0]['status'], 'active')
self.assertEqual(auction["bids"][1]['status'], 'active')
self.assertEqual(auction["bids"][2]['status'], 'invalid')

self.assertEqual('active.qualification', auction["status"])

for i, status in enumerate(['pending.verification', 'pending.waiting']):
self.assertIn("tenderers", auction["bids"][i])
self.assertIn("name", auction["bids"][i]["tenderers"][0])
# self.assertIn(auction["awards"][0]["id"], response.headers['Location'])
self.assertEqual(auction["awards"][i]['bid_id'], bids[i]['id'])
self.assertEqual(auction["awards"][i]['value']['amount'], bids[i]['value']['amount'])
self.assertEqual(auction["awards"][i]['suppliers'], bids[i]['tenderers'])
self.assertEqual(auction["awards"][i]['status'], status)
if status == 'pending.verification':
self.assertIn("verificationPeriod", auction["awards"][i])

def test_post_auction_one_valid_bid(self):
self.app.authorization = ('Basic', ('auction', ''))

bids = deepcopy(self.initial_bids)
bids[0]['value']['amount'] = bids[0]['value']['amount'] * 2
response = self.app.post_json('/auctions/{}/auction'.format(self.auction_id), {'data': {'bids': bids}})
self.assertEqual(response.status, '200 OK')
self.assertEqual(response.content_type, 'application/json')
auction = response.json['data']

self.assertEqual(auction["bids"][0]['value']['amount'], bids[0]['value']['amount'])
self.assertEqual(auction["bids"][1]['value']['amount'], bids[1]['value']['amount'])
self.assertEqual(auction["bids"][2]['value']['amount'], bids[2]['value']['amount'])

value_threshold = auction['value']['amount'] + auction['minimalStep']['amount']

self.assertGreater(auction["bids"][0]['value']['amount'], value_threshold)
self.assertLess(auction["bids"][1]['value']['amount'], value_threshold)
self.assertLess(auction["bids"][2]['value']['amount'], value_threshold)

self.assertEqual(auction["bids"][0]['status'], 'active')
self.assertEqual(auction["bids"][1]['status'], 'invalid')
self.assertEqual(auction["bids"][2]['status'], 'invalid')

self.assertEqual('active.qualification', auction["status"])

for i, status in enumerate(['pending.verification', 'unsuccessful']):
self.assertIn("tenderers", auction["bids"][i])
self.assertIn("name", auction["bids"][i]["tenderers"][0])
# self.assertIn(auction["awards"][0]["id"], response.headers['Location'])
self.assertEqual(auction["awards"][i]['bid_id'], bids[i]['id'])
self.assertEqual(auction["awards"][i]['value']['amount'], bids[i]['value']['amount'])
self.assertEqual(auction["awards"][i]['suppliers'], bids[i]['tenderers'])
self.assertEqual(auction["awards"][i]['status'], status)
if status == 'pending.verification':
self.assertIn("verificationPeriod", auction["awards"][i])


class AuctionBidderProcessTest(BaseAuctionWebTest):
initial_data = test_auction_data
initial_bids = test_bids
Expand Down
40 changes: 34 additions & 6 deletions openprocurement/auctions/dgf/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
check_auction_status, remove_draft_bids
)

from .constants import DOCUMENT_TYPE_URL_ONLY, DOCUMENT_TYPE_OFFLINE
from .constants import (
DOCUMENT_TYPE_URL_ONLY,
DOCUMENT_TYPE_OFFLINE,
NUMBER_OF_BIDS_TO_BE_QUALIFIED
)


PKG = get_distribution(__package__)
LOGGER = getLogger(PKG.project_name)
Expand Down Expand Up @@ -70,15 +75,23 @@ def check_auction_status(request):


def create_awards(request):
"""
Function create NUMBER_OF_BIDS_TO_BE_QUALIFIED awards objects
First award always in pending.verification status
others in pending.waiting status
"""
auction = request.validated['auction']
auction.status = 'active.qualification'
now = get_now()
auction.awardPeriod = type(auction).awardPeriod({'startDate': now})

bids = chef(auction.bids, auction.features or [], [], True)
for i, bid in enumerate(bids):
bid = bid.serialize()
# minNumberOfQualifiedBids == 1
bids_to_qualify = NUMBER_OF_BIDS_TO_BE_QUALIFIED \
if (len(bids) > NUMBER_OF_BIDS_TO_BE_QUALIFIED) \
else len(bids)
for i in xrange(0, bids_to_qualify):
status = 'pending.verification' if i == 0 else 'pending.waiting'
bid = bids[i].serialize()
award = type(auction).awards.model_class({
'__parent__': request.context,
'bid_id': bid['id'],
Expand All @@ -88,9 +101,15 @@ def create_awards(request):
'suppliers': bid['tenderers'],
'complaintPeriod': {'startDate': now}
})
if bid['status'] == 'invalid':
award.status = 'unsuccessful'
award.complaintPeriod.endDate = now
if award.status == 'pending.verification':
award.verificationPeriod = award.paymentPeriod = award.signingPeriod = {'startDate': now}
request.response.headers['Location'] = request.route_url('{}:Auction Awards'.format(auction.procurementMethodType), auction_id=auction.id, award_id=award['id'])
request.response.headers['Location'] = request.route_url('{}:Auction Awards'.format(
auction.procurementMethodType),
auction_id=auction.id,
award_id=award['id'])
auction.awards.append(award)


Expand Down Expand Up @@ -179,6 +198,15 @@ def calculate_enddate(auction, period, duration):
period.endDate = calculate_business_date(period.endDate, round_to_18_hour_delta, auction, False)


def invalidate_bids_under_threshold(auction):
"""Invalidate bids that lower value.amount + minimalStep.amount"""
value_threshold = round(auction['value']['amount'] +
auction['minimalStep']['amount'], 2)
for bid in auction['bids']:
if bid['value']['amount'] < value_threshold:
bid['status'] = 'invalid'


def get_auction_creation_date(data):
auction_creation_date = (data.get('revisions')[0].date if data.get('revisions') else get_now())
return auction_creation_date
Expand All @@ -195,4 +223,4 @@ def remove_invalid_bids(request):
def invalidate_bids_data(request):
auction = request.validated['auction']
for bid in auction.bids:
setattr(bid, "status", "invalid")
setattr(bid, "status", "invalid")
17 changes: 14 additions & 3 deletions openprocurement/auctions/dgf/views/other/auction.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
apply_patch,
opresource,
)
from openprocurement.auctions.dgf.utils import create_awards
from openprocurement.auctions.dgf.utils import (
create_awards,
invalidate_bids_under_threshold
)
from openprocurement.auctions.core.validation import (
validate_auction_auction_data,
)
Expand Down Expand Up @@ -163,7 +166,11 @@ def collection_post(self):
"""
apply_patch(self.request, save=False, src=self.request.validated['auction_src'])
auction = self.request.validated['auction']
create_awards(self.request)
invalidate_bids_under_threshold(auction)
if any([i.status == 'active' for i in auction.bids]):
create_awards(self.request)
else:
auction.status = 'unsuccessful'
if save_auction(self.request):
self.LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'auction_auction_post'}))
return {'data': self.request.validated['auction'].serialize(self.request.validated['auction'].status)}
Expand All @@ -184,7 +191,11 @@ def post(self):
auction = self.request.validated['auction']
if all([i.auctionPeriod and i.auctionPeriod.endDate for i in auction.lots if i.numberOfBids > 1 and i.status == 'active']):
cleanup_bids_for_cancelled_lots(auction)
create_awards(self.request)
invalidate_bids_under_threshold(auction)
if any([i.status == 'active' for i in auction.bids]):
create_awards(self.request)
else:
auction.status = 'unsuccessful'
if save_auction(self.request):
self.LOGGER.info('Report auction results', extra=context_unpack(self.request, {'MESSAGE_ID': 'auction_lot_auction_post'}))
return {'data': self.request.validated['auction'].serialize(self.request.validated['auction'].status)}

0 comments on commit 1a393f7

Please sign in to comment.