diff --git a/.travis.yml b/.travis.yml index c6e03dc5..082c6073 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,8 @@ python: - "2.7" services: - couchdb +env: + - TZ=Europe/Kiev cache: directories: - eggs diff --git a/bootstrap.py b/bootstrap.py old mode 100644 new mode 100755 diff --git a/docs.py b/docs.py old mode 100644 new mode 100755 diff --git a/docs/source/tutorial/award-complaint-accept.http b/docs/source/tutorial/award-complaint-accept.http index 8c7b1983..615e303f 100644 --- a/docs/source/tutorial/award-complaint-accept.http +++ b/docs/source/tutorial/award-complaint-accept.http @@ -1,4 +1,4 @@ -PATCH /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4 HTTP/1.0 +PATCH /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137 HTTP/1.0 Authorization: Basic cmV2aWV3ZXI6 Content-Length: 32 Content-Type: application/json @@ -19,12 +19,12 @@ Content-Type: application/json; charset=UTF-8 { "author": "complaint_owner", "title": "Complaint_Attachement.pdf", - "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4/documents/28cbb7a576a6413495bbbde1679820ef?download=e4308e9037a64cf48068cbfba4d98ca6", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137/documents/91909b0f4b4c45eeb381310eb50d454a?download=62340542d3c64d97b44c53d20b1458cd", "format": "application/pdf", "documentOf": "tender", - "datePublished": "2016-02-01T16:18:59.084081+02:00", - "id": "28cbb7a576a6413495bbbde1679820ef", - "dateModified": "2016-02-01T16:18:59.084119+02:00" + "datePublished": "2016-02-11T16:47:21.340754+02:00", + "id": "91909b0f4b4c45eeb381310eb50d454a", + "dateModified": "2016-02-11T16:47:21.340804+02:00" } ], "description": "Умови виставлені замовником не містять достатньо інформації, щоб заявка мала сенс.", @@ -50,10 +50,10 @@ Content-Type: application/json; charset=UTF-8 } }, "title": "Недостатньо інформації", - "dateSubmitted": "2016-02-01T16:18:59.224423+02:00", - "date": "2016-02-01T16:18:58.753880+02:00", + "dateSubmitted": "2016-02-11T16:47:21.528703+02:00", + "date": "2016-02-11T16:47:20.903103+02:00", "type": "complaint", - "id": "fa17d99108524c658940302e3591e2b4" + "id": "0b79acf448cf497683bbd453bccc3137" } } diff --git a/docs/source/tutorial/award-complaint-complaint.http b/docs/source/tutorial/award-complaint-complaint.http index a8d66172..25fdfa24 100644 --- a/docs/source/tutorial/award-complaint-complaint.http +++ b/docs/source/tutorial/award-complaint-complaint.http @@ -1,4 +1,4 @@ -PATCH /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4?acc_token=a03b162b9cf84d14bd3f113795c35e6f HTTP/1.0 +PATCH /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137?acc_token=7e7b337eb069455dadda6f2981651233 HTTP/1.0 Authorization: Basic YnJva2VyOg== Content-Length: 31 Content-Type: application/json @@ -19,12 +19,12 @@ Content-Type: application/json; charset=UTF-8 { "author": "complaint_owner", "title": "Complaint_Attachement.pdf", - "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4/documents/28cbb7a576a6413495bbbde1679820ef?download=e4308e9037a64cf48068cbfba4d98ca6", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137/documents/91909b0f4b4c45eeb381310eb50d454a?download=62340542d3c64d97b44c53d20b1458cd", "format": "application/pdf", "documentOf": "tender", - "datePublished": "2016-02-01T16:18:59.084081+02:00", - "id": "28cbb7a576a6413495bbbde1679820ef", - "dateModified": "2016-02-01T16:18:59.084119+02:00" + "datePublished": "2016-02-11T16:47:21.340754+02:00", + "id": "91909b0f4b4c45eeb381310eb50d454a", + "dateModified": "2016-02-11T16:47:21.340804+02:00" } ], "description": "Умови виставлені замовником не містять достатньо інформації, щоб заявка мала сенс.", @@ -50,10 +50,10 @@ Content-Type: application/json; charset=UTF-8 } }, "title": "Недостатньо інформації", - "dateSubmitted": "2016-02-01T16:18:59.224423+02:00", - "date": "2016-02-01T16:18:58.753880+02:00", + "dateSubmitted": "2016-02-11T16:47:21.528703+02:00", + "date": "2016-02-11T16:47:20.903103+02:00", "type": "complaint", - "id": "fa17d99108524c658940302e3591e2b4" + "id": "0b79acf448cf497683bbd453bccc3137" } } diff --git a/docs/source/tutorial/award-complaint-reject.http b/docs/source/tutorial/award-complaint-reject.http index 71b51020..0ccb694b 100644 --- a/docs/source/tutorial/award-complaint-reject.http +++ b/docs/source/tutorial/award-complaint-reject.http @@ -1,4 +1,4 @@ -PATCH /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/6329300ffc6a4ccdae63413e02f4e457 HTTP/1.0 +PATCH /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/91a18893883a40cfaa063561a353abac HTTP/1.0 Authorization: Basic cmV2aWV3ZXI6 Content-Length: 31 Content-Type: application/json @@ -15,7 +15,7 @@ Content-Type: application/json; charset=UTF-8 { "data": { "status": "invalid", - "dateDecision": "2016-02-01T16:18:59.654266+02:00", + "dateDecision": "2016-02-11T16:47:21.978407+02:00", "description": "Умови виставлені замовником не містять достатньо інформації, щоб заявка мала сенс.", "author": { "contactPoint": { @@ -39,10 +39,10 @@ Content-Type: application/json; charset=UTF-8 } }, "title": "Недостатньо інформації", - "dateSubmitted": "2016-02-01T16:18:59.354242+02:00", - "date": "2016-02-01T16:18:59.352815+02:00", + "dateSubmitted": "2016-02-11T16:47:21.677576+02:00", + "date": "2016-02-11T16:47:21.676090+02:00", "type": "complaint", - "id": "6329300ffc6a4ccdae63413e02f4e457" + "id": "91a18893883a40cfaa063561a353abac" } } diff --git a/docs/source/tutorial/award-complaint-submission-complaint.http b/docs/source/tutorial/award-complaint-submission-complaint.http index cf7a7c55..12e4d2d3 100644 --- a/docs/source/tutorial/award-complaint-submission-complaint.http +++ b/docs/source/tutorial/award-complaint-submission-complaint.http @@ -1,4 +1,4 @@ -POST /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints?acc_token=dacd3526c7624fa5acd025138c2149a9 HTTP/1.0 +POST /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints?acc_token=a2378a8829b148fdaf207238f78e126a HTTP/1.0 Authorization: Basic YnJva2VyOg== Content-Length: 1703 Content-Type: application/json @@ -35,10 +35,10 @@ DATA: Response: 201 Created Content-Type: application/json; charset=UTF-8 -Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/6329300ffc6a4ccdae63413e02f4e457 +Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/91a18893883a40cfaa063561a353abac { "access": { - "token": "c44e2abb06a440b6a4795a4040597939" + "token": "6aa7d528dd3b4a08ae521a1e910c094e" }, "data": { "status": "pending", @@ -65,10 +65,10 @@ Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed4120348 } }, "title": "Недостатньо інформації", - "dateSubmitted": "2016-02-01T16:18:59.354242+02:00", - "date": "2016-02-01T16:18:59.352815+02:00", + "dateSubmitted": "2016-02-11T16:47:21.677576+02:00", + "date": "2016-02-11T16:47:21.676090+02:00", "type": "complaint", - "id": "6329300ffc6a4ccdae63413e02f4e457" + "id": "91a18893883a40cfaa063561a353abac" } } diff --git a/docs/source/tutorial/award-complaint-submission-upload.http b/docs/source/tutorial/award-complaint-submission-upload.http index e61e09b2..3de01234 100644 --- a/docs/source/tutorial/award-complaint-submission-upload.http +++ b/docs/source/tutorial/award-complaint-submission-upload.http @@ -1,22 +1,22 @@ -POST /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4/documents?acc_token=a03b162b9cf84d14bd3f113795c35e6f HTTP/1.0 +POST /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137/documents?acc_token=7e7b337eb069455dadda6f2981651233 HTTP/1.0 Authorization: Basic YnJva2VyOg== -Content-Length: 199 -Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy52629908468$ +Content-Length: 201 +Content-Type: multipart/form-data; boundary=----------a_BoUnDaRy848218209748$ Host: api-sandbox.openprocurement.org Response: 201 Created Content-Type: application/json; charset=UTF-8 -Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4/documents/28cbb7a576a6413495bbbde1679820ef +Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137/documents/91909b0f4b4c45eeb381310eb50d454a { "data": { "author": "complaint_owner", "title": "Complaint_Attachement.pdf", - "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4/documents/28cbb7a576a6413495bbbde1679820ef?download=e4308e9037a64cf48068cbfba4d98ca6", + "url": "http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137/documents/91909b0f4b4c45eeb381310eb50d454a?download=62340542d3c64d97b44c53d20b1458cd", "format": "application/pdf", "documentOf": "tender", - "datePublished": "2016-02-01T16:18:59.084081+02:00", - "id": "28cbb7a576a6413495bbbde1679820ef", - "dateModified": "2016-02-01T16:18:59.084119+02:00" + "datePublished": "2016-02-11T16:47:21.340754+02:00", + "id": "91909b0f4b4c45eeb381310eb50d454a", + "dateModified": "2016-02-11T16:47:21.340804+02:00" } } diff --git a/docs/source/tutorial/award-complaint-submission.http b/docs/source/tutorial/award-complaint-submission.http index d7173886..30b99f80 100644 --- a/docs/source/tutorial/award-complaint-submission.http +++ b/docs/source/tutorial/award-complaint-submission.http @@ -1,4 +1,4 @@ -POST /api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints?acc_token=dacd3526c7624fa5acd025138c2149a9 HTTP/1.0 +POST /api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints?acc_token=a2378a8829b148fdaf207238f78e126a HTTP/1.0 Authorization: Basic YnJva2VyOg== Content-Length: 1682 Content-Type: application/json @@ -34,10 +34,10 @@ DATA: Response: 201 Created Content-Type: application/json; charset=UTF-8 -Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed41203486bb37008eac4f06e41/awards/a85cbc348f524e4aa6bdd93dd448fda2/complaints/fa17d99108524c658940302e3591e2b4 +Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/83985b0f5ea54e53bbac9a5cc512a569/awards/49d5348a60a3429995bdfba908412b88/complaints/0b79acf448cf497683bbd453bccc3137 { "access": { - "token": "a03b162b9cf84d14bd3f113795c35e6f" + "token": "7e7b337eb069455dadda6f2981651233" }, "data": { "status": "draft", @@ -64,9 +64,9 @@ Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/f8fd2ed4120348 } }, "title": "Недостатньо інформації", - "date": "2016-02-01T16:18:58.753880+02:00", + "date": "2016-02-11T16:47:20.903103+02:00", "type": "complaint", - "id": "fa17d99108524c658940302e3591e2b4" + "id": "0b79acf448cf497683bbd453bccc3137" } } diff --git a/docs/source/tutorial/tender-post-attempt-json-data.http b/docs/source/tutorial/tender-post-attempt-json-data.http index 4727ca79..68be3300 100644 --- a/docs/source/tutorial/tender-post-attempt-json-data.http +++ b/docs/source/tutorial/tender-post-attempt-json-data.http @@ -7,7 +7,7 @@ DATA: { "data": { "tenderPeriod": { - "endDate": "2016-02-14T18:54:24.696596+02:00" + "endDate": "2016-02-27T16:59:32.137349+02:00" }, "title": "футляри до державних нагород", "minimalStep": { @@ -64,90 +64,3 @@ DATA: } } -Response: 201 Created -Content-Type: application/json; charset=UTF-8 -Location: http://api-sandbox.openprocurement.org/api/0.12/tenders/b337832d657e4a07bdd0f6572b431fbd -{ - "access": { - "token": "0d8b9c3b5b5c4551829844cbbe979700" - }, - "data": { - "procurementMethod": "open", - "status": "active.tendering", - "tenderID": "UA-2016-01-29-000001", - "tenderPeriod": { - "startDate": "2016-01-29T18:54:26.346017+02:00", - "endDate": "2016-02-14T18:54:24.696596+02:00" - }, - "title": "футляри до державних нагород", - "minimalStep": { - "currency": "UAH", - "amount": 35.0, - "valueAddedTaxIncluded": true - }, - "items": [ - { - "description": "Послуги шкільних їдалень", - "classification": { - "scheme": "CPV", - "description": "Test", - "id": "37810000-9" - }, - "additionalClassifications": [ - { - "scheme": "ДКПП", - "id": "17.21.1", - "description": "Послуги шкільних їдалень" - } - ], - "id": "03636c21a8f943dcb8fac93d020240f3", - "unit": { - "code": "44617100-9", - "name": "item" - }, - "quantity": 1 - } - ], - "complaintPeriod": { - "startDate": "2016-01-29T18:54:26.346017+02:00", - "endDate": "2016-02-10T18:54:24.696596+02:00" - }, - "procurementMethodType": "aboveThresholdUA", - "value": { - "currency": "UAH", - "amount": 500.0, - "valueAddedTaxIncluded": true - }, - "submissionMethod": "electronicAuction", - "next_check": "2016-02-14T18:54:24.696596+02:00", - "procuringEntity": { - "contactPoint": { - "url": "http://sch10.edu.vn.ua/", - "name": "Куца Світлана Валентинівна", - "telephone": "+380 (432) 46-53-02" - }, - "identifier": { - "scheme": "UA-EDR", - "id": "21725150", - "legalName": "Заклад \"Загальноосвітня школа І-ІІІ ступенів № 10 Вінницької міської ради\"" - }, - "name": "ЗОСШ #10 м.Вінниці", - "address": { - "postalCode": "21027", - "countryName": "Україна", - "streetAddress": "вул. Стахурського. 22", - "region": "м. Вінниця", - "locality": "м. Вінниця" - } - }, - "enquiryPeriod": { - "startDate": "2016-01-29T18:54:26.346017+02:00", - "endDate": "2016-02-11T18:54:24.696596+02:00" - }, - "owner": "broker", - "dateModified": "2016-01-29T18:54:26.421907+02:00", - "id": "b337832d657e4a07bdd0f6572b431fbd", - "awardCriteria": "lowestCost" - } -} - diff --git a/openprocurement/tender/openua/models.py b/openprocurement/tender/openua/models.py index 1781839a..b225b6e1 100644 --- a/openprocurement/tender/openua/models.py +++ b/openprocurement/tender/openua/models.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from datetime import timedelta +from datetime import timedelta, time, datetime from zope.interface import implementer from schematics.types import StringType, BooleanType from schematics.types.compound import ModelType @@ -19,7 +19,7 @@ auction_role, chronograph_role, chronograph_view_role, view_bid_role, Administrator_bid_role, Administrator_role, schematics_default_role, TZ, get_now, schematics_embedded_role, validate_lots_uniq, - embedded_lot_role, default_lot_role, + embedded_lot_role, default_lot_role, calc_auction_end_time, get_tender, ) from openprocurement.tender.openua.interfaces import ITenderUA from openprocurement.tender.openua.utils import ( @@ -101,6 +101,51 @@ def export_loop(self, list_instance, field_converter, return data +class TenderAuctionPeriod(Period): + """The auction period.""" + + @serializable(serialize_when_none=False) + def shouldStartAfter(self): + if self.endDate: + return + tender = self.__parent__ + if tender.lots or tender.status not in ['active.tendering', 'active.auction']: + return + if self.startDate and get_now() > calc_auction_end_time(tender.numberOfBids, self.startDate): + return calc_auction_end_time(tender.numberOfBids, self.startDate).isoformat() + else: + decision_dates = [ + datetime.combine(complaint.dateDecision.date() + timedelta(days=3), time(0, tzinfo=complaint.dateDecision.tzinfo)) + for complaint in tender.complaints + if complaint.dateDecision + ] + decision_dates.append(tender.tenderPeriod.endDate) + return max(decision_dates).isoformat() + + +class LotAuctionPeriod(Period): + """The auction period.""" + + @serializable(serialize_when_none=False) + def shouldStartAfter(self): + if self.endDate: + return + tender = get_tender(self) + lot = self.__parent__ + if tender.status not in ['active.tendering', 'active.auction'] or lot.status != 'active': + return + if self.startDate and get_now() > calc_auction_end_time(lot.numberOfBids, self.startDate): + return calc_auction_end_time(lot.numberOfBids, self.startDate).isoformat() + else: + decision_dates = [ + datetime.combine(complaint.dateDecision.date() + timedelta(days=3), time(0, tzinfo=complaint.dateDecision.tzinfo)) + for complaint in tender.complaints + if complaint.dateDecision + ] + decision_dates.append(tender.tenderPeriod.endDate) + return max(decision_dates).isoformat() + + class Bid(BaseBid): class Options: @@ -225,6 +270,8 @@ class Options: 'chronograph_view': whitelist('id', 'auctionPeriod', 'numberOfBids', 'status'), } + auctionPeriod = ModelType(LotAuctionPeriod, default={}) + @serializable def numberOfBids(self): """A property that is serialized by schematics exports.""" @@ -274,6 +321,7 @@ class Options: enquiryPeriod = ModelType(Period, required=False) tenderPeriod = ModelType(PeriodStartEndRequired, required=True) + auctionPeriod = ModelType(TenderAuctionPeriod, default={}) bids = SifterListType(ModelType(Bid), default=list(), filter_by='status', filter_in_values=['invalid', 'deleted']) # A list of all the companies who entered submissions for the tender. awards = ListType(ModelType(Award), default=list()) complaints = ListType(ModelType(Complaint), default=list()) @@ -301,12 +349,25 @@ def numberOfBids(self): """A property that is serialized by schematics exports.""" return len([bid for bid in self.bids if bid.status == "active"]) - @serializable + @serializable(serialize_when_none=False) def next_check(self): now = get_now() checks = [] if self.status == 'active.tendering' and self.tenderPeriod.endDate and not any([i.status in BLOCK_COMPLAINT_STATUS for i in self.complaints]): checks.append(self.tenderPeriod.endDate.astimezone(TZ)) + elif not self.lots and self.status == 'active.auction' and self.auctionPeriod and self.auctionPeriod.startDate and not self.auctionPeriod.endDate: + if now < self.auctionPeriod.startDate: + checks.append(self.auctionPeriod.startDate.astimezone(TZ)) + elif now < calc_auction_end_time(self.numberOfBids, self.auctionPeriod.startDate).astimezone(TZ): + checks.append(calc_auction_end_time(self.numberOfBids, self.auctionPeriod.startDate).astimezone(TZ)) + elif self.lots and self.status == 'active.auction': + for lot in self.lots: + if lot.status != 'active' or not lot.auctionPeriod or not lot.auctionPeriod.startDate or lot.auctionPeriod.endDate: + continue + if now < lot.auctionPeriod.startDate: + checks.append(lot.auctionPeriod.startDate.astimezone(TZ)) + elif now < calc_auction_end_time(lot.numberOfBids, lot.auctionPeriod.startDate).astimezone(TZ): + checks.append(calc_auction_end_time(lot.numberOfBids, lot.auctionPeriod.startDate).astimezone(TZ)) elif not self.lots and self.status == 'active.awarded': standStillEnds = [ a.complaintPeriod.endDate.astimezone(TZ) @@ -335,7 +396,7 @@ def next_check(self): lots_ends.append(standStillEnd) if lots_ends: checks.append(min(lots_ends)) - return sorted(checks)[0].isoformat() if checks else None + return min(checks).isoformat() if checks else None def invalidate_bids_data(self): for bid in self.bids: diff --git a/openprocurement/tender/openua/tests/chronograph.py b/openprocurement/tender/openua/tests/chronograph.py index 66169ecd..5f29724d 100644 --- a/openprocurement/tender/openua/tests/chronograph.py +++ b/openprocurement/tender/openua/tests/chronograph.py @@ -23,9 +23,10 @@ def test_set_auction_period(self): response = self.app.patch_json('/tenders/{}'.format(self.tender_id), {'data': {"auctionPeriod": {"startDate": None}}}) self.assertEqual(response.status, '200 OK') - self.assertNotIn('auctionPeriod', response.json['data']) - + self.assertIn('auctionPeriod', response.json['data']) + self.assertNotIn('startDate', response.json['data']['auctionPeriod']) + class TenderSwitch1BidResourceTest(BaseTenderUAContentWebTest): initial_bids = test_bids[:1] @@ -141,7 +142,8 @@ def test_set_auction_period(self): response = self.app.patch_json('/tenders/{}'.format(self.tender_id), {'data': {"lots": [{"auctionPeriod": {"startDate": None}}]}}) self.assertEqual(response.status, '200 OK') - self.assertNotIn('auctionPeriod', response.json['data']["lots"][0]) + self.assertIn('auctionPeriod', response.json['data']["lots"][0]) + self.assertNotIn('startDate', response.json['data']["lots"][0]['auctionPeriod']) class TenderLotSwitch1BidResourceTest(BaseTenderUAContentWebTest): diff --git a/openprocurement/tender/openua/tests/lot.py b/openprocurement/tender/openua/tests/lot.py index 52590e57..e44bd7bc 100644 --- a/openprocurement/tender/openua/tests/lot.py +++ b/openprocurement/tender/openua/tests/lot.py @@ -353,13 +353,14 @@ def test_get_tender_lot(self): response = self.app.get('/tenders/{}/lots/{}'.format(self.tender_id, lot['id'])) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(set(response.json['data']), set([u'id', u'title', u'description', u'minimalStep', u'value', u'status'])) + self.assertEqual(set(response.json['data']), set([u'status', u'description', u'title', u'minimalStep', u'auctionPeriod', u'value', u'id'])) self.set_status('active.qualification') response = self.app.get('/tenders/{}/lots/{}'.format(self.tender_id, lot['id'])) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') + lot.pop('auctionPeriod') self.assertEqual(response.json['data'], lot) response = self.app.get('/tenders/{}/lots/some_id'.format(self.tender_id), status=404) @@ -389,13 +390,14 @@ def test_get_tender_lots(self): response = self.app.get('/tenders/{}/lots'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') - self.assertEqual(set(response.json['data'][0]), set([u'id', u'title', u'description', u'minimalStep', u'value', u'status'])) + self.assertEqual(set(response.json['data'][0]), set([u'status', u'description', u'title', u'minimalStep', u'auctionPeriod', u'value', u'id'])) self.set_status('active.qualification') response = self.app.get('/tenders/{}/lots'.format(self.tender_id)) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') + lot.pop('auctionPeriod') self.assertEqual(response.json['data'][0], lot) response = self.app.get('/tenders/some_id/lots', status=404) diff --git a/openprocurement/tender/openua/tests/tender.py b/openprocurement/tender/openua/tests/tender.py index 9c02ec86..3aac352e 100644 --- a/openprocurement/tender/openua/tests/tender.py +++ b/openprocurement/tender/openua/tests/tender.py @@ -504,9 +504,13 @@ def test_create_tender_generated(self): self.assertEqual(response.status, '201 Created') self.assertEqual(response.content_type, 'application/json') tender = response.json['data'] - self.assertEqual(set(tender), set([u'procurementMethodType', u'id', u'dateModified', u'tenderID', u'status', u'enquiryPeriod', - u'tenderPeriod', u'complaintPeriod', u'minimalStep', u'items', u'value', u'procuringEntity', u'next_check', - u'procurementMethod', u'awardCriteria', u'submissionMethod', u'title', u'owner'])) + self.assertEqual(set(tender), set([ + u'procurementMethodType', u'id', u'dateModified', u'tenderID', + u'status', u'enquiryPeriod', u'tenderPeriod', u'complaintPeriod', + u'minimalStep', u'items', u'value', u'procuringEntity', + u'next_check', u'procurementMethod', u'awardCriteria', + u'submissionMethod', u'auctionPeriod', u'title', u'owner', + ])) self.assertNotEqual(data['id'], tender['id']) self.assertNotEqual(data['doc_id'], tender['id']) self.assertNotEqual(data['tenderID'], tender['tenderID']) @@ -520,8 +524,11 @@ def test_create_tender(self): self.assertEqual(response.status, '201 Created') self.assertEqual(response.content_type, 'application/json') tender = response.json['data'] - self.assertEqual(set(tender) - set(test_tender_ua_data), set( - [u'id', u'dateModified', u'enquiryPeriod', u'complaintPeriod', u'tenderID', u'status', u'procurementMethod', u'awardCriteria', u'submissionMethod', u'next_check', u'owner'])) + self.assertEqual(set(tender) - set(test_tender_ua_data), set([ + u'id', u'dateModified', u'enquiryPeriod', u'auctionPeriod', + u'complaintPeriod', u'tenderID', u'status', u'procurementMethod', + u'awardCriteria', u'submissionMethod', u'next_check', u'owner', + ])) self.assertIn(tender['id'], response.headers['Location']) response = self.app.get('/tenders/{}'.format(tender['id'])) diff --git a/openprocurement/tender/openua/utils.py b/openprocurement/tender/openua/utils.py index 6647eda4..f3906db5 100644 --- a/openprocurement/tender/openua/utils.py +++ b/openprocurement/tender/openua/utils.py @@ -24,11 +24,14 @@ def calculate_business_date(date_obj, timedelta_obj, context=None): def check_bids(request): tender = request.validated['tender'] if tender.lots: + [setattr(i.auctionPeriod, 'startDate', None) for i in tender.lots if i.numberOfBids < 2 and i.auctionPeriod and i.auctionPeriod.startDate] [setattr(i, 'status', 'unsuccessful') for i in tender.lots if i.numberOfBids < 2] if not set([i.status for i in tender.lots]).difference(set(['unsuccessful', 'cancelled'])): tender.status = 'unsuccessful' else: if tender.numberOfBids < 2: + if tender.auctionPeriod and tender.auctionPeriod.startDate: + tender.auctionPeriod.startDate = None tender.status = 'unsuccessful' diff --git a/openprocurement/tender/openua/views/complaint.py b/openprocurement/tender/openua/views/complaint.py index a67b9e32..05911b63 100644 --- a/openprocurement/tender/openua/views/complaint.py +++ b/openprocurement/tender/openua/views/complaint.py @@ -1,5 +1,4 @@ # -*- coding: utf-8 -*- -from datetime import timedelta, time, datetime from logging import getLogger from openprocurement.api.models import get_now from openprocurement.api.views.complaint import TenderComplaintResource @@ -131,23 +130,12 @@ def patch(self): elif self.request.authenticated_role == 'reviewers' and self.context.status == 'pending' and data.get('status', self.context.status) == 'invalid': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateDecision = get_now() - tenderPeriodendDate = datetime.combine(self.context.dateDecision.date(), time(0, tzinfo=self.context.dateDecision.tzinfo)) + timedelta(days=3) - if tender.tenderPeriod.endDate < tenderPeriodendDate: - tender.tenderPeriod.endDate = tenderPeriodendDate - tender.auctionPeriod = None elif self.request.authenticated_role == 'reviewers' and self.context.status == 'pending' and data.get('status', self.context.status) == 'accepted': apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateAccepted = get_now() elif self.request.authenticated_role == 'reviewers' and self.context.status == 'accepted' and data.get('status', self.context.status) == self.context.status: apply_patch(self.request, save=False, src=self.context.serialize()) - elif self.request.authenticated_role == 'reviewers' and self.context.status == 'accepted' and data.get('status', self.context.status) == 'declined': - apply_patch(self.request, save=False, src=self.context.serialize()) - self.context.dateDecision = get_now() - tenderPeriodendDate = datetime.combine(self.context.dateDecision.date(), time(0, tzinfo=self.context.dateDecision.tzinfo)) + timedelta(days=3) - if tender.tenderPeriod.endDate < tenderPeriodendDate: - tender.tenderPeriod.endDate = tenderPeriodendDate - tender.auctionPeriod = None - elif self.request.authenticated_role == 'reviewers' and self.context.status == 'accepted' and data.get('status', self.context.status) == 'satisfied': + elif self.request.authenticated_role == 'reviewers' and self.context.status == 'accepted' and data.get('status', self.context.status) in ['declined', 'satisfied']: apply_patch(self.request, save=False, src=self.context.serialize()) self.context.dateDecision = get_now() else: