testBPMEvaluation.py 31.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
# -*- coding: utf-8 -*-
##############################################################################
# Copyright (c) 2009 Nexedi SA and Contributors. All Rights Reserved.
#          Łukasz Nowak <luke@nexedi.com>
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
##############################################################################
"""
This is BPM Evaluation Test class using erp5_bpm development Business Template

Generally it tries to use two Business Processes - one with sequence very
similar to normal ERP5 - TestBPMEvaluationDefaultProcessMixin, second one
inverted - TestBPMEvaluationDifferentProcessMixin.

It uses only Sale path to demonstrate BPM.

It is advised to *NOT* remove erp5_administration.
"""
import unittest
40
import transaction
41 42

from Products.ERP5.tests.testBPMCore import TestBPMMixin
43

44 45 46 47 48 49
from DateTime import DateTime

class TestBPMEvaluationMixin(TestBPMMixin):
  node_portal_type = 'Organisation'
  order_portal_type = 'Sale Order'
  order_line_portal_type = 'Sale Order Line'
50 51
  packing_list_portal_type = 'Sale Packing List'
  packing_list_line_portal_type = 'Sale Packing List Line'
52
  trade_condition_portal_type = 'Sale Trade Condition'
53
  invoice_portal_type = 'Sale Invoice Transaction'
54 55 56 57 58 59
  product_portal_type = 'Product'
  order_start_date = DateTime()
  order_stop_date = order_start_date + 10

  def getBusinessTemplateList(self):
    return TestBPMMixin.getBusinessTemplateList(self) + ('erp5_bpm',
60
        'erp5_administration', 'erp5_simulation',)
61 62 63 64 65 66

  def afterSetUp(self):
    TestBPMMixin.afterSetUp(self)
    self._createNodes()
    self._createBusinessProcess()
    self._createTradeCondition()
67
    self._createRootDocument()
68
    self._setUpRules()
69 70
    self.stepTic()

71 72 73 74 75 76 77 78 79 80 81 82
  def _setUpRules(self):
    """Setups rules

    Rules are part of configuration, so anything provided by Business
    Templates or previous test runs is ignored - all old rules are invalidated
    between tests and new rules are created, configured and validated.
    """
    self.rule_tool = self.portal.portal_rules
    for rule in self.rule_tool.contentValues():
      if rule.getValidationState() == 'validated':
        rule.invalidate()
    transaction.commit()
83
    self._createOrderRootSimulationRule()
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
84
    self._createDeliveryRootSimulationRule()
85 86 87 88 89 90
    self._createDeliverySimulationRule()
    self._createInvoiceSimulationRule()
    self._createInvoiceRootSimulationRule()
    self._createTradeModelSimulationRule()

  def _createTradeRootSimulationRule(self, **kw):
91 92 93 94 95 96
    edit_dict = {}
    edit_dict.update(
      trade_phase = 'default/delivery',
    )
    edit_dict.update(**kw)
    rule = self.rule_tool.newContent(**edit_dict)
97 98

    # matching providers
99
    for category in ('delivery',):
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=False,
        matching_provider=True)

    # divergence providers
    for category in ('source_section',
                     'resource',
                     'destination_section',
                     'source',
                     'aggregate'):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Net Converted Quantity Divergence Tester',
      title='quantity divergence tester',
      tested_property='quantity',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
    for property_id in ('start_date', 'stop_date'):
      rule.newContent(
        portal_type='DateTime Divergence Tester',
        title='%s divergence tester' % property_id,
        tested_property=property_id,
        quantity=0,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Float Divergence Tester',
      title='price divergence tester',
      tested_property='price',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
141 142 143

    return rule

144 145
  def _createOrderRootSimulationRule(self):
    rule = self._createTradeRootSimulationRule(portal_type='Order Root Simulation Rule',
146
        reference='default_order_rule')
147 148 149
    rule.validate()
    transaction.commit()

150 151
  def _createDeliveryRootSimulationRule(self):
    rule = self._createTradeRootSimulationRule(portal_type='Delivery Root Simulation Rule',
152
        reference='default_delivery_rule')
153 154 155
    rule.validate()
    transaction.commit()

156 157
  def _createDeliverySimulationRule(self):
    rule = self.rule_tool.newContent(portal_type='Delivery Simulation Rule',
Kazuhiko Shiozaki's avatar
Kazuhiko Shiozaki committed
158
      reference='default_delivering_rule',
159 160
      test_method_id = ('SimulationMovement_testDeliverySimulationRule',)
      )
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
    # matching providers
    for category in ('resource',):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=False,
        matching_provider=True)
    rule.newContent(
      portal_type='Variation Divergence Tester',
      title='variation divergence tester',
      tested_property='variation_property_dict',
      divergence_provider=False,
      matching_provider=True)

    # divergence providers
    for category in ('resource',
                     'source_section',
                     'destination_section',
                     'source',
                     'source_function',
182
                     'destination',
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
                     'destination_function',
                     'source_project',
                     'destination_project',
                     'aggregate',
                     'price_currency',
                     'base_contribution',
                     'base_application',
                     'source_account',
                     'destination_account',
                     ):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Net Converted Quantity Divergence Tester',
      title='quantity divergence tester',
      tested_property='quantity',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
    for property_id in ('start_date', 'stop_date'):
      rule.newContent(
        portal_type='DateTime Divergence Tester',
        title='%s divergence tester' % property_id,
        tested_property=property_id,
        quantity=0,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Float Divergence Tester',
      title='price divergence tester',
      tested_property='price',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)

222 223 224 225
    rule.validate()
    transaction.commit()

  def _createTradeModelSimulationRule(self):
226
    rule = self.rule_tool.newContent(portal_type='Trade Model Simulation Rule',
227
      reference='default_trade_model_rule',
228
      test_method_id = ('SimulationMovement_testTradeModelSimulationRule',)
229
      )
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    # matching providers
    for category in ('resource',):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=False,
        matching_provider=True)
    rule.newContent(
      portal_type='Variation Divergence Tester',
      title='variation divergence tester',
      tested_property='variation_property_dict',
      divergence_provider=False,
      matching_provider=True)

    # divergence providers
    for category in ('resource',
                     'source_section',
                     'destination_section',
                     'source',
                     'source_function',
                     'destination_function',
                     'source_project',
                     'destination_project',
                     'aggregate',
                     'price_currency',
                     'base_contribution',
                     'base_application',
                     'source_account',
                     'destination_account',
                     ):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Net Converted Quantity Divergence Tester',
      title='quantity divergence tester',
      tested_property='quantity',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
    for property_id in ('start_date', 'stop_date'):
      rule.newContent(
        portal_type='DateTime Divergence Tester',
        title='%s divergence tester' % property_id,
        tested_property=property_id,
        quantity=0,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Float Divergence Tester',
      title='price divergence tester',
      tested_property='price',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
289 290 291 292

    rule.validate()
    transaction.commit()

293
  def _createInvoiceRootSimulationRule(self):
294 295
    # Note: This is not used, but invoices, even if built from simulation,
    #       need those rule to create empty one applied rule
296 297
    rule_tool = self.portal.portal_rules

298
    clipboard = rule_tool.manage_copyObjects(ids = ['new_invoice_root_simulation_rule'])
299 300 301 302 303
    pasted = rule_tool.manage_pasteObjects(clipboard)
    new_rule = getattr(rule_tool, pasted[0]['new_id'])
    new_rule.validate()
    transaction.commit()

304
  def _createInvoiceSimulationRule(self):
305 306 307
    edit_dict = {}
    edit_dict.update(
    )
308
    rule = self.rule_tool.newContent(portal_type='Invoice Simulation Rule',
309
      reference='default_invoicing_rule',
310
      trade_phase = 'default/invoicing',
311
      test_method_id = ('SimulationMovement_testInvoiceSimulationRule',)
312
      )
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
    # matching providers
    for category in ('resource',):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=False,
        matching_provider=True)
    rule.newContent(
      portal_type='Variation Divergence Tester',
      title='variation divergence tester',
      tested_property='variation_property_dict',
      divergence_provider=False,
      matching_provider=True)

    # divergence providers
    for category in ('resource',
                     'source_section',
                     'destination_section',
                     'source',
                     'source_function',
                     'destination_function',
                     'source_project',
                     'destination_project',
                     'aggregate',
                     'price_currency',
                     'base_contribution',
                     'base_application',
                     'source_account',
                     'destination_account',
                     ):
      rule.newContent(
        portal_type='Category Membership Divergence Tester',
        title='%s divergence tester' % category,
        tested_property=category,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Net Converted Quantity Divergence Tester',
      title='quantity divergence tester',
      tested_property='quantity',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
    for property_id in ('start_date', 'stop_date'):
      rule.newContent(
        portal_type='DateTime Divergence Tester',
        title='%s divergence tester' % property_id,
        tested_property=property_id,
        quantity=0,
        divergence_provider=True,
        matching_provider=False)
    rule.newContent(
      portal_type='Float Divergence Tester',
      title='price divergence tester',
      tested_property='price',
      quantity=0,
      divergence_provider=True,
      matching_provider=False)
372 373 374 375

    rule.validate()
    transaction.commit()

376 377 378 379 380 381 382 383 384 385 386 387 388
  def _createDocument(self, portal_type, **kw):
    module = self.portal.getDefaultModule(portal_type=portal_type)
    return module.newContent(portal_type=portal_type, **kw)

  def _createProduct(self, **kw):
    return self._createDocument(self.product_portal_type, **kw)

  def _createNode(self, **kw):
    return self._createDocument(self.node_portal_type, **kw)

  def _createTradeCondition(self, **kw):
    self.trade_condition = self._createDocument(
        self.trade_condition_portal_type,
389
        title = self.id(),
390 391
        specialise_value=self.business_process, **kw)

392 393 394
  def _createRootDocumentLine(self, **kw):
    return self.root_document.newContent(
        portal_type=self.root_document_line_portal_type, **kw)
395 396 397 398 399 400

  def _createNodes(self):
    self.source, self.source_section = self._createNode(), self._createNode()
    self.destination, self.destination_section = self._createNode() \
        , self._createNode()

Łukasz Nowak's avatar
Łukasz Nowak committed
401 402 403 404 405 406 407 408
  def _createBusinessStateList(self):
    """Creates list of defaults states, set them on self as name_state property"""
    for state_name in ('ordered', 'delivered', 'invoiced', 'accounted',
        'paid'):
      state_document = self.createBusinessState(self.business_process,
        title=state_name)
      setattr(self,'%s_state' % state_name, state_document)

409 410
  def _createRootDocument(self):
    self.root_document = self._createDocument(self.root_document_portal_type,
411 412 413 414 415 416 417 418
        source_value = self.source,
        source_section_value = self.source_section,
        destination_value = self.destination,
        destination_section_value = self.destination_section,
        start_date = self.order_start_date,
        stop_date = self.order_stop_date,
        specialise_value = self.trade_condition)

419
  def _checkBPMSimulation(self):
420
    """Checks BPMised related simumation.
Łukasz Nowak's avatar
Łukasz Nowak committed
421 422 423

    Note: Simulation tree is the same, it is totally independent from
    BPM sequence"""
424 425
    # TODO:
    #  - gather errors into one list
426
    bpm_root_rule = self.root_document.getCausalityRelatedValue(
427
        portal_type='Applied Rule')
428
    # check that correct root rule applied
429
    self.assertEqual(bpm_root_rule.getSpecialiseValue().getPortalType(),
430
        self.root_rule_portal_type)
431 432 433
    root_simulation_movement_list = bpm_root_rule.contentValues()
    for root_simulation_movement in root_simulation_movement_list:
      self.assertEqual(root_simulation_movement.getPortalType(),
434
          'Simulation Movement')
435
      movement = root_simulation_movement.getDeliveryValue()
436
      property_problem_list = []
437 438
      # check some properties equality between delivery line and simulation
      # movement, gather errors
439 440 441
      for property in 'resource', 'price', 'start_date', 'stop_date', \
                      'source', 'destination', 'source_section', \
                      'destination_section':
442
        if movement.getProperty(property) != root_simulation_movement \
443 444
            .getProperty(property):
          property_problem_list.append('property %s movement %s '
445 446
              'simulation %s' % (property, movement.getProperty(property),
                root_simulation_movement.getProperty(property)))
447 448
      if len(property_problem_list) > 0:
        self.fail('\n'.join(property_problem_list))
449
      self.assertEqual(
450
        movement.getQuantity() * root_simulation_movement.getDeliveryRatio(),
451
        root_simulation_movement.getQuantity())
452 453
      # root rule is order or delivery - so below each movement invoicing one
      # is expected
454
      self.assertEquals(len(root_simulation_movement.contentValues()), 1)
455 456 457 458 459 460 461
      if self.root_rule_portal_type == 'Order Root Simulation Rule':
        delivery_rule = root_simulation_movement.contentValues()[0]
        delivery_simulation_movement_list = delivery_rule.contentValues()
        self.assertEqual(1, len(delivery_simulation_movement_list))
        delivery_simulation_movement = delivery_simulation_movement_list[0]
      else:
        delivery_simulation_movement = root_simulation_movement
462
      for bpm_invoicing_rule in delivery_simulation_movement.contentValues():
463 464
        self.assertEqual(bpm_invoicing_rule.getPortalType(), 'Applied Rule')
        self.assertEqual(bpm_invoicing_rule.getSpecialiseValue() \
465
            .getPortalType(), 'Invoice Simulation Rule')
466
        # only one movement inside invoicing rule
467
        self.assertEquals(len(bpm_invoicing_rule.contentValues()), 1)
468 469 470 471 472 473
        for invoicing_simulation_movement in bpm_invoicing_rule \
            .contentValues():
          self.assertEqual(invoicing_simulation_movement.getPortalType(),
              'Simulation Movement')
          self.assertEqual(invoicing_simulation_movement.getCausalityValue(),
              self.invoice_path)
Łukasz Nowak's avatar
Łukasz Nowak committed
474
          property_problem_list = []
475
          # check equality of some properties, gather them
476
          for property in 'resource', 'price', 'start_date', \
Łukasz Nowak's avatar
Łukasz Nowak committed
477 478
            'stop_date', 'source', 'destination', 'source_section', \
            'destination_section':
479 480
            if movement.getProperty(property) != \
                invoicing_simulation_movement.getProperty(property):
Łukasz Nowak's avatar
Łukasz Nowak committed
481 482 483 484 485
              property_problem_list.append('property %s movement %s '
                  'simulation %s' % (property, movement.getProperty(property),
                    invoicing_simulation_movement.getProperty(property)))
          if len(property_problem_list) > 0:
            self.fail('\n'.join(property_problem_list))
486
          self.assertEqual(
487
            movement.getQuantity() * root_simulation_movement.getDeliveryRatio(),
488
            invoicing_simulation_movement.getQuantity())
489 490
          # simple check for trade model rule existence, without movements,
          # as no trade condition configured
491 492
          self.assertEquals(
              len(invoicing_simulation_movement.contentValues()), 1)
493 494 495 496
          for trade_model_rule in invoicing_simulation_movement \
              .contentValues():
            self.assertEqual(trade_model_rule.getPortalType(), 'Applied Rule')
            self.assertEqual(trade_model_rule.getSpecialiseValue() \
497
                .getPortalType(), 'Trade Model Simulation Rule')
498 499 500 501 502
            self.assertSameSet(trade_model_rule.contentValues(
              portal_type='Simulation Movement'), [])

class TestBPMEvaluationDefaultProcessMixin:
  def _createBusinessProcess(self):
503 504
    self.business_process = self.createBusinessProcess(title=self.id(),
        referential_date='start_date')
Łukasz Nowak's avatar
Łukasz Nowak committed
505
    self._createBusinessStateList()
506 507

    self.delivery_path = self.createBusinessPath(self.business_process,
508 509
        predecessor_value=self.ordered_state,
        successor_value=self.delivered_state,
Łukasz Nowak's avatar
Łukasz Nowak committed
510 511
        trade_phase='default/delivery',
        deliverable=1,
512 513
        completed_state_list=['started', 'stopped', 'delivered'],
        frozen_state_list=['started', 'stopped', 'delivered'],
Łukasz Nowak's avatar
Łukasz Nowak committed
514
        delivery_builder='portal_deliveries/bpm_sale_packing_list_builder',
Łukasz Nowak's avatar
Łukasz Nowak committed
515
        )
516 517

    self.invoice_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
518 519 520 521
        predecessor_value=self.delivered_state,
        successor_value=self.invoiced_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
522
        delivery_builder='portal_deliveries/bpm_sale_invoice_builder',
523 524 525
        trade_phase='default/invoicing')

    self.account_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
526 527 528 529
        predecessor_value=self.invoiced_state,
        successor_value=self.accounted_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
530 531 532
        trade_phase='default/accounting')

    self.pay_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
533 534 535 536
        predecessor_value=self.invoiced_state,
        successor_value=self.accounted_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
537 538 539 540 541 542
        trade_phase='default/payment')

    self.stepTic()

class TestBPMEvaluationDifferentProcessMixin:
  def _createBusinessProcess(self):
543 544
    self.business_process = self.createBusinessProcess(title=self.id(),
        referential_date='start_date')
Łukasz Nowak's avatar
Łukasz Nowak committed
545
    self._createBusinessStateList()
546 547

    self.invoice_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
548 549 550 551
        predecessor_value=self.ordered_state,
        successor_value=self.invoiced_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
552 553 554
        trade_phase='default/invoicing')

    self.account_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
555 556 557 558
        predecessor_value=self.invoiced_state,
        successor_value=self.accounted_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
559 560 561
        trade_phase='default/accounting')

    self.pay_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
562 563 564 565
        predecessor_value=self.accounted_state,
        successor_value=self.paid_state,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'],
566 567 568
        trade_phase='default/payment')

    self.delivery_path = self.createBusinessPath(self.business_process,
Łukasz Nowak's avatar
Łukasz Nowak committed
569 570 571 572 573 574
        predecessor_value=self.paid_state,
        successor_value=self.delivered_state,
        trade_phase='default/delivery',
        deliverable=1,
        completed_state_list=['delivered'],
        frozen_state_list=['stopped', 'delivered'])
575 576 577

    self.stepTic()

578
class GenericRuleTestsMixin:
579
  """Tests which are generic for BPMised Order, Delivery and Invoice Rule"""
580 581 582
  def test_transition(self):
    self.order_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
583 584
    self.stepTic()

585
    self._doFirstTransition(self.root_document)
586
    self.stepTic()
587
    self._checkBPMSimulation()
588

589 590 591
  def _split(self):
    """Invoke manual splitting"""
    ratio = .5 # hardcoded value, hopefully float friendly
Łukasz Nowak's avatar
Łukasz Nowak committed
592 593 594 595
    applied_rule = self.root_document.getCausalityRelatedValue(
        portal_type='Applied Rule')
    for movement in applied_rule.contentValues(
        portal_type='Simulation Movement'):
596 597 598 599 600 601 602 603 604 605 606 607 608 609 610
      new_movement = movement.Base_createCloneDocument(batch_mode=1)
      old_quantity = movement.getQuantity()
      movement.edit(
        quantity = old_quantity * ratio
      )

      new_movement.edit(
        quantity = old_quantity * (1 - ratio)
      )

    self.stepTic()

    # recalculate order ratio
    for movement in self.root_document.getMovementList():
      movement_quantity = movement.getQuantity()
611
      for simulation_movement in movement.getDeliveryRelatedValueList():
