Commit 2c2c9038 authored by Mike Hoang's avatar Mike Hoang
Browse files

fix(LSP priority transfer): changed logic of priority transfer for LSP

parent cd8cc512
......@@ -310,6 +310,11 @@ class AdditionalClassification(Classification):
class _Bid(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResources):
@property
def _bidder(self):
if bidders := self.bidders:
return bidders[0]
class Options:
roles = {
'public': (
......@@ -390,38 +395,6 @@ class Bid(_Bid):
raise DataError({'amount': "Should be positive"})
class PriorityEnglishBid(Bid):
class Options:
override = True
import_roles = {
'owner': (
role.ImportRoleChain() +
blacklist(
'classification',
'isCurrentTenant'
) +
role.ImportStatusRole(
{'active_tendering'},
whitelist(
'bidders',
'status',
'value',
'unit',
'quantity',
'documents',
'qualified'
)
) +
role.ImportStatusRole(
{'qualification', 'active_qualification'},
whitelist(
'documents'
)
)
)
}
class DutchBid(_Bid):
class Options:
import_roles = {
......
......@@ -267,7 +267,7 @@ schemas:
x-legalNameUa: Початкова сума
x-legalNameEn: Initial amount
landSell-priorityEnglish.Bid:
x-baseClass: 'prozorro_sale.procedure.models.base.PriorityEnglishBid'
x-baseClass: 'prozorro_sale.procedure.packages.landSell.procedure.PriorityEnglishBid'
title: landSell-priorityEnglish.Bid
allOf:
- $ref: '#/components/schemas/landSell-english.Bid'
......@@ -289,6 +289,12 @@ schemas:
readOnly: true
x-legalNameUa: Учасник з переважним правом
x-legalNameEn: Participant with priority right
dateActivated:
type: string
format: date-time
readOnly: true
x-legalNameUa: Дата активації заяви на участь
x-legalNameEn: Bid activation date
landSell.Award:
x-baseClass: 'prozorro_sale.procedure.packages.landSell.procedure.Award'
title: landSell.Award
......
......@@ -681,7 +681,6 @@ schemas:
required:
- bidders
legitimatePropertyLease-priorityEnglish.Bid:
x-baseClass: 'prozorro_sale.procedure.models.base.PriorityEnglishBid'
allOf:
- $ref: '#/components/schemas/legitimatePropertyLease-english.Bid'
- type: object
......
......@@ -140,8 +140,8 @@ class Tendering(mixins.BidDocumentDocTypeMixin,
if self.procedure.tenderAttempts > 1 and self.procedure.previousAuctionBidder:
prev_bidder_id = self.procedure.previousAuctionBidder['identifier']['id']
prev_bidder_bid = next(
(bid for bid in self.procedure.bids if bid.bidders[0]['identifier']['id'] == prev_bidder_id), None)
if prev_bidder_bid and prev_bidder_bid['status'] == 'active':
(bid for bid in self.procedure.bids if bid._bidder.identifier.id == prev_bidder_id), None)
if prev_bidder_bid and prev_bidder_bid.status == constants.BidStatus.active:
self.procedure.minNumberOfQualifiedBids = 1
def _timer_tick(self):
......
......@@ -390,15 +390,40 @@ class ProcedurePriorityEnglish(ProcedureEnglish):
)
}
@property
def _current_tenant_bid(self):
if self.bids:
return next(
filter(
lambda x: x.isCurrentTenant and x.status == priority_english_constants.BidStatus.active,
self.bids
),
None
)
def _on_currentTenant_set(self, value):
if value:
if value and self.bids:
if current_tenant_id := value.get('identifier', {}).get('id'):
if bids := self.bids:
for bid in bids:
if bid.status == priority_english_constants.BidStatus.active:
if bid.bidders[0].identifier.id == current_tenant_id:
bid.isCurrentTenant = True
# todo add logic for multiple bids with same identifier.id
elif bid.isCurrentTenant:
bid.isCurrentTenant = False
bid.status = priority_english_constants.BidStatus.inactive
# deactivation of current tenant bid
if current_tenant_bid := self._current_tenant_bid:
current_tenant_bid.isCurrentTenant = False
current_tenant_bid.status = priority_english_constants.BidStatus.inactive
# activation of new current tenant bid
potential_current_tenants = sorted(
filter(
lambda x: (
x._bidder.identifier.id == current_tenant_id and
x.status == priority_english_constants.BidStatus.active
),
self.bids
),
key=lambda x: x.dateActivated
)
if potential_current_tenants:
potential_current_tenants[0].isCurrentTenant = True
class PriorityEnglishBid(base.Bid):
def _on_status_set(self, value):
if value == priority_english_constants.BidStatus.active:
self.dateActivated = datetime.now()
from datetime import datetime
from schematics.exceptions import DataError
from prozorro_sale.procedure import calculator
from prozorro_sale.procedure.utils import get_notifier, check_forbidden_fields
from prozorro_sale.procedure import calculator, utils
from prozorro_sale.procedure.errors import ForbiddenStatusError, AclPermissionError
from prozorro_sale.procedure.roles import owner, public, any_of, bidder
from prozorro_sale.procedure.state_machine import mixins, awards, states, base, base_states
......@@ -146,8 +145,8 @@ class Tendering(mixins.BidDocumentDocTypeMixin, states.Tendering):
if self.procedure.tenderAttempts > 1 and self.procedure.previousAuctionBidder:
prev_bidder_id = self.procedure.previousAuctionBidder['identifier']['id']
prev_bidder_bid = next(
(bid for bid in self.procedure.bids if bid.bidders[0]['identifier']['id'] == prev_bidder_id), None)
if prev_bidder_bid and prev_bidder_bid['status'] == 'active':
(bid for bid in self.procedure.bids if bid._bidder.identifier.id == prev_bidder_id), None)
if prev_bidder_bid and prev_bidder_bid.status == english_constants.BidStatus.active:
self.procedure.minNumberOfQualifiedBids = 1
def _timer_tick(self):
......@@ -631,9 +630,9 @@ class PriorityTendering(mixins.CurrentTenantMixin, Tendering):
def update_procedure(self, data, context):
self._ensure_priority_transfer_period()
forbidden_fields = tuple(field for field in data.keys() if field != 'currentTenant')
check_forbidden_fields(forbidden_fields, data)
utils.check_forbidden_fields(forbidden_fields, data)
self.procedure.import_data(raw_data=data, role='owner', app_data={'procedure_status': self.procedure.status})
get_notifier().add_notification('procedure_updated', payload={})
utils.get_notifier().add_notification('procedure_updated', payload={})
@bidder
def update_bid_status(self, bid_id, status, context):
......
......@@ -449,7 +449,7 @@ class CurrentTenantMixin:
def _check_bid_is_from_current_tenant(self, bid):
if self._current_tenant:
return bid.bidders[0].identifier.id == self._current_tenant.identifier.id
return bid._bidder.identifier.id == self._current_tenant.identifier.id
return False
def _validate_multi_current_tenants(self, bids):
......
import datetime
import pytest
from contextlib import nullcontext as does_not_raise
from unittest.mock import patch
from schematics.exceptions import DataError
from prozorro_sale.procedure import errors
class TenderingTest:
@pytest.mark.parametrize("data, expected", [
@pytest.mark.parametrize(('data', 'expected'), [
pytest.param(
{'documents': []},
does_not_raise(),
id='bid without documents'
),
pytest.param(
{'dateModified': datetime.datetime.now().isoformat()},
pytest.raises(DataError, match='^{"dateModified": "Cannot update with role owner"}$'),
id='dateModified field blacklisting'
),
pytest.param(
{'datePublished': datetime.datetime.now().isoformat()},
pytest.raises(DataError, match='^{"datePublished": "Cannot update with role owner"}$'),
id='datePublished field blacklisting'
),
])
def test_create_bid(self, make_active_tendering_procedure, bid_data, acl_context, data, expected):
test_procedure = make_active_tendering_procedure()
......
import datetime
import pytest
from copy import deepcopy
......@@ -135,16 +137,100 @@ class TestProcedureTendering(TenderingTest, TestBidsVisibility, TenderingTestBid
with error_expectation:
test_procedure.update_bid_status(bid.id, second_bid_status, acl_context(bid))
@pytest.mark.parametrize("data, expected", [
@pytest.mark.parametrize(('data', 'expected'), [
pytest.param(
{'isCurrentTenant': True},
pytest.raises(DataError, match='^{"isCurrentTenant": "Cannot update with role owner"}$'),
id='bid without documents'
id='isCurrentTenant field blacklisting'
),
pytest.param(
{'dateActivated': datetime.datetime.now().isoformat()},
pytest.raises(DataError, match='^{"dateActivated": "Cannot update with role owner"}$'),
id='dateActivated field blacklisting'
),
])
def test_LSP_create_bid(self, make_active_tendering_procedure, bid_data, acl_context, data, expected):
super().test_create_bid(make_active_tendering_procedure, bid_data, acl_context, data, expected)
def test_priority_transfer(self):
# todo add tests
pass
def get_current_tenant(self, proc, current_tenant_id, constants):
current_tenants = list(
filter(lambda x: x.status == constants.BidStatus.active and x.isCurrentTenant, proc.bids))
if current_tenants:
assert len(current_tenants) == 1
current_tenant = current_tenants[0]
assert current_tenant.bidders[0].identifier.id == current_tenant_id
return current_tenant
@pytest.mark.parametrize("current_tenant_id, bidders_ids, updated_current_tenant_id", [
pytest.param(
"12345678",
["12345678", "87654321"],
"87654321",
id='current tenant changed'
),
pytest.param(
"12345678",
["12345678", "87654321", "87654321"],
"87654321",
id='current tenant changed, 2 possible new tenants'
),
pytest.param(
"12345678",
["12345678", "87654321", "87654321", "87654321"],
"87654321",
id='current tenant changed, 3 possible new tenants'
),
pytest.param(
"12121212",
["12345678", "87654321", "12121212"],
"12121212",
id='current tenant didn\'t change'
),
])
def test_priority_transfer(self, make_active_tendering_procedure, procedure_data, bid_data, acl_context, constants,
current_tenant_id, bidders_ids, updated_current_tenant_id):
_procedure_data = {
'currentTenant': procedure_data['currentTenant']
}
_procedure_data['currentTenant']['identifier']['id'] = current_tenant_id
test_procedure = make_active_tendering_procedure(
**_procedure_data
)
# create active bids
for bid_id in bidders_ids:
bid_data['bidders'][0]['identifier']['id'] = bid_id
_bid_data = test_procedure.create_bid(bid_data, acl_context(test_procedure))
bid = test_procedure.bids[_bid_data['id']]
test_procedure.update_bid_status(bid.id, constants.BidStatus.active, acl_context(bid))
assert len(list(filter(lambda x: x.status == constants.BidStatus.active, test_procedure.bids))) == len(
bidders_ids)
# check current tenant before update
old_current_tenant = self.get_current_tenant(test_procedure, current_tenant_id, constants)
# update current tenant
_procedure_data['currentTenant']['identifier']['id'] = updated_current_tenant_id
test_procedure.update_procedure(_procedure_data, acl_context(test_procedure))
# check current tenant after update
new_current_tenant = self.get_current_tenant(test_procedure, updated_current_tenant_id, constants)
if new_current_tenant:
# check if new current tenant was activated the earliest
potential_current_tenants = sorted(list(
filter(lambda x: x.status == constants.BidStatus.active and x.bidders[
0].identifier.id == updated_current_tenant_id, test_procedure.bids)),
key=lambda x: x.dateActivated
)
assert len(potential_current_tenants) >= 1
assert potential_current_tenants[0].dateActivated <= potential_current_tenants[-1].dateActivated
assert new_current_tenant.id == potential_current_tenants[0].id
# check deactivation of old current tenant
if old_current_tenant:
if old_current_tenant.id != new_current_tenant.id:
assert old_current_tenant.isCurrentTenant is False
assert old_current_tenant.status == constants.BidStatus.inactive
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment