Revision 2864e701

b/snf-astakos-app/astakos/quotaholder/callpoint.py
34 34
from astakos.quotaholder.exception import (
35 35
    QuotaholderError,
36 36
    CorruptedError, InvalidDataError,
37
    NoStockError, NoCapacityError,
37
    NoCapacityError,
38 38
    DuplicateError)
39 39

  
40 40
from astakos.quotaholder.commission import (
41
    Import, Export, Reclaim, Release, Operations)
41
    Import, Release, Operations)
42 42

  
43 43
from astakos.quotaholder.utils.newname import newname
44 44
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
......
56 56

  
57 57
    def _init_holding(self,
58 58
                      holder, resource, capacity,
59
                      imported_min, imported_max, stock_min, stock_max,
59
                      imported_min, imported_max,
60 60
                      flags):
61 61
        try:
62 62
            h = db_get_holding(holder=holder, resource=resource,
......
68 68
        h.flags = flags
69 69
        h.imported_min = imported_min
70 70
        h.imported_max = imported_max
71
        h.stock_min = stock_min
72
        h.stock_max = stock_max
73 71
        h.save()
74 72

  
75 73
    def init_holding(self, context=None, init_holding=[]):
......
78 76

  
79 77
        for idx, sfh in enumerate(init_holding):
80 78
            (holder, resource, capacity,
81
             imported_min, imported_max, stock_min, stock_max,
79
             imported_min, imported_max,
82 80
             flags) = sfh
83 81

  
84 82
            self._init_holding(holder, resource, capacity,
85 83
                               imported_min, imported_max,
86
                               stock_min, stock_max,
87 84
                               flags)
88 85
        if rejected:
89 86
            raise QuotaholderError(rejected)
......
94 91
        append = rejected.append
95 92

  
96 93
        for idx, tpl in enumerate(reset_holding):
97
            (holder, resource,
98
             imported_min, imported_max, stock_min, stock_max) = tpl
94
            (holder, source, resource,
95
             imported_min, imported_max) = tpl
99 96

  
100 97
            try:
101
                h = db_get_holding(holder=holder, resource=resource,
98
                h = db_get_holding(holder=holder,
99
                                   source=source,
100
                                   resource=resource,
102 101
                                   for_update=True)
103 102
                h.imported_min = imported_min
104 103
                h.imported_max = imported_max
105
                h.stock_min = stock_min
106
                h.stock_max = stock_max
107 104
                h.save()
108 105
            except Holding.DoesNotExist:
109 106
                append(idx)
......
113 110
            raise QuotaholderError(rejected)
114 111
        return rejected
115 112

  
116
    def _check_pending(self, holder, resource):
117
        cs = Commission.objects.filter(holder=holder)
118
        cs = [c for c in cs if c.provisions.filter(resource=resource)]
119
        as_target = [c.serial for c in cs]
120

  
121
        ps = Provision.objects.filter(holder=holder, resource=resource)
122
        as_source = [p.serial.serial for p in ps]
123

  
124
        return as_target + as_source
113
    def _check_pending(self, holding):
114
        ps = Provision.objects.filter(holding=holding)
115
        return ps.count()
125 116

  
126 117
    def release_holding(self, context=None, release_holding=[]):
127 118
        rejected = []
128 119
        append = rejected.append
129 120

  
130
        for idx, (holder, resource) in enumerate(release_holding):
121
        for idx, (holder, source, resource) in enumerate(release_holding):
131 122
            try:
132
                h = db_get_holding(holder=holder, resource=resource,
123
                h = db_get_holding(holder=holder,
124
                                   source=source,
125
                                   resource=resource,
133 126
                                   for_update=True)
134 127
            except Holding.DoesNotExist:
135 128
                append(idx)
136 129
                continue
137 130

  
138
            if self._check_pending(holder, resource):
131
            if self._check_pending(h):
139 132
                append(idx)
140 133
                continue
141 134

  
......
166 159
                reject(holder)
167 160
                continue
168 161

  
169
            append([(holder, h.resource,
170
                     h.imported_min, h.imported_max, h.stock_min, h.stock_max)
162
            append([(holder, h.source, h.resource,
163
                     h.imported_min, h.imported_max)
171 164
                    for h in holdings])
172 165

  
173 166
        return holdings_list, rejected
......
180 173
        hs = Holding.objects.filter(holder__in=holders)
181 174
        holdings = {}
182 175
        for h in hs:
183
            holdings[(h.holder, h.resource)] = h
176
            holdings[(h.holder, h.source, h.resource)] = h
184 177

  
185
        for holder, resource in get_quota:
178
        for holder, source, resource in get_quota:
186 179
            try:
187
                h = holdings[(holder, resource)]
180
                h = holdings[(holder, source, resource)]
188 181
            except:
189 182
                continue
190 183

  
191
            append((h.holder, h.resource, h.capacity,
184
            append((h.holder, h.source, h.resource, h.capacity,
192 185
                    h.imported_min, h.imported_max,
193
                    h.stock_min, h.stock_max,
194 186
                    h.flags))
195 187

  
196 188
        return quotas
......
201 193

  
202 194
        q_holdings = Q()
203 195
        holders = []
204
        for (holder, resource, _, _) in set_quota:
196
        for (holder, source, resource, _, _) in set_quota:
205 197
            holders.append(holder)
206 198

  
207 199
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
208 200
        holdings = {}
209 201
        for h in hs:
210
            holdings[(h.holder, h.resource)] = h
202
            holdings[(h.holder, h.source, h.resource)] = h
211 203

  
212
        for (holder, resource,
204
        for (holder, source, resource,
213 205
             capacity,
214 206
             flags) in set_quota:
215 207

  
216 208
            try:
217
                h = holdings[(holder, resource)]
209
                h = holdings[(holder, source, resource)]
218 210
                h.flags = flags
219 211
            except KeyError:
220
                h = Holding(holder=holder, resource=resource,
212
                h = Holding(holder=holder,
213
                            source=source,
214
                            resource=resource,
221 215
                            flags=flags)
222 216

  
223 217
            h.capacity = capacity
224 218
            h.save()
225
            holdings[(holder, resource)] = h
219
            holdings[(holder, source, resource)] = h
226 220

  
227 221
        if rejected:
228 222
            raise QuotaholderError(rejected)
......
279 273
    def issue_commission(self,
280 274
                         context=None,
281 275
                         clientkey=None,
282
                         target=None,
283 276
                         name=None,
284 277
                         provisions=()):
285 278

  
279
        if name is None:
280
            name = ""
286 281
        create = Commission.objects.create
287
        commission = create(holder=target, clientkey=clientkey, name=name)
282
        commission = create(clientkey=clientkey, name=name)
288 283
        serial = commission.serial
289 284

  
290 285
        operations = Operations()
291 286

  
292 287
        try:
293 288
            checked = []
294
            for holder, resource, quantity in provisions:
289
            for holder, source, resource, quantity in provisions:
295 290

  
296
                if holder == target:
291
                if holder == source:
297 292
                    m = ("Cannot issue commission from a holder "
298 293
                         "to itself (%s)" % (holder,))
299 294
                    raise InvalidDataError(m)
......
304 299
                    raise DuplicateError(m)
305 300
                checked.append(ent_res)
306 301

  
307
                # Source
308
                try:
309
                    h = (db_get_holding(holder=holder, resource=resource,
310
                                        for_update=True)
311
                         if holder is not None
312
                         else None)
313
                except Holding.DoesNotExist:
314
                    m = ("%s has no stock of %s." % (holder, resource))
315
                    raise NoStockError(m,
316
                                       holder=holder,
317
                                       resource=resource,
318
                                       requested=quantity,
319
                                       current=0,
320
                                       limit=0)
321

  
322 302
                # Target
323 303
                try:
324
                    th = db_get_holding(holder=target, resource=resource,
304
                    th = db_get_holding(holder=holder,
305
                                        resource=resource,
306
                                        source=source,
325 307
                                        for_update=True)
326 308
                except Holding.DoesNotExist:
327 309
                    m = ("There is no capacity "
328
                         "to allocate into in %s.%s" % (target, resource))
310
                         "to allocate into in %s.%s" % (holder, resource))
329 311
                    raise NoCapacityError(m,
330 312
                                          holder=holder,
331 313
                                          resource=resource,
......
334 316
                                          limit=0)
335 317

  
336 318
                if quantity >= 0:
337
                    if h is not None:
338
                        operations.prepare(Export, h, quantity)
339 319
                    operations.prepare(Import, th, quantity)
340 320

  
341 321
                else: # release
342 322
                    abs_quantity = -quantity
343

  
344
                    if h is not None:
345
                        operations.prepare(Reclaim, h, abs_quantity)
346 323
                    operations.prepare(Release, th, abs_quantity)
347 324

  
348 325
                Provision.objects.create(serial=commission,
349
                                         holder=holder,
350
                                         resource=resource,
326
                                         holding=th,
351 327
                                         quantity=quantity)
352 328

  
353 329
        except QuotaholderError:
......
357 333
        return serial
358 334

  
359 335
    def _log_provision(self,
360
                       commission, s_holding, t_holding,
361
                       provision, log_time, reason):
362

  
363
        if s_holding is not None:
364
            s_holder = s_holding.holder
365
            s_capacity = s_holding.capacity
366
            s_imported_min = s_holding.imported_min
367
            s_imported_max = s_holding.imported_max
368
            s_stock_min = s_holding.stock_min
369
            s_stock_max = s_holding.stock_max
370
        else:
371
            s_holder = None
372
            s_capacity = None
373
            s_imported_min = None
374
            s_imported_max = None
375
            s_stock_min = None
376
            s_stock_max = None
336
                       commission, provision, log_time, reason):
337

  
338
        holding = provision.holding
377 339

  
378 340
        kwargs = {
379 341
            'serial':              commission.serial,
380 342
            'name':                commission.name,
381
            'source':              s_holder,
382
            'target':              t_holding.holder,
383
            'resource':            provision.resource,
384
            'source_capacity':     s_capacity,
385
            'source_imported_min': s_imported_min,
386
            'source_imported_max': s_imported_max,
387
            'source_stock_min':    s_stock_min,
388
            'source_stock_max':    s_stock_max,
389
            'target_capacity':     t_holding.capacity,
390
            'target_imported_min': t_holding.imported_min,
391
            'target_imported_max': t_holding.imported_max,
392
            'target_stock_min':    t_holding.stock_min,
393
            'target_stock_max':    t_holding.stock_max,
343
            'holder':              holding.holder,
344
            'source':              holding.source,
345
            'resource':            holding.resource,
346
            'capacity':            holding.capacity,
347
            'imported_min':        holding.imported_min,
348
            'imported_max':        holding.imported_max,
394 349
            'delta_quantity':      provision.quantity,
395 350
            'issue_time':          commission.issue_time,
396 351
            'log_time':            log_time,
......
411 366
            except Commission.DoesNotExist:
412 367
                return
413 368

  
414
            t = c.holder
415

  
416 369
            operations = Operations()
417 370

  
418 371
            provisions = db_filter_provision(serial=serial, for_update=True)
419 372
            for pv in provisions:
420 373
                try:
421
                    h = (db_get_holding(holder=pv.holder,
422
                                        resource=pv.resource, for_update=True)
423
                         if pv.holder is not None
424
                         else None)
425
                    th = db_get_holding(holder=t, resource=pv.resource,
374
                    th = db_get_holding(id=pv.holding_id,
426 375
                                        for_update=True)
427 376
                except Holding.DoesNotExist:
428 377
                    m = "Corrupted provision"
......
431 380
                quantity = pv.quantity
432 381

  
433 382
                if quantity >= 0:
434
                    if h is not None:
435
                        operations.finalize(Export, h, quantity)
436 383
                    operations.finalize(Import, th, quantity)
437 384
                else: # release
438 385
                    abs_quantity = -quantity
439

  
440
                    if h is not None:
441
                        operations.finalize(Reclaim, h, abs_quantity)
442 386
                    operations.finalize(Release, th, abs_quantity)
443 387

  
444 388
                reason = 'ACCEPT:' + reason[-121:]
445
                self._log_provision(c, h, th, pv, log_time, reason)
389
                self._log_provision(c, pv, log_time, reason)
446 390
                pv.delete()
447 391
            c.delete()
448 392

  
......
460 404
            except Commission.DoesNotExist:
461 405
                return
462 406

  
463
            t = c.holder
464

  
465 407
            operations = Operations()
466 408

  
467 409
            provisions = db_filter_provision(serial=serial, for_update=True)
468 410
            for pv in provisions:
469 411
                try:
470
                    h = (db_get_holding(holder=pv.holder,
471
                                        resource=pv.resource, for_update=True)
472
                         if pv.holder is not None
473
                         else None)
474
                    th = db_get_holding(holder=t, resource=pv.resource,
412
                    th = db_get_holding(id=pv.holding_id,
475 413
                                        for_update=True)
476 414
                except Holding.DoesNotExist:
477 415
                    m = "Corrupted provision"
......
480 418
                quantity = pv.quantity
481 419

  
482 420
                if quantity >= 0:
483
                    if h is not None:
484
                        operations.undo(Export, h, quantity)
485 421
                    operations.undo(Import, th, quantity)
486 422
                else: # release
487 423
                    abs_quantity = -quantity
488

  
489
                    if h is not None:
490
                        operations.undo(Reclaim, h, abs_quantity)
491 424
                    operations.undo(Release, th, abs_quantity)
492 425

  
493 426
                reason = 'REJECT:' + reason[-121:]
494
                self._log_provision(c, h, th, pv, log_time, reason)
427
                self._log_provision(c, pv, log_time, reason)
495 428
                pv.delete()
496 429
            c.delete()
497 430

  
b/snf-astakos-app/astakos/quotaholder/commission.py
32 32
# or implied, of GRNET S.A.
33 33

  
34 34
from astakos.quotaholder.exception import (
35
    NoCapacityError, NoStockError,
36
    NonImportedError, NoStockReleaseError, NonExportedError)
35
    NoCapacityError, NonImportedError)
37 36

  
38 37

  
39 38
class Operation(object):
......
42 41
    def assertions(holding):
43 42
        assert(0 <= holding.imported_min)
44 43
        assert(holding.imported_min <= holding.imported_max)
45
        assert(0 <= holding.stock_min)
46
        assert(holding.stock_min <= holding.stock_max)
47 44

  
48 45
    @classmethod
49 46
    def _prepare(cls, holding, quantity, check=True):
......
98 95
    @classmethod
99 96
    def _finalize(cls, holding, quantity):
100 97
        holding.imported_min += quantity
101
        holding.stock_min += quantity
102
        holding.stock_max += quantity
103
        holding.save()
104

  
105

  
106
class Export(Operation):
107

  
108
    @classmethod
109
    def _prepare(cls, holding, quantity, check=True):
110
        stock_min = holding.stock_min
111
        new_stock_min = stock_min - quantity
112

  
113
        if check and new_stock_min < 0:
114
            holder = holding.holder
115
            resource = holding.resource
116
            m = ("%s has not enough stock of %s." % (holder, resource))
117
            raise NoStockError(m,
118
                               holder=holder,
119
                               resource=resource,
120
                               requested=quantity,
121
                               limit=stock_min)
122

  
123
        holding.stock_min = new_stock_min
124
        holding.save()
125

  
126
    @classmethod
127
    def _finalize(cls, holding, quantity):
128
        holding.stock_max -= quantity
129 98
        holding.save()
130 99

  
131 100

  
......
136 105
        imported_min = holding.imported_min
137 106
        new_imported_min = imported_min - quantity
138 107

  
139
        stock_min = holding.stock_min
140
        new_stock_min = stock_min - quantity
141
        stock_max = holding.stock_max
142
        new_stock_max = stock_max - quantity
143

  
144 108
        if check and new_imported_min < 0:
145 109
            holder = holding.holder
146 110
            resource = holding.resource
......
152 116
                                   requested=quantity,
153 117
                                   limit=imported_min)
154 118

  
155
        if check and new_stock_min < 0:
156
            holder = holding.holder
157
            resource = holding.resource
158
            m = ("%s attempts to release %s that has been reexported." %
159
                 (holder, resource))
160
            raise NoStockReleaseError(m,
161
                                      holder=holder,
162
                                      resource=resource,
163
                                      requested=quantity,
164
                                      limit=stock_min)
165

  
166 119
        holding.imported_min = new_imported_min
167
        holding.stock_min = new_stock_min
168
        holding.stock_max = new_stock_max
169 120
        holding.save()
170 121

  
171 122
    @classmethod
......
174 125
        holding.save()
175 126

  
176 127

  
177
class Reclaim(Operation):
178

  
179
    @classmethod
180
    def _prepare(cls, holding, quantity, check=True):
181
        stock_max = holding.stock_max
182
        new_stock_max = stock_max + quantity
183

  
184
        imported_min = holding.imported_min
185
        if check and new_stock_max > imported_min:
186
            holder = holding.holder
187
            resource = holding.resource
188
            m = ("%s attempts to reclaim %s not originating by itself." %
189
                 (holder, resource))
190
            raise NonExportedError(m,
191
                                   holder=holder,
192
                                   resource=resource,
193
                                   requested=quantity,
194
                                   current=stock_max,
195
                                   limit=imported_min)
196

  
197
        holding.stock_max = new_stock_max
198
        holding.save()
199

  
200
    @classmethod
201
    def _finalize(cls, holding, quantity):
202
        holding.stock_min += quantity
203
        holding.save()
204

  
205

  
206 128
class Operations(object):
207 129
    def __init__(self):
208 130
        self.operations = []
b/snf-astakos-app/astakos/quotaholder/exception.py
59 59
        CommissionException.__init__(self, *args, **kwargs)
60 60

  
61 61

  
62
class NoStockError(CommissionValueException):
63
    pass
64

  
65

  
66 62
class NoCapacityError(CommissionValueException):
67 63
    pass
68 64

  
......
71 67
    pass
72 68

  
73 69

  
74
class NoStockReleaseError(CommissionValueException):
75
    pass
76

  
77

  
78
class NonExportedError(CommissionValueException):
79
    pass
80

  
81

  
82 70
class DuplicateError(CommissionException):
83 71
    pass
b/snf-astakos-app/astakos/quotaholder/models.py
42 42
class Holding(Model):
43 43

  
44 44
    holder      =   CharField(max_length=4096, db_index=True)
45
    source      =   CharField(max_length=4096, null=True)
45 46
    resource    =   CharField(max_length=4096, null=False)
46 47

  
47 48
    capacity    =   intDecimalField()
......
49 50

  
50 51
    imported_min    =   intDecimalField(default=0)
51 52
    imported_max    =   intDecimalField(default=0)
52
    stock_min       =   intDecimalField(default=0)
53
    stock_max       =   intDecimalField(default=0)
54 53

  
55 54
    objects     =   ForUpdateManager()
56 55

  
57 56
    class Meta:
58
        unique_together = (('holder', 'resource'),)
57
        unique_together = (('holder', 'source', 'resource'),)
59 58

  
60 59

  
61 60
from datetime import datetime
......
67 66
class Commission(Model):
68 67

  
69 68
    serial      =   AutoField(primary_key=True)
70
    holder      =   CharField(max_length=4096, db_index=True)
71 69
    name        =   CharField(max_length=4096, null=True)
72 70
    clientkey   =   CharField(max_length=4096, null=False)
73 71
    issue_time  =   CharField(max_length=24, default=now)
......
79 77
    serial      =   ForeignKey( Commission,
80 78
                                to_field='serial',
81 79
                                related_name='provisions'   )
82

  
83
    holder      =   CharField(max_length=4096, db_index=True, null=True)
84
    resource    =   CharField(max_length=4096, null=False)
80
    holding     =   ForeignKey(Holding,
81
                               related_name='provisions')
85 82
    quantity    =   intDecimalField()
86 83

  
87 84
    objects     =   ForUpdateManager()
......
89 86
class ProvisionLog(Model):
90 87

  
91 88
    serial              =   BigIntegerField()
92
    source              =   CharField(max_length=4096, null=True)
93
    target              =   CharField(max_length=4096)
94
    name                =   CharField(max_length=4096)
89
    name                =   CharField(max_length=4096, null=True)
95 90
    issue_time          =   CharField(max_length=4096)
96 91
    log_time            =   CharField(max_length=4096)
92
    holder              =   CharField(max_length=4096)
93
    source              =   CharField(max_length=4096, null=True)
97 94
    resource            =   CharField(max_length=4096)
98
    source_capacity     =   intDecimalField(null=True)
99
    source_imported_min =   intDecimalField(null=True)
100
    source_imported_max =   intDecimalField(null=True)
101
    source_stock_min    =   intDecimalField(null=True)
102
    source_stock_max    =   intDecimalField(null=True)
103
    target_capacity     =   intDecimalField()
104
    target_imported_min =   intDecimalField()
105
    target_imported_max =   intDecimalField()
106
    target_stock_min    =   intDecimalField()
107
    target_stock_max    =   intDecimalField()
95
    capacity            =   intDecimalField()
96
    imported_min        =   intDecimalField()
97
    imported_max        =   intDecimalField()
108 98
    delta_quantity      =   intDecimalField()
109 99
    reason              =   CharField(max_length=4096)
110 100

  

Also available in: Unified diff