612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658
        new_ratio = simulation_movement.getQuantity() / movement_quantity
        simulation_movement.edit(order_ratio = new_ratio)
        if simulation_movement.getDelivery() is not None:
          simulation_movement.edit(delivery_ratio = new_ratio)

    # reexpand
    applied_rule.expand()
    self.stepTic()

    self._checkBPMSimulation()

  def test_transition_split(self):
    self.order_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
    self.stepTic()

    self._doFirstTransition(self.root_document)
    self.stepTic()
    self._checkBPMSimulation()

    self._split()

    # expand
    self.root_document.edit(title = self.root_document.getTitle() + 'a')

    self.stepTic()
    self._checkBPMSimulation()

  def test_transition_split_line_add(self):
    self.test_transition_split()
    self.order_line_2 = self._createRootDocumentLine(
        resource_value = self._createProduct(), quantity = 4, price = 2)
    self.stepTic()
    self._checkBPMSimulation()

  def test_transition_split_line_add_split(self):
    self.test_transition_split_line_add()

    # second split
    self._split()

    # expand
    self.root_document.edit(title = self.root_document.getTitle() + 'a')

    self.stepTic()
    self._checkBPMSimulation()

659 660
  def test_transition_line_edit(self):
    self.test_transition()
661 662
    self.order_line.edit(quantity = 8, price = 6)
    self.stepTic()
663
    self._checkBPMSimulation()
664

665 666 667
  def test_transition_line_edit_add(self):
    self.test_transition_line_edit()
    self.order_line_2 = self._createRootDocumentLine(
668 669
        resource_value = self._createProduct(), quantity = 4, price = 2)
    self.stepTic()
670
    self._checkBPMSimulation()
671

672 673 674
  def test_transition_line_edit_add_many_transactions(self):
    self.test_transition_line_edit()
    self.order_line_9 = self._createRootDocumentLine()
675
    self.stepTic()
676
    self._checkBPMSimulation()
677 678 679

    self.order_line_9.edit(resource_value = self._createProduct())
    self.stepTic()
680
    self._checkBPMSimulation()
681 682 683

    self.order_line_9.edit(quantity = 1)
    self.stepTic()
684
    self._checkBPMSimulation()
685 686 687

    self.order_line_9.edit(price = 33)
    self.stepTic()
688
    self._checkBPMSimulation()
689 690 691

    self.order_line_9.edit(resource_value = self._createProduct())
    self.stepTic()
692
    self._checkBPMSimulation()
693

694 695
  def test_transition_line_edit_add_same_resource(self):
    self.test_transition_line_edit()
696
    resource = self.order_line.getResourceValue()
697 698
    self.order_line_10 = self._createRootDocumentLine(
      resource_value = resource, quantity = 9, price = 2)
699
    self.stepTic()
700
    self._checkBPMSimulation()
701

702 703
  def test_transition_line_edit_add_same_resource_edit_again(self):
    self.test_transition_line_edit_add_same_resource()
704

705 706 707
    self.root_document.edit(title = self.root_document.getTitle() + 'a' )
    self.stepTic()
    self._checkBPMSimulation()
708

709
class TestOrder(TestBPMEvaluationMixin, GenericRuleTestsMixin):
710
  """Check BPMised Order Rule behaviour"""
711 712
  root_document_portal_type = 'Sale Order'
  root_document_line_portal_type = 'Sale Order Line'
713
  root_rule_portal_type = 'Order Root Simulation Rule'
714

715 716
  def _doFirstTransition(self, document):
    document.plan()
717

718 719 720
  def test_confirming(self):
    self.order_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
721 722
    self.stepTic()

723
    self.root_document.confirm()
724
    self.stepTic()
725 726 727 728 729 730 731 732 733 734
    self._checkBPMSimulation()
    self.assertEqual(
      2,
      len(self.root_document.getCausalityRelatedList())
    )
    self.assertEqual(
      'Applied Rule',
      self.root_document.getCausalityRelatedValue(
        portal_type='Applied Rule').getPortalType()
    )
735

736 737 738 739 740
    self.assertEqual(
      self.packing_list_portal_type,
      self.root_document.getCausalityRelatedValue(
        portal_type=self.packing_list_portal_type).getPortalType()
    )
741

742 743 744 745
class TestPackingList(TestBPMEvaluationMixin, GenericRuleTestsMixin):
  """Check BPM Delivery Rule behaviour"""
  root_document_portal_type = 'Sale Packing List'
  root_document_line_portal_type = 'Sale Packing List Line'
746
  root_rule_portal_type = 'Delivery Root Simulation Rule'
747

