Revision d03796c2

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

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

  
40 43
from astakos.quotaholder.utils.newname import newname
41 44
from astakos.quotaholder.api import QH_PRACTICALLY_INFINITE
42 45

  
......
61 64
            except Policy.DoesNotExist:
62 65
                continue
63 66

  
64
            append((policy, p.quantity, p.capacity))
67
            append((policy, p.capacity))
65 68

  
66 69
        return limits
67 70

  
68 71
    def set_limits(self, context=None, set_limits=[]):
69 72

  
70
        for (policy, quantity, capacity) in set_limits:
73
        for (policy, capacity) in set_limits:
71 74

  
72 75
            try:
73 76
                policy = db_get_policy(policy=policy, for_update=True)
74 77
            except Policy.DoesNotExist:
75 78
                Policy.objects.create(policy=policy,
76
                                      quantity=quantity,
77 79
                                      capacity=capacity,
78 80
                                      )
79 81
            else:
80
                policy.quantity = quantity
81 82
                policy.capacity = capacity
82 83
                policy.save()
83 84

  
......
94 95
                continue
95 96

  
96 97
            append((h.holder, h.resource, h.policy.policy,
97
                    h.imported, h.exported,
98
                    h.returned, h.released, h.flags))
98
                    h.imported_min, h.imported_max,
99
                    h.stock_min, h.stock_max, h.flags))
99 100

  
100 101
        return holdings
101 102

  
......
126 127

  
127 128
    def _init_holding(self,
128 129
                      holder, resource, policy,
129
                      imported, exported, returned, released,
130
                      imported_min, imported_max, stock_min, stock_max,
130 131
                      flags):
131 132
        try:
132 133
            h = db_get_holding(holder=holder, resource=resource,
......
136 137

  
137 138
        h.policy = policy
138 139
        h.flags = flags
139
        h.imported = imported
140
        h.importing = imported
141
        h.exported = exported
142
        h.exporting = exported
143
        h.returned = returned
144
        h.returning = returned
145
        h.released = released
146
        h.releasing = released
140
        h.imported_min = imported_min
141
        h.imported_max = imported_max
142
        h.stock_min = stock_min
143
        h.stock_max = stock_max
147 144
        h.save()
148 145

  
149 146
    def init_holding(self, context=None, init_holding=[]):
......
152 149

  
153 150
        for idx, sfh in enumerate(init_holding):
154 151
            (holder, resource, policy,
155
             imported, exported, returned, released,
152
             imported_min, imported_max, stock_min, stock_max,
156 153
             flags) = sfh
157 154

  
158 155
            try:
......
162 159
                continue
163 160

  
164 161
            self._init_holding(holder, resource, p,
165
                               imported, exported,
166
                               returned, released,
162
                               imported_min, imported_max,
163
                               stock_min, stock_max,
167 164
                               flags)
168 165
        if rejected:
169 166
            raise QuotaholderError(rejected)
......
175 172

  
176 173
        for idx, tpl in enumerate(reset_holding):
177 174
            (holder, resource,
178
             imported, exported, returned, released) = tpl
175
             imported_min, imported_max, stock_min, stock_max) = tpl
179 176

  
180 177
            try:
181 178
                h = db_get_holding(holder=holder, resource=resource,
182 179
                                   for_update=True)
183
                h.imported = imported
184
                h.importing = imported
185
                h.exported = exported
186
                h.exporting = exported
187
                h.returned = returned
188
                h.returning = returned
189
                h.released = released
190
                h.releasing = released
180
                h.imported_min = imported_min
181
                h.imported_max = imported_max
182
                h.stock_min = stock_min
183
                h.stock_max = stock_max
191 184
                h.save()
192 185
            except Holding.DoesNotExist:
193 186
                append(idx)
......
207 200

  
208 201
        return as_target + as_source
209 202

  
210
    def _actual_quantity(self, holding):
211
        hp = holding.policy
212
        return hp.quantity + (holding.imported + holding.returned -
213
                              holding.exported - holding.released)
214

  
215 203
    def release_holding(self, context=None, release_holding=[]):
216 204
        rejected = []
217 205
        append = rejected.append
......
228 216
                append(idx)
229 217
                continue
230 218

  
231
            q = self._actual_quantity(h)
232
            if q > 0:
219
            if h.imported_max > 0:
233 220
                append(idx)
234 221
                continue
235 222

  
......
257 244
                continue
258 245

  
259 246
            append([(holder, h.resource,
260
                     h.imported, h.exported, h.returned, h.released)
247
                     h.imported_min, h.imported_max, h.stock_min, h.stock_max)
261 248
                    for h in holdings])
262 249

  
263 250
        return holdings_list, rejected
......
280 267

  
281 268
            p = h.policy
282 269

  
283
            append((h.holder, h.resource, p.quantity, p.capacity,
284
                    h.imported, h.exported,
285
                    h.returned, h.released,
270
            append((h.holder, h.resource, p.capacity,
271
                    h.imported_min, h.imported_max,
272
                    h.stock_min, h.stock_max,
286 273
                    h.flags))
287 274

  
288 275
        return quotas
......
293 280

  
294 281
        q_holdings = Q()
295 282
        holders = []
296
        for (holder, resource, _, _, _) in set_quota:
283
        for (holder, resource, _, _) in set_quota:
297 284
            holders.append(holder)
298 285

  
299 286
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
......
304 291
        old_policies = []
305 292

  
306 293
        for (holder, resource,
307
             quantity, capacity,
294
             capacity,
308 295
             flags) in set_quota:
309 296

  
310 297
            policy = newname('policy_')
311 298
            newp = Policy(policy=policy,
312
                          quantity=quantity,
313 299
                          capacity=capacity,
314 300
                          )
315 301

  
......
345 331
        sources = sub_quota + add_quota
346 332
        q_holdings = Q()
347 333
        holders = []
348
        for (holder, resource, _, _) in sources:
334
        for (holder, resource, _) in sources:
349 335
            holders.append(holder)
350 336

  
351 337
        hs = Holding.objects.filter(holder__in=holders).select_for_update()
......
360 346

  
361 347
        for removing, source in [(True, sub_quota), (False, add_quota)]:
362 348
            for (holder, resource,
363
                 quantity, capacity,
349
                 capacity,
364 350
                 ) in source:
365 351

  
366 352
                try:
......
381 367
                policy = newname('policy_')
382 368
                newp = Policy(policy=policy)
383 369

  
384
                newp.quantity = _add(p.quantity if p else 0, quantity,
385
                                     invert=removing)
386 370
                newp.capacity = _add(p.capacity if p else 0, capacity,
387 371
                                     invert=removing)
388 372

  
......
413 397
                         clientkey=None,
414 398
                         target=None,
415 399
                         name=None,
416
                         provisions=[]):
400
                         provisions=()):
417 401

  
418 402
        create = Commission.objects.create
419 403
        commission = create(holder=target, clientkey=clientkey, name=name)
420 404
        serial = commission.serial
421 405

  
422
        checked = []
423
        for holder, resource, quantity in provisions:
406
        operations = Operations()
424 407

  
425
            if holder == target:
426
                m = "Cannot issue commission from an holder to itself (%s)" % (
427
                    holder,)
428
                raise InvalidDataError(m)
429

  
430
            ent_res = holder, resource
431
            if ent_res in checked:
432
                m = "Duplicate provision for %s.%s" % ent_res
433
                raise DuplicateError(m)
434
            checked.append(ent_res)
408
        try:
409
            checked = []
410
            for holder, resource, quantity in provisions:
435 411

  
436
            release = 0
437
            if quantity < 0:
438
                release = 1
412
                if holder == target:
413
                    m = ("Cannot issue commission from a holder "
414
                         "to itself (%s)" % (holder,))
415
                    raise InvalidDataError(m)
439 416

  
440
            # Source limits checks
441
            try:
442
                h = db_get_holding(holder=holder, resource=resource,
443
                                   for_update=True)
444
            except Holding.DoesNotExist:
445
                m = ("There is no quantity "
446
                     "to allocate from in %s.%s" % (holder, resource))
447
                raise NoQuantityError(m,
448
                                      source=holder, target=target,
449
                                      resource=resource, requested=quantity,
450
                                      current=0, limit=0)
451

  
452
            hp = h.policy
453

  
454
            if not release:
455
                limit = hp.quantity + h.imported - h.releasing
456
                unavailable = h.exporting - h.returned
457
                available = limit - unavailable
458

  
459
                if quantity > available:
460
                    m = ("There is not enough quantity "
461
                         "to allocate from in %s.%s" % (holder, resource))
462
                    raise NoQuantityError(m,
463
                                          source=holder,
464
                                          target=target,
465
                                          resource=resource,
466
                                          requested=quantity,
467
                                          current=unavailable,
468
                                          limit=limit)
469
            else:
470
                current = (+ h.importing + h.returning
471
                           - h.exported - h.returned)
472
                limit = hp.capacity
473
                if current - quantity > limit:
474
                    m = ("There is not enough capacity "
475
                         "to release to in %s.%s" % (holder, resource))
476
                    raise NoQuantityError(m,
477
                                          source=holder,
478
                                          target=target,
479
                                          resource=resource,
480
                                          requested=quantity,
481
                                          current=current,
482
                                          limit=limit)
417
                ent_res = holder, resource
418
                if ent_res in checked:
419
                    m = "Duplicate provision for %s.%s" % ent_res
420
                    raise DuplicateError(m)
421
                checked.append(ent_res)
483 422

  
484
            # Target limits checks
485
            try:
486
                th = db_get_holding(holder=target, resource=resource,
487
                                    for_update=True)
488
            except Holding.DoesNotExist:
489
                m = ("There is no capacity "
490
                     "to allocate into in %s.%s" % (target, resource))
491
                raise NoCapacityError(m,
492
                                      source=holder,
493
                                      target=target,
494
                                      resource=resource,
495
                                      requested=quantity,
496
                                      current=0,
497
                                      limit=0)
498

  
499
            tp = th.policy
500

  
501
            if not release:
502
                limit = tp.quantity + tp.capacity
503
                current = (+ th.importing + th.returning + tp.quantity
504
                           - th.exported - th.released)
505

  
506
                if current + quantity > limit:
507
                    m = ("There is not enough capacity "
423
                # Source
424
                try:
425
                    h = db_get_holding(holder=holder, resource=resource,
426
                                       for_update=True)
427
                except Holding.DoesNotExist:
428
                    m = ("%s has no stock of %s." % (holder, resource))
429
                    raise NoStockError(m,
430
                                       holder=holder,
431
                                       resource=resource,
432
                                       requested=quantity,
433
                                       current=0,
434
                                       limit=0)
435

  
436
                # Target
437
                try:
438
                    th = db_get_holding(holder=target, resource=resource,
439
                                        for_update=True)
440
                except Holding.DoesNotExist:
441
                    m = ("There is no capacity "
508 442
                         "to allocate into in %s.%s" % (target, resource))
509 443
                    raise NoCapacityError(m,
510
                                          source=holder,
511
                                          target=target,
444
                                          holder=holder,
512 445
                                          resource=resource,
513 446
                                          requested=quantity,
514
                                          current=current,
515
                                          limit=limit)
516
            else:
517
                limit = tp.quantity + th.imported - th.releasing
518
                unavailable = th.exporting - th.returned
519
                available = limit - unavailable
447
                                          current=0,
448
                                          limit=0)
520 449

  
521
                if available + quantity < 0:
522
                    m = ("There is not enough quantity "
523
                         "to release from in %s.%s" % (target, resource))
524
                    raise NoCapacityError(m,
525
                                          source=holder,
526
                                          target=target,
527
                                          resource=resource,
528
                                          requested=quantity,
529
                                          current=unavailable,
530
                                          limit=limit)
531

  
532
            Provision.objects.create(serial=commission,
533
                                     holder=holder,
534
                                     resource=resource,
535
                                     quantity=quantity)
536
            if release:
537
                h.returning -= quantity
538
                th.releasing -= quantity
539
            else:
540
                h.exporting += quantity
541
                th.importing += quantity
450
                if quantity >= 0:
451
                    operations.prepare(Export, h, quantity)
452
                    operations.prepare(Import, th, quantity)
542 453

  
543
            h.save()
544
            th.save()
454
                else: # release
455
                    abs_quantity = -quantity
456

  
457
                    operations.prepare(Reclaim, h, abs_quantity)
458
                    operations.prepare(Release, th, abs_quantity)
459

  
460
                Provision.objects.create(serial=commission,
461
                                         holder=holder,
462
                                         resource=resource,
463
                                         quantity=quantity)
464

  
465
        except QuotaholderError:
466
            operations.revert()
467
            raise
545 468

  
546 469
        return serial
547 470

  
......
560 483
            'source':              s_holder,
561 484
            'target':              t_holder,
562 485
            'resource':            provision.resource,
563
            'source_quantity':     s_policy.quantity,
564 486
            'source_capacity':     s_policy.capacity,
565
            'source_imported':     s_holding.imported,
566
            'source_exported':     s_holding.exported,
567
            'source_returned':     s_holding.returned,
568
            'source_released':     s_holding.released,
569
            'target_quantity':     t_policy.quantity,
487
            'source_imported_min': s_holding.imported_min,
488
            'source_imported_max': s_holding.imported_max,
489
            'source_stock_min':    s_holding.stock_min,
490
            'source_stock_max':    s_holding.stock_max,
570 491
            'target_capacity':     t_policy.capacity,
571
            'target_imported':     t_holding.imported,
572
            'target_exported':     t_holding.exported,
573
            'target_returned':     t_holding.returned,
574
            'target_released':     t_holding.released,
492
            'target_imported_min': t_holding.imported_min,
493
            'target_imported_max': t_holding.imported_max,
494
            'target_stock_min':    t_holding.stock_min,
495
            'target_stock_max':    t_holding.stock_max,
575 496
            'delta_quantity':      provision.quantity,
576 497
            'issue_time':          commission.issue_time,
577 498
            'log_time':            log_time,
......
594 515

  
595 516
            t = c.holder
596 517

  
518
            operations = Operations()
519

  
597 520
            provisions = db_filter_provision(serial=serial, for_update=True)
598 521
            for pv in provisions:
599 522
                try:
......
606 529
                    raise CorruptedError(m)
607 530

  
608 531
                quantity = pv.quantity
609
                release = 0
610
                if quantity < 0:
611
                    release = 1
612 532

  
613
                if release:
614
                    h.returned -= quantity
615
                    th.released -= quantity
616
                else:
617
                    h.exported += quantity
618
                    th.imported += quantity
533
                if quantity >= 0:
534
                    operations.finalize(Export, h, quantity)
535
                    operations.finalize(Import, th, quantity)
536
                else: # release
537
                    abs_quantity = -quantity
538

  
539
                    operations.finalize(Reclaim, h, abs_quantity)
540
                    operations.finalize(Release, th, abs_quantity)
619 541

  
620 542
                reason = 'ACCEPT:' + reason[-121:]
621 543
                self._log_provision(c, h, th, pv, log_time, reason)
622
                h.save()
623
                th.save()
624 544
                pv.delete()
625 545
            c.delete()
626 546

  
......
640 560

  
641 561
            t = c.holder
642 562

  
563
            operations = Operations()
564

  
643 565
            provisions = db_filter_provision(serial=serial, for_update=True)
644 566
            for pv in provisions:
645 567
                try:
......
652 574
                    raise CorruptedError(m)
653 575

  
654 576
                quantity = pv.quantity
655
                release = 0
656
                if quantity < 0:
657
                    release = 1
658 577

  
659
                if release:
660
                    h.returning += quantity
661
                    th.releasing += quantity
662
                else:
663
                    h.exporting -= quantity
664
                    th.importing -= quantity
578
                if quantity >= 0:
579
                    operations.undo(Export, h, quantity)
580
                    operations.undo(Import, th, quantity)
581
                else: # release
582
                    abs_quantity = -quantity
583

  
584
                    operations.undo(Reclaim, h, abs_quantity)
585
                    operations.undo(Release, th, abs_quantity)
665 586

  
666 587
                reason = 'REJECT:' + reason[-121:]
667 588
                self._log_provision(c, h, th, pv, log_time, reason)
668
                h.save()
669
                th.save()
670 589
                pv.delete()
671 590
            c.delete()
672 591

  
b/snf-astakos-app/astakos/quotaholder/commission.py
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

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

  
38

  
39
class Operation(object):
40

  
41
    @staticmethod
42
    def assertions(holding):
43
        assert(0 <= holding.imported_min)
44
        assert(holding.imported_min <= holding.imported_max)
45
        assert(0 <= holding.stock_min)
46
        assert(holding.stock_min <= holding.stock_max)
47

  
48
    @classmethod
49
    def _prepare(cls, holding, quantity, check=True):
50
        raise NotImplementedError
51

  
52
    @classmethod
53
    def prepare(cls, holding, quantity, check=True):
54
        cls.assertions(holding)
55
        cls._prepare(holding, quantity, check=True)
56

  
57
    @classmethod
58
    def _finalize(cls, holding, quantity):
59
        raise NotImplementedError
60

  
61
    @classmethod
62
    def finalize(cls, holding, quantity):
63
        cls.assertions(holding)
64
        cls._finalize(holding, quantity)
65

  
66
    @classmethod
67
    def undo(cls, holding, quantity):
68
        cls.prepare(holding, -quantity, check=False)
69

  
70
    @classmethod
71
    def revert(cls, holding, quantity):
72
        # Assertions do not hold when reverting
73
        cls._prepare(holding, -quantity, check=False)
74

  
75

  
76
class Import(Operation):
77

  
78
    @classmethod
79
    def _prepare(cls, holding, quantity, check=True):
80
        imported_max = holding.imported_max
81
        new_imported_max = imported_max + quantity
82

  
83
        capacity = holding.policy.capacity
84
        if check and new_imported_max > capacity:
85
            holder = holding.holder
86
            resource = holding.resource
87
            m = ("%s has not enough capacity of %s." % (holder, resource))
88
            raise NoCapacityError(m,
89
                                  holder=holder,
90
                                  resource=resource,
91
                                  requested=quantity,
92
                                  current=imported_max,
93
                                  limit=capacity)
94

  
95
        holding.imported_max = new_imported_max
96
        holding.save()
97

  
98
    @classmethod
99
    def _finalize(cls, holding, quantity):
100
        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
        holding.save()
130

  
131

  
132
class Release(Operation):
133

  
134
    @classmethod
135
    def _prepare(cls, holding, quantity, check=True):
136
        imported_min = holding.imported_min
137
        new_imported_min = imported_min - quantity
138

  
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
        if check and new_imported_min < 0:
145
            holder = holding.holder
146
            resource = holding.resource
147
            m = ("%s attempts to release more %s than it contains." %
148
                 (holder, resource))
149
            raise NonImportedError(m,
150
                                   holder=holder,
151
                                   resource=resource,
152
                                   requested=quantity,
153
                                   limit=imported_min)
154

  
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
        holding.imported_min = new_imported_min
167
        holding.stock_min = new_stock_min
168
        holding.stock_max = new_stock_max
169
        holding.save()
170

  
171
    @classmethod
172
    def _finalize(cls, holding, quantity):
173
        holding.imported_max -= quantity
174
        holding.save()
175

  
176

  
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
class Operations(object):
207
    def __init__(self):
208
        self.operations = []
209

  
210
    def prepare(self, operation, holding, quantity):
211
        operation.prepare(holding, quantity)
212
        self.operations.append((operation, holding, quantity))
213

  
214
    def finalize(self, operation, holding, quantity):
215
        operation.finalize(holding, quantity)
216
        self.operations.append((operation, holding, quantity))
217

  
218
    def undo(self, operation, holding, quantity):
219
        operation.undo(holding, quantity)
220
        self.operations.append((operation, holding, quantity))
221

  
222
    def revert(self):
223
        for (operation, holding, quantity) in self.operations:
224
            operation.revert(holding, quantity)
b/snf-astakos-app/astakos/quotaholder/exception.py
51 51
class CommissionValueException(CommissionException):
52 52
    def __init__(self, *args, **kwargs):
53 53

  
54
        self.source    = kwargs['source']
55
        self.target    = kwargs['target']
56
        self.resource  = kwargs['resource']
57
        self.requested = kwargs['requested']
58
        self.current   = kwargs['current']
59
        self.limit     = kwargs['limit']
54
        self.holder    = kwargs.pop('holder', None)
55
        self.resource  = kwargs.pop('resource', None)
56
        self.requested = kwargs.pop('requested', None)
57
        self.current   = kwargs.pop('current', None)
58
        self.limit     = kwargs.pop('limit', None)
59
        CommissionException.__init__(self, *args, **kwargs)
60 60

  
61 61

  
62
class NoQuantityError(CommissionValueException):
62
class NoStockError(CommissionValueException):
63 63
    pass
64 64

  
65 65

  
......
67 67
    pass
68 68

  
69 69

  
70
class NonImportedError(CommissionValueException):
71
    pass
72

  
73

  
74
class NoStockReleaseError(CommissionValueException):
75
    pass
76

  
77

  
78
class NonExportedError(CommissionValueException):
79
    pass
80

  
81

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

  
44 44
    policy          =   CharField(max_length=4096, primary_key=True)
45
    quantity        =   intDecimalField()
46 45
    capacity        =   intDecimalField()
47 46

  
48 47
    objects     =   ForUpdateManager()
......
55 54
    policy      =   ForeignKey(Policy, to_field='policy')
56 55
    flags       =   BigIntegerField(null=False, default=0)
57 56

  
58
    imported    =   intDecimalField(default=0)
59
    importing   =   intDecimalField(default=0)
60
    exported    =   intDecimalField(default=0)
61
    exporting   =   intDecimalField(default=0)
62
    returned    =   intDecimalField(default=0)
63
    returning   =   intDecimalField(default=0)
64
    released    =   intDecimalField(default=0)
65
    releasing   =   intDecimalField(default=0)
57
    imported_min    =   intDecimalField(default=0)
58
    imported_max    =   intDecimalField(default=0)
59
    stock_min       =   intDecimalField(default=0)
60
    stock_max       =   intDecimalField(default=0)
66 61

  
67 62
    objects     =   ForUpdateManager()
68 63

  
......
107 102
    issue_time          =   CharField(max_length=4096)
108 103
    log_time            =   CharField(max_length=4096)
109 104
    resource            =   CharField(max_length=4096)
110
    source_quantity     =   intDecimalField()
111 105
    source_capacity     =   intDecimalField()
112
    source_imported     =   intDecimalField()
113
    source_exported     =   intDecimalField()
114
    source_returned     =   intDecimalField()
115
    source_released     =   intDecimalField()
116
    target_quantity     =   intDecimalField()
106
    source_imported_min =   intDecimalField()
107
    source_imported_max =   intDecimalField()
108
    source_stock_min    =   intDecimalField()
109
    source_stock_max    =   intDecimalField()
117 110
    target_capacity     =   intDecimalField()
118
    target_imported     =   intDecimalField()
119
    target_exported     =   intDecimalField()
120
    target_returned     =   intDecimalField()
121
    target_released     =   intDecimalField()
111
    target_imported_min =   intDecimalField()
112
    target_imported_max =   intDecimalField()
113
    target_stock_min    =   intDecimalField()
114
    target_stock_max    =   intDecimalField()
122 115
    delta_quantity      =   intDecimalField()
123 116
    reason              =   CharField(max_length=4096)
124 117

  
125 118
    objects     =   ForUpdateManager()
126 119

  
127
    def source_allocated_through(self):
128
        return self.source_imported - self.source_released
129

  
130
    def source_allocated(self):
131
        return (+ self.source_allocated_through()
132
                - self.source_exported
133
                + self.source_returned)
134

  
135
    def source_inbound_through(self):
136
        return self.source_imported
137

  
138
    def source_inbound(self):
139
        return self.source_inbound_through() + self.source_returned
140

  
141
    def source_outbound_through(self):
142
        return self.source_released
143

  
144
    def source_outbound(self):
145
        return self.source_outbound_through() + self.source_exported
146

  
147
    def target_allocated_through(self):
148
        return self.target_imported - self.target_released
149

  
150
    def target_allocated(self):
151
        return (+ self.target_allocated_through()
152
                - self.target_exported
153
                + self.target_returned)
154

  
155
    def target_inbound_through(self):
