Commit 01f2297f authored by Mike Hoang's avatar Mike Hoang
Browse files

fix(LL, LR, LS): moved current tenant determination to seperate mixin, added...

fix(LL, LR, LS): moved current tenant determination to seperate mixin, added base class for validation of bank accounts with required currency
parent 2e8386b1
import random
import re
import uuid
from collections import namedtuple
from copy import deepcopy
from datetime import datetime, timedelta
from itertools import combinations, product
from schematics import role
from schematics import types
from schematics import role, types
from schematics.exceptions import DataError
from schematics.models import Model
from schematics.transforms import blacklist, whitelist
......@@ -309,23 +309,29 @@ class AdditionalClassification(Classification):
}
class Bid(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResources):
class _Bid(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResources):
class Options:
import_roles = {
roles = {
'public': (
role.RoleChain() +
blacklist(
'participationUrl'
)
),
'owner': (
role.ImportRoleChain() + blacklist('classification') +
role.ImportStatusRole(
{'active_tendering'},
whitelist('bidders', 'status', 'value', 'unit', 'quantity', 'documents', 'qualified')
) +
role.ImportStatusRole({'qualification', 'active_qualification'}, whitelist('documents'))
role.RoleChain() +
blacklist(
'participationUrl'
)
),
}
roles = {
'public': role.RoleChain() + blacklist('participationUrl'),
'owner': role.RoleChain() + blacklist('participationUrl'),
'bidder': role.RoleChain() + role.StatusRoleNot(
{'active_auction'}, blacklist('participationUrl')
'bidder': (
role.RoleChain() +
role.StatusRoleNot(
{'active_auction'},
blacklist(
'participationUrl'
)
)
)
}
......@@ -337,6 +343,43 @@ class Bid(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResource
def Value(cls):
return cls.fields['value'].model_class
def validate_additionalClassifications(self, value):
dictionaries.validate_classifiers(value)
def _on_documents_set(self, value):
super()._on_documents_set(value)
self.dateModified = datetime.now()
class Bid(_Bid):
class Options:
import_roles = {
'owner': (
role.ImportRoleChain() +
blacklist(
'classification'
) +
role.ImportStatusRole(
{'active_tendering'},
whitelist(
'bidders',
'status',
'value',
'unit',
'quantity',
'documents',
'qualified'
)
) +
role.ImportStatusRole(
{'qualification', 'active_qualification'},
whitelist(
'documents'
)
)
)
}
def validate_value(self, value):
if not value:
return
......@@ -346,53 +389,70 @@ class Bid(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResource
raise DataError({'amount': "Should be positive"})
def validate_additionalClassifications(self, value):
dictionaries.validate_classifiers(value)
def _on_documents_set(self, value):
super()._on_documents_set(value)
self.dateModified = datetime.now()
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(model_utils.DocumentMixin, model_utils.DateModifiedMixin, _AclResources):
class DutchBid(_Bid):
class Options:
import_roles = {
'owner': (
role.ImportRoleChain() + blacklist('classification') +
role.ImportRoleChain() +
blacklist(
'classification'
) +
role.ImportStatusRole(
{'active_tendering', 'active_auction'},
whitelist('bidders', 'status', 'unit', 'quantity', 'documents', 'qualified')
whitelist(
'bidders',
'status',
'unit',
'quantity',
'documents',
'qualified'
)
) +
role.ImportStatusRole({'qualification', 'active_qualification'}, whitelist('documents'))
),
}
roles = {
'public': role.RoleChain() + blacklist('participationUrl'),
'owner': role.RoleChain() + blacklist('participationUrl'),
'bidder': role.RoleChain() + role.StatusRoleNot(
{'active_auction'}, blacklist('participationUrl')
role.ImportStatusRole(
{'qualification', 'active_qualification'},
whitelist(
'documents'
)
)
)
}
@tools.classproperty
def Doc(cls):
return cls.fields['documents'].model_class
@tools.classproperty
def Value(cls):
return cls.fields['value'].model_class
def validate_additionalClassifications(self, value):
dictionaries.validate_classifiers(value)
def to_primitive_withoutroles(self, app_data):
return super(_BaseModel, self).to_primitive(app_data=app_data)
def _on_documents_set(self, value):
super()._on_documents_set(value)
self.dateModified = datetime.now()
class Item(_BaseModel):
......@@ -515,22 +575,34 @@ class Award(model_utils.DateModifiedMixin, model_utils.DocumentMixin, _BaseModel
class Options:
import_roles = {
'owner': (
role.ImportRoleChain()
+ role.ImportStatusRole(
{'qualification', 'active_qualification'},
whitelist('title', 'description', 'status', 'documents', 'terminationReason')
)
role.ImportRoleChain() +
role.ImportStatusRole(
{'qualification', 'active_qualification'},
whitelist(
'title',
'description',
'status',
'documents',
'terminationReason'
)
)
),
'bidder': (
role.ImportRoleChain()
+ role.ImportStatusRole(
{'qualification'},
whitelist('title', 'description', 'documents')
)
+ role.ImportStatusRole(
{'active_qualification'},
whitelist('documents')
)
role.ImportRoleChain() +
role.ImportStatusRole(
{'qualification'},
whitelist(
'title',
'description',
'documents'
)
) +
role.ImportStatusRole(
{'active_qualification'},
whitelist(
'documents'
)
)
),
}
......@@ -860,7 +932,7 @@ class RealEstateProps(BaseItemProps):
try:
datetime.strptime(str(value), '%Y')
except ValueError:
raise errors.DataError({'constructionYear': "constructionYear should match pattern '%Y'"})
raise DataError({'constructionYear': "constructionYear should match pattern '%Y'"})
class VehicleProps(BaseItemProps):
......@@ -871,14 +943,14 @@ class VehicleProps(BaseItemProps):
try:
datetime.strptime(str(value), '%Y')
except ValueError:
raise errors.DataError({'productionYear': "productionYear should match pattern '%Y'"})
raise DataError({'productionYear': "productionYear should match pattern '%Y'"})
def _on_damagePresence_set(self, value):
if value is True:
if self.get('damagedDescription') is None:
raise errors.DataError({'damagedDescription': 'Required field'})
raise DataError({'damagedDescription': 'Required field'})
if not self['damagedDescription']:
raise errors.DataError({'damagedDescription': 'Value is required'})
raise DataError({'damagedDescription': 'Value is required'})
class LeaseRules(_BaseModel):
......@@ -1046,3 +1118,24 @@ class LifeTime(_BaseModel):
return
if self.dateFrom and value <= self.dateFrom:
raise DataError({'dateTill': 'dateTill must be greater then dateFrom'})
class BankAccountsByType(_BaseModel):
@property
def required_currency_mapping(self) -> dict:
"""
example:
{
'payment': {'UAH', 'USD'},
}
accountType - payment, required accounts[].currency - UAH and USD
:return: mapping of bank accounts types with required currency
"""
raise NotImplementedError
def _validate_data(self, raw_data, **kwargs):
super()._validate_data(raw_data, **kwargs)
if required_currency := self.required_currency_mapping.get(self.accountType, {}):
if missing_required_currency := required_currency - {account.currency for account in self.accounts}:
raise DataError({'accounts': [f'This account type requires account with {currency} currency.' for
currency in missing_required_currency]})
......@@ -681,7 +681,7 @@ schemas:
required:
- bidders
legitimatePropertyLease-priorityEnglish.Bid:
x-baseClass: 'prozorro_sale.procedure.packages.legitimatePropertyLease.procedure.PriorityBid'
x-baseClass: 'prozorro_sale.procedure.models.base.PriorityEnglishBid'
allOf:
- $ref: '#/components/schemas/legitimatePropertyLease-english.Bid'
- type: object
......
......@@ -90,9 +90,10 @@ class AwardActive(AwardStatusUpdateMixin, awards.AwardActive):
def _validate_contract(self, contract, data):
super()._validate_contract(contract, data)
# todo investigate how to implement this in ImportStatusRole
if contract.status == constants.ContractStatus.signed:
utils.check_forbidden_fields(('contractNumber', 'title', 'description', 'value', 'contractTotalValue',
'items', 'buyers', 'dateSigned', 'contractTime'), data)
forbidden_fields = tuple(field for field in data.keys() if field != 'lotPaymentConfirmation')
utils.check_forbidden_fields(forbidden_fields, data)
if contract.status == constants.ContractStatus.pending:
utils.check_forbidden_fields(('lotPaymentConfirmation',), data)
......
......@@ -267,17 +267,12 @@ class BidDocument(base.Document):
)
class BankAccountsByType(base._BaseModel):
UAH_required_account_types = ('payment',)
def _validate_data(self, raw_data, **kwargs):
super()._validate_data(raw_data, **kwargs)
# required UAH currency account validation
if self.accountType in self.UAH_required_account_types:
has_uah_account = any(account for account in self.accounts if account.currency == 'UAH')
if not has_uah_account:
raise errors.DataError({'accounts': 'This account type requires account with UAH currency.'})
class BankAccountsByType(base.BankAccountsByType):
@property
def required_currency_mapping(self):
return {
'payment': {'UAH'}
}
class Contract(base.Contract):
......
......@@ -286,42 +286,6 @@ class RealEstateItem(Item):
self.unit = base.Unit({'code': 'MTK'})
class PriorityBid(base.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'
))
),
}
roles = {
'public': (
role.RoleChain()
+ blacklist('participationUrl')
),
'owner': (
role.RoleChain()
+ blacklist('participationUrl')
),
'bidder': (
role.RoleChain()
+ role.StatusRoleNot({'active_auction'},
blacklist('participationUrl')
)
)
}
class ProcedurePriorityEnglish(mixins.ValidateLegitimatePropertyLeaseDataMixin, mixins.ProcedureDocumentValidationMixin,
state_machine.StateMachine):
required_docs = {
......@@ -405,8 +369,13 @@ class ProcedurePriorityEnglish(mixins.ValidateLegitimatePropertyLeaseDataMixin,
})
class BankAccountsGroup(base._BaseModel):
UAH_required_account_types = ('guarantee', 'registrationFee')
class BankAccountsGroup(base.BankAccountsByType):
@property
def required_currency_mapping(self):
return {
'guarantee': {'UAH'},
'registrationFee': {'UAH'}
}
class Options:
import_roles = {
......@@ -419,16 +388,14 @@ class BankAccountsGroup(base._BaseModel):
if len(value) != len(set(account.currency for account in value)):
raise errors.DataError({'accounts': 'There could be only one account for each currency type.'})
def _validate_data(self, raw_data, **kwargs):
super()._validate_data(raw_data, **kwargs)
if self.accountType in self.UAH_required_account_types:
has_uah_account = any(account for account in self.accounts if account.currency == 'UAH')
if not has_uah_account:
raise errors.DataError({'accounts': 'This account type requires account with UAH currency.'})
class HolderBankAccountsGroup(BankAccountsGroup):
UAH_required_account_types = ('advancePayment', 'lease')
@property
def required_currency_mapping(self):
return {
'advancePayment': {'UAH'},
'lease': {'UAH'}
}
def _validate_data(self, raw_data, **kwargs):
super()._validate_data(raw_data, **kwargs)
......
......@@ -300,22 +300,17 @@ class DutchActiveQualification(ActiveQualification):
self._create_challenger_award(challenger_bid)
class PriorityTendering(Tendering):
def check_bid_is_from_current_tenant(self, bid):
if current_tenant := self.procedure.relatedOrganizations.currentTenant:
return bid.bidders[0].identifier.id == current_tenant.identifier.id
return False
def checking_current_tenant_exist(self):
if len([x for x in self.procedure.bids if x.isCurrentTenant is True and x.status == 'active']) > 1:
raise errors.DataError({'bidders': {'identifier': {'id': 'Bid for current tenant is already registered'}}})
class PriorityTendering(mixins.CurrentTenantMixin, Tendering):
@property
def _current_tenant(self):
return self.procedure.relatedOrganizations.currentTenant
@bidder
def update_bid_status(self, bid_id, status, context):
super().update_bid_status(bid_id, status, context)
bid = self.procedure.bids[bid_id]
bid.isCurrentTenant = self.check_bid_is_from_current_tenant(bid)
self.checking_current_tenant_exist()
bid.isCurrentTenant = self._check_bid_is_from_current_tenant(bid)
self._validate_multi_current_tenants()
class PriorityActiveQualification(ActiveQualification):
......
......@@ -436,3 +436,21 @@ class ClarificationDocumentMixin:
"Procedure cannot be updated without clarifications document. "
"To update procedure data upload clarifications document first")
raise errors.DataError({'documents': err_msg})
class CurrentTenantMixin:
@property
def _current_tenant(self):
"""
:return: current tenant field from procedure
"""
raise NotImplementedError
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 False
def _validate_multi_current_tenants(self):
if len([x for x in self.procedure.bids if x.isCurrentTenant is True and x.status == 'active']) > 1:
raise errors.DataError({'bidders': {'identifier': {'id': 'Bid for current tenant is already registered'}}})
......@@ -55,8 +55,8 @@ class RectificationTest:
{'accountType': 'advancePayment'},
{'accountType': 'lease'}
],
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"0": \{"accounts": "This account type '
r'requires account with UAH currency."\}\}\}$'),
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"0": \{"accounts": \["This account type '
r'requires account with UAH currency."\]\}\}\}$'),
id='required bankAccounts.accounts.currency=UAH validation for bankAccounts.accountType=registrationFee'
),
pytest.param(
......@@ -66,8 +66,8 @@ class RectificationTest:
{'accountType': 'advancePayment'},
{'accountType': 'lease'}
],
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"1": \{"accounts": "This account type '
r'requires account with UAH currency."\}\}\}$'),
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"1": \{"accounts": \["This account type '
r'requires account with UAH currency."\]\}\}\}$'),
id='required bankAccounts.accounts.currency=UAH validation for bankAccounts.accountType=guarantee'
),
pytest.param(
......@@ -77,8 +77,8 @@ class RectificationTest:
{'accountType': 'advancePayment', 'accounts': []},
{'accountType': 'lease'}
],
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"2": \{"accounts": "This account type '
r'requires account with UAH currency."\}\}\}$'),
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"2": \{"accounts": \["This account type '
r'requires account with UAH currency."\]\}\}\}$'),
id='required bankAccounts.accounts.currency=UAH validation for bankAccounts.accountType=advancePayment'
),
pytest.param(
......@@ -88,8 +88,8 @@ class RectificationTest:
{'accountType': 'advancePayment'},
{'accountType': 'lease', 'accounts': []}
],
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"3": \{"accounts": "This account type '
r'requires account with UAH currency."\}\}\}$'),
pytest.raises(errors.DataError, match=r'^\{"bankAccounts": \{"3": \{"accounts": \["This account type '
r'requires account with UAH currency."\]\}\}\}$'),
id='required bankAccounts.accounts.currency=UAH validation for bankAccounts.accountType=lease'
),
pytest.param(
......
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