748 749 750 751 752 753 754 755 756 757 758
  def _packDelivery(self):
    """Packs delivery fully, removes possible containers before"""
    self.root_document.deleteContent(self.root_document.contentIds(
      filter={'portal_type':'Container'}))
    cont = self.root_document.newContent(portal_type='Container')
    for movement in self.root_document.getMovementList():
      cont.newContent(portal_type='Container Line',
        resource = movement.getResource(), quantity = movement.getQuantity())
    self.stepTic()
    self._checkBPMSimulation()

759 760 761
  def _doFirstTransition(self, document):
    document.confirm()

762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792
  def test_starting(self):
    self.delivery_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
    self.stepTic()

    self.root_document.confirm()
    self.stepTic()
    self._checkBPMSimulation()

    self._packDelivery()

    self.root_document.start()
    self.stepTic()
    self._checkBPMSimulation()

    self.assertEqual(
      2,
      len(self.root_document.getCausalityRelatedList())
    )
    self.assertEqual(
      'Applied Rule',
      self.root_document.getCausalityRelatedValue(
        portal_type='Applied Rule').getPortalType()
    )

    self.assertEqual(
      self.invoice_portal_type,
      self.root_document.getCausalityRelatedValue(
        portal_type=self.invoice_portal_type).getPortalType()
    )

793 794 795
class TestInvoice(TestBPMEvaluationMixin, GenericRuleTestsMixin):
  """Check BPM Invoice Rule behaviour"""
  # not implemented yet
796 797
  pass

798 799 800
class TestOrderDefaultProcess(TestOrder,
    TestBPMEvaluationDefaultProcessMixin):
  pass
801

802 803 804
class TestPackingListDefaultProcess(TestPackingList,
    TestBPMEvaluationDefaultProcessMixin):
  pass
805

806 807 808
class TestInvoiceDefaultProcess(TestInvoice,
    TestBPMEvaluationDefaultProcessMixin):
  pass
809

810 811
class TestOrderDifferentProcess(TestOrder,
    TestBPMEvaluationDifferentProcessMixin):
812 813 814 815
  def test_confirming(self):
    # in current BPM configuration nothing shall be built
    # as soon as test business process will be finished, it shall built proper
    # delivery
816 817
    self.order_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
818 819
    self.stepTic()

820
    self.root_document.confirm()
821
    self.stepTic()
822
    self._checkBPMSimulation()
823 824
    self.assertEqual(
      1,
825
      len(self.root_document.getCausalityRelatedList())
826 827 828
    )
    self.assertEqual(
      'Applied Rule',
829
      self.root_document.getCausalityRelatedValue().getPortalType()
830
    )
831

832 833
class TestPackingListDifferentProcess(TestPackingList,
    TestBPMEvaluationDifferentProcessMixin):
834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856
  def test_starting(self):
    self.delivery_line = self._createRootDocumentLine(
      resource_value = self._createProduct(), quantity = 10, price = 5)
    self.stepTic()

    self.root_document.confirm()
    self.stepTic()
    self._checkBPMSimulation()

    self._packDelivery()
    self.root_document.start()
    self.stepTic()
    self._checkBPMSimulation()

    self.assertEqual(
      1,
      len(self.root_document.getCausalityRelatedList())
    )
    self.assertEqual(
      'Applied Rule',
      self.root_document.getCausalityRelatedValue(
        portal_type='Applied Rule').getPortalType()
    )
857

858 859
class TestInvoiceDifferentProcess(TestInvoice,
    TestBPMEvaluationDifferentProcessMixin):
860 861 862 863 864 865
  pass

def test_suite():
  suite = unittest.TestSuite()
  suite.addTest(unittest.makeSuite(TestOrderDefaultProcess))
  suite.addTest(unittest.makeSuite(TestPackingListDefaultProcess))
866 867
#  suite.addTest(unittest.makeSuite(TestInvoiceDefaultProcess))

868 869
  suite.addTest(unittest.makeSuite(TestOrderDifferentProcess))
  suite.addTest(unittest.makeSuite(TestPackingListDifferentProcess))
870
#  suite.addTest(unittest.makeSuite(TestInvoiceDifferentProcess))
871
  return suite