156
        return self.target_imported
157

  
158
    def target_inbound(self):
159
        return self.target_inbound_through() + self.target_returned
160

  
161
    def target_outbound_through(self):
162
        return self.target_released
163

  
164
    def target_outbound(self):
165
        return self.target_outbound_through() + self.target_exported
166

  
167 120

  
168 121
def _get(*args, **kwargs):
169 122
    model = args[0]
b/snf-astakos-app/astakos/quotaholder/test/simpletests.py
38 38
from astakos.quotaholder.exception import (
39 39
                            QuotaholderError,
40 40
                            InvalidDataError,
41
                            NoQuantityError, NoCapacityError,
41
                            NoCapacityError,
42
                            NoStockError,
43
                            NonExportedError,
42 44
                            CommissionValueException,
43 45
                            DuplicateError)
44 46

  
......
84 86
        return self.rand_name(self.used_resources)
85 87

  
86 88
    def rand_limits(self):
87
        q = random_nat()
88 89
        c = random_nat()
89
        return q, c,
90
        return (c,)
90 91

  
91 92
    def rand_policy_limits(self):
92 93
        p = self.rand_policy()
......
180 181
        resource1 = self.rand_resource()
181 182

  
182 183
        self.qh.set_quota(
183
            set_quota=[(e0, resource0) + (5, QH_PRACTICALLY_INFINITE) + (0,),
184
                       (e1, resource0) + (5, 5) + (0,)])
184
            set_quota=[(e0, resource0) + (QH_PRACTICALLY_INFINITE, 0),
185
                       (e1, resource0) + (5, 0)])
185 186

  
186 187
        self.qh.add_quota(sub_quota=[(e0, resource0,
187
                                      0, QH_PRACTICALLY_INFINITE)],
188
                                      QH_PRACTICALLY_INFINITE)],
188 189
                          add_quota=[(e0, resource0,
189
                                      0, 3),
190
                                      3),
190 191
                                     # new holding
191 192
                                     (e0, resource1,
192
                                      0, QH_PRACTICALLY_INFINITE)])
193
                                      QH_PRACTICALLY_INFINITE)])
193 194

  
194 195
        r = self.qh.get_quota(get_quota=[(e0, resource0),
195 196
                                         (e0, resource1)])
196
        self.assertEqual(r, [(e0, resource0, 5, 3)
197
        self.assertEqual(r, [(e0, resource0, 3)
197 198
                             + DEFAULT_HOLDING + (0,),
198
                             (e0, resource1, 0, QH_PRACTICALLY_INFINITE)
199
                             (e0, resource1, QH_PRACTICALLY_INFINITE)
199 200
                             + DEFAULT_HOLDING + (0,)])
200 201

  
201 202
        with self.assertRaises(QuotaholderError) as cm:
202 203
            self.qh.add_quota(add_quota=[(e1, resource0,
203
                                          0, (-10)),
204
                                         (e0, resource1, 1, 0)])
204
                                          (-10)),
205
                                         (e0, resource1, 0)])
205 206

  
206 207
        err = cm.exception
207 208
        self.assertEqual(err.message, [(e1, resource0)])
......
222 223

  
223 224
        self.qh.set_quota(
224 225
            set_quota=[(e0, resource0) +
225
                       (5, QH_PRACTICALLY_INFINITE) + (0,)])
226
                       (QH_PRACTICALLY_INFINITE,) + (0,)])
226 227

  
227 228
        self.qh.add_quota(add_quota=[(e0, resource0,
228
                                      0, QH_PRACTICALLY_INFINITE)])
229
                                      QH_PRACTICALLY_INFINITE)])
229 230

  
230 231
        r = self.qh.get_quota(get_quota=[(e0, resource0)])
231
        self.assertEqual(r, [(e0, resource0, 5, 2*QH_PRACTICALLY_INFINITE)
232
        self.assertEqual(r, [(e0, resource0, 2*QH_PRACTICALLY_INFINITE)
232 233
                             + DEFAULT_HOLDING + (0,)])
233 234

  
234 235
    @transaction.commit_on_success
......
236 237
        e0 = self.rand_holder()
237 238
        e1 = self.rand_holder()
238 239
        resource = self.rand_resource()
239
        q0, c0 = self.new_quota(e0, resource)
240
        q1, c1 = self.new_quota(e1, resource)
241

  
242
        most = min(c0, q1)
243
        if most < 0:
244
            raise AssertionError("%s <= 0" % most)
240
        c0, = self.new_quota(e0, resource)
241
        c1, = self.new_quota(e1, resource)
245 242

  
246 243
        @transaction.commit_on_success
247 244
        def f():
245
            self.qh.reset_holding(
246
                reset_holding=[(e1, resource, c1, c1, c1, c1)])
247

  
248
            most = min(c0, c1)
249
            if most < 0:
250
                raise AssertionError("%s <= 0" % most)
251

  
248 252
            return self.qh.issue_commission(clientkey=self.client, target=e0,
249 253
                                            name='something',
250 254
                                            provisions=[(e1, resource, most)])
251 255

  
252 256
        r = f()
257

  
253 258
        self.assertEqual(r, 1)
254 259

  
255 260
        @transaction.commit_on_success
......
275 280
        et1 = self.rand_holder()
276 281
        et2 = self.rand_holder()
277 282
        resource = self.rand_resource()
278
        self.new_quota(es1, resource, (10, 5))
279
        self.new_quota(es2, resource, (10, 5))
280
        self.new_quota(et1, resource, (0, 15))
281
        self.new_quota(et2, resource, (0, 15))
283
        self.new_quota(es1, resource, (10,))
284
        self.new_quota(es2, resource, (10,))
285
        self.new_quota(et1, resource, (15,))
286
        self.new_quota(et2, resource, (15,))
282 287

  
283
        with self.assertRaises(NoQuantityError) as cm:
288
        self.qh.reset_holding(
289
            reset_holding=[(es1, resource, 10, 10, 10, 10),
290
                           (es2, resource, 10, 10, 10, 10)])
291

  
292
        with self.assertRaises(NoStockError) as cm:
284 293
            self.qh.issue_commission(clientkey=self.client, target=et1,
285 294
                                     name='something',
286 295
                                     provisions=[(es1, resource, 12)])
287 296
        e = cm.exception
288
        self.assertEqual(e.source, es1)
289
        self.assertEqual(e.target, et1)
297
        self.assertEqual(e.holder, es1)
290 298
        self.assertEqual(e.resource, resource)
291 299
        self.assertEqual(int(e.limit), 10)
292 300
        self.assertEqual(int(e.requested), 12)
293
        self.assertEqual(int(e.current), 0)
294 301

  
295 302
        r = self.qh.issue_commission(clientkey=self.client, target=et1,
296 303
                                     name='something',
297 304
                                     provisions=[(es1, resource, 2)])
298 305
        self.assertGreater(r, 0)
299 306

  
300
        # with self.assertRaises(ImportLimitError) as cm:
301
        #     self.qh.issue_commission(clientkey=self.client, target=et1,
302
        #                              name='something',
303
        #                              provisions=[(es1, resource, 2)])
304
        # e = cm.exception
305
        # self.assertEqual(e.source, es1)
306
        # self.assertEqual(e.target, et1)
307
        # self.assertEqual(e.resource, resource)
308
        # self.assertEqual(int(e.limit), 3)
309
        # self.assertEqual(int(e.requested), 2)
310
        # self.assertEqual(int(e.current), 2)
307

  
311 308

  
312 309
        r = self.qh.issue_commission(clientkey=self.client, target=et2,
313 310
                                     name='something',
......
320 317
                                     provisions=[(es2, resource, 1),
321 318
                                                 (es1, resource, 6)])
322 319
        e = cm.exception
323
        self.assertEqual(e.source, es1)
324
        self.assertEqual(e.target, et2)
320
        self.assertEqual(e.holder, et2)
325 321
        self.assertEqual(e.resource, resource)
326 322
        self.assertEqual(int(e.limit), 15)
327 323
        self.assertEqual(int(e.requested), 6)
......
335 331
        resource = 'list_holdings_resource'
336 332
        sys = 'system'
337 333

  
338
        self.qh.set_quota(set_quota=[(sys, resource, 10, 0, 0),
339
                                     (e0, resource, 0, 10, 0),
340
                                     (e1, resource, 0, 10, 0)])
334
        self.qh.set_quota(set_quota=[(sys, resource, 10, 0),
335
                                     (e0, resource, 10, 0),
336
                                     (e1, resource, 10, 0)])
337

  
338
        self.qh.reset_holding(
339
            reset_holding=[(sys, resource, 10, 10, 10, 10)])
341 340

  
342 341
        s0 = self.qh.issue_commission(clientkey=self.client, target=e0,
343 342
                                      name='a commission',
......
347 346
                                      name='a commission',
348 347
                                      provisions=[('system', resource, 4)])
349 348

  
349
        holdings_list, rejected = self.qh.list_holdings(
350
            list_holdings=[e0, e1, e0+e1])
351

  
352
        self.assertEqual(rejected, [e0+e1])
353
        self.assertEqual(holdings_list, [[(e0, resource, 0, 3, 0, 0)],
354
                                         [(e1, resource, 0, 4, 0, 0)]])
355

  
350 356
        self.qh.accept_commission(clientkey=self.client, serials=[s0, s1])
351 357

  
352 358
        holdings_list, rejected = self.qh.list_holdings(
353 359
            list_holdings=[e0, e1, e0+e1])
354 360

  
355 361
        self.assertEqual(rejected, [e0+e1])
356
        self.assertEqual(holdings_list, [[(e0, resource, 3, 0, 0, 0)],
357
                                         [(e1, resource, 4, 0, 0, 0)]])
362
        self.assertEqual(holdings_list, [[(e0, resource, 3, 3, 3, 3)],
363
                                         [(e1, resource, 4, 4, 4, 4)]])
358 364

  
359 365

  
360 366
    @transaction.commit_on_success
361 367
    def test_0130_release_holding(self):
362 368
        e = self.rand_holder()
363 369
        resource = self.rand_resource()
364
        limits = self.new_quota(e, resource, (1, 2))
370
        limits = self.new_quota(e, resource, (2,))
371
        self.qh.reset_holding(
372
            reset_holding=[(e, resource, 1, 1, 1, 1)])
365 373

  
366 374
        with self.assertRaises(QuotaholderError) as cm:
367 375
            self.qh.release_holding(release_holding=[(e, resource)])
......
373 381
    def test_0131_release_holding(self):
374 382
        e = self.rand_holder()
375 383
        resource = self.rand_resource()
376
        limits = self.new_quota(e, resource, (0, 2))
384
        limits = self.new_quota(e, resource, (2,))
377 385

  
378 386
        self.qh.release_holding(release_holding=[(e, resource)])
379 387

  
......
382 390
        resource = self.rand_resource()
383 391

  
384 392
        es = self.rand_holder()
385
        limits_s = self.new_quota(es, resource, (3, 3))
393
        limits_s = self.new_quota(es, resource, (3,))
394

  
395
        self.qh.reset_holding(
396
            reset_holding=[(es, resource, 3, 3, 3, 3)])
397

  
386 398
        e = self.rand_holder()
387
        limits = self.new_quota(e, resource, (0, 2))
399
        limits = self.new_quota(e, resource, (2,))
388 400

  
389 401
        r = self.qh.issue_commission(clientkey=self.client, target=e,
390 402
                                     name='something',
......
427 439
        resource = "resource"
428 440
        target = "test_015_release_nocapacity_target"
429 441
        flags = 0
430
        source_limits  = [source, 6, 0]
442
        source_limits  = [source, 6]
431 443
        source_holding = [source, resource, source, flags]
432
        target_limits  = [target, 0, 5]
444
        target_limits  = [target, 5]
433 445
        target_holding = [target, resource, target, flags]
434 446

  
435 447
        failed = AssertionError("Quotaholder call failed")
......
438 450
        if qh.set_holding(set_holding=[source_holding, target_holding]):
439 451
            raise failed
440 452

  
453
        self.qh.reset_holding(
454
            reset_holding=[(source, resource, 6, 6, 6, 6)])
455

  
441 456
        serial = qh.issue_commission(clientkey=self.client, target=target,
442 457
                                     name="something",
443 458
                                     provisions=[(source, resource, 5)])
......
445 460

  
446 461
        holding = qh.get_holding(get_holding=[[source, resource]])
447 462
        self.assertEqual(tuple(holding[0]),
448
                         (source, resource, source, 0, 5, 0, 0, flags))
463
                         (source, resource, source, 6, 6, 1, 1, flags))
449 464
        holding = qh.get_holding(get_holding=[[target, resource]])
450 465
        self.assertEqual(tuple(holding[0]),
451
                         (target, resource, target, 5, 0, 0, 0, flags))
466
                         (target, resource, target, 5, 5, 5, 5, flags))
452 467

  
453
        if qh.reset_holding(reset_holding=[[target, resource, 10, 0, 0, 0]]):
468
        if qh.reset_holding(
469
            reset_holding=[[target, resource, 10, 10, 10, 10]]):
454 470
            raise failed
455 471

  
456 472
        with self.assertRaises(NoCapacityError):
......
458 474
                                name="something",
459 475
                                provisions=[(source, resource, 1)])
460 476

  
461
        with self.assertRaises(NoQuantityError):
477
        with self.assertRaises(NonExportedError):
462 478
            qh.issue_commission(clientkey=self.client, target=target,
463 479
                                name="something",
464 480
                                provisions=[(source, resource, -7)])
465 481

  
466
        source_limits  = [source, 6, 10]
467
        if qh.set_limits(set_limits=[source_limits]):
468
            raise failed
469

  
470 482
        serial = qh.issue_commission(clientkey=self.client, target=target,
471 483
                                     name="something",
472 484
                                     provisions=[(source, resource, -1)])
......
474 486

  
475 487
        holding = qh.get_holding(get_holding=[[source, resource]])
476 488
        self.assertEqual(tuple(holding[0]),
477
                         (source, resource, source, 0, 5, 1, 0, flags))
489
                         (source, resource, source, 6, 6, 2, 2, flags))
478 490
        holding = qh.get_holding(get_holding=[[target, resource]])
479 491
        self.assertEqual(tuple(holding[0]),
480
                         (target, resource, target, 10, 0, 0, 1, flags))
492
                         (target, resource, target, 9, 9, 9, 9, flags))
481 493

  
482
        with self.assertRaises(NoCapacityError):
494
        with self.assertRaises(NonExportedError):
483 495
            qh.issue_commission(clientkey=self.client, target=target,
484 496
                                name="something",
485 497
                                provisions=[(source, resource, -10)])

Also available in: Unified diff