├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── codeception.dist.yml ├── composer.json ├── diagram.png ├── src ├── migrations │ ├── Assignment.php │ ├── WorkLog.php │ ├── m170101_000001_workflow.php │ ├── m170101_000002_workflow_stage.php │ ├── m170101_000003_workflow_transition.php │ └── m170101_000004_workflow_transition_permission.php ├── models │ ├── Assignment.php │ ├── Process.php │ ├── Stage.php │ ├── Transition.php │ ├── TransitionPermission.php │ ├── WorkLog.php │ └── Workflow.php └── roa │ ├── models │ ├── Stage.php │ ├── StageSearch.php │ ├── Transition.php │ ├── TransitionPermission.php │ ├── TransitionPermissionSearch.php │ ├── TransitionSearch.php │ ├── Workflow.php │ └── WorkflowSearch.php │ ├── modules │ └── Version.php │ └── resources │ ├── PermissionResource.php │ ├── StageResource.php │ ├── TransitionResource.php │ └── WorkflowResource.php └── tests ├── _app ├── api │ ├── models │ │ ├── Credit.php │ │ ├── CreditAssignment.php │ │ ├── CreditSearch.php │ │ └── CreditWorklog.php │ ├── modules │ │ └── Version.php │ └── resources │ │ ├── CreditAssignmentResource.php │ │ ├── CreditResource.php │ │ └── CreditWorklogResource.php ├── assets │ └── .gitignore ├── config │ ├── .gitignore │ ├── common.php │ ├── console.php │ ├── db.php │ ├── test.php │ └── web.php ├── controllers │ └── SiteController.php ├── fixtures │ ├── AuthItemFixture.php │ ├── CreditAssignmentFixture.php │ ├── CreditFixture.php │ ├── CreditWorklogFixture.php │ ├── OauthAccessTokensFixture.php │ ├── OauthClientsFixture.php │ ├── StageFixture.php │ ├── TransitionFixture.php │ ├── TransitionPermissionFixture.php │ ├── UserFixture.php │ ├── WorkflowFixture.php │ └── data │ │ ├── access_tokens.php │ │ ├── auth_item.php │ │ ├── clients.php │ │ ├── credit.php │ │ ├── credit_assignment.php │ │ ├── credit_worklog.php │ │ ├── stage.php │ │ ├── transition.php │ │ ├── transition_permission.php │ │ ├── user.php │ │ └── workflow.php ├── index.php ├── migrations │ ├── m130101_000001_user.php │ ├── m171130_000002_credit.php │ ├── m171130_000003_credit_worklog.php │ └── m171130_000004_credit_assignment.php ├── models │ ├── Credit.php │ ├── CreditAssignment.php │ ├── CreditWorklog.php │ └── User.php ├── views │ ├── layouts │ │ └── main.php │ └── site │ │ └── index.php └── yii.php ├── _bootstrap.php ├── _data ├── .gitkeep └── schema.sql ├── _output └── .gitignore ├── _support ├── ApiTester.php ├── FunctionalTester.php ├── Helper │ ├── Acceptance.php │ ├── Api.php │ ├── Functional.php │ └── Unit.php └── UnitTester.php ├── api.suite.yml ├── api ├── CreditAssignmentCest.php ├── CreditCest.php ├── CreditWorklogCest.php ├── StageCest.php ├── TransitionCest.php ├── TransitionPermissionCest.php ├── WorkflowCest.php └── _bootstrap.php ├── unit.suite.yml └── unit ├── _bootstrap.php └── models ├── CreditCest.php └── CreditWorklogCest.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders to ignore 2 | vendor 3 | 4 | # autogenerated files 5 | composer.lock 6 | tests/_support/_generated 7 | tests/_app/runtime 8 | 9 | # OS or Editor files and folders 10 | .buildpath 11 | .cache 12 | .DS_Store 13 | .idea 14 | .project 15 | .settings 16 | .tmproj 17 | desktop.ini 18 | nbproject 19 | Thumbs.db 20 | *.sublime-project 21 | *.sublime-workspace 22 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: [tests/_support/_generated/*] 3 | checks: 4 | php: 5 | code_rating: true 6 | verify_property_names: false 7 | use_statement_alias_conflict: true 8 | use_self_instead_of_fqcn: true 9 | uppercase_constants: true 10 | unused_variables: true 11 | unused_properties: true 12 | unused_parameters: false 13 | unused_methods: true 14 | unreachable_code: true 15 | too_many_arguments: true 16 | sql_injection_vulnerabilities: true 17 | return_doc_comments: true 18 | return_doc_comment_if_not_inferrable: true 19 | require_scope_for_properties: true 20 | require_scope_for_methods: true 21 | require_php_tag_first: true 22 | psr2_switch_declaration: true 23 | psr2_class_declaration: true 24 | property_assignments: true 25 | properties_in_camelcaps: true 26 | prefer_while_loop_over_for_loop: true 27 | precedence_mistakes: true 28 | precedence_in_conditions: true 29 | php5_style_constructor: true 30 | parse_doc_comments: true 31 | parameters_in_camelcaps: false 32 | parameter_non_unique: true 33 | parameter_doc_comments: true 34 | param_doc_comment_if_not_inferrable: true 35 | overriding_private_members: true 36 | overriding_parameter: true 37 | optional_parameters_at_the_end: true 38 | one_class_per_file: true 39 | non_commented_empty_catch_block: true 40 | no_unnecessary_if: true 41 | no_unnecessary_final_modifier: true 42 | no_underscore_prefix_in_properties: true 43 | no_underscore_prefix_in_methods: true 44 | no_trait_type_hints: true 45 | no_trailing_whitespace: true 46 | no_short_open_tag: true 47 | no_property_on_interface: true 48 | no_non_implemented_abstract_methods: true 49 | no_goto: true 50 | no_global_keyword: true 51 | no_exit: true 52 | no_eval: true 53 | no_error_suppression: true 54 | no_empty_statements: true 55 | no_duplicate_arguments: true 56 | no_debug_code: true 57 | no_commented_out_code: true 58 | more_specific_types_in_doc_comments: true 59 | missing_arguments: true 60 | method_calls_on_non_object: true 61 | line_length: 62 | max_length: '120' 63 | instanceof_class_exists: true 64 | function_in_camel_caps: true 65 | foreach_usable_as_reference: true 66 | foreach_traversable: true 67 | encourage_single_quotes: true 68 | encourage_shallow_comparison: true 69 | encourage_postdec_operator: true 70 | duplication: true 71 | deprecated_code_usage: true 72 | deadlock_detection_in_loops: true 73 | comparison_always_same_result: true 74 | code_rating: true 75 | closure_use_not_conflicting: true 76 | closure_use_modifiable: true 77 | classes_in_camel_caps: true 78 | check_method_contracts: 79 | verify_interface_like_constraints: true 80 | verify_documented_constraints: true 81 | verify_parent_constraints: true 82 | catch_class_exists: true 83 | call_to_parent_method: true 84 | blank_line_after_namespace_declaration: true 85 | avoid_useless_overridden_methods: true 86 | avoid_usage_of_logical_operators: true 87 | avoid_unnecessary_concatenation: true 88 | avoid_todo_comments: true 89 | avoid_superglobals: true 90 | avoid_perl_style_comments: true 91 | avoid_multiple_statements_on_same_line: true 92 | avoid_length_functions_in_loops: true 93 | avoid_fixme_comments: true 94 | avoid_duplicate_types: true 95 | avoid_corrupting_byteorder_marks: true 96 | avoid_conflicting_incrementers: true 97 | avoid_closing_tag: true 98 | assignment_of_null_return: true 99 | argument_type_checks: true 100 | align_assignments: true 101 | remove_extra_empty_lines: true 102 | remove_php_closing_tag: true 103 | remove_trailing_whitespace: true 104 | fix_use_statements: 105 | remove_unused: true 106 | preserve_multiple: false 107 | preserve_blanklines: true 108 | order_alphabetically: true 109 | fix_php_opening_tag: true 110 | fix_linefeed: true 111 | fix_line_ending: true 112 | fix_identation_4spaces: true 113 | fix_doc_comments: true 114 | 115 | coding_style: 116 | php: 117 | spaces: 118 | before_parentheses: 119 | closure_definition: true 120 | around_operators: 121 | concatenation: true 122 | other: 123 | after_type_cast: false 124 | braces: 125 | classes_functions: 126 | class: new-line 127 | function: new-line 128 | closure: end-of-line 129 | if: 130 | opening: end-of-line 131 | for: 132 | opening: end-of-line 133 | while: 134 | opening: end-of-line 135 | do_while: 136 | opening: end-of-line 137 | switch: 138 | opening: end-of-line 139 | try: 140 | opening: end-of-line 141 | upper_lower_casing: 142 | constants: 143 | true_false_null: lower 144 | tools: 145 | external_code_coverage: false 146 | php_analyzer: true 147 | php_code_coverage: true 148 | php_code_sniffer: 149 | config: 150 | standard: PSR2 151 | sniffs: { generic: { formatting: { multiple_statement_alignment_sniff: false } } } 152 | php_cpd: 153 | enabled: false # solicitado por scrutinizer para usar php_analyzer 154 | excluded_dirs: [vendor, tests] 155 | build: 156 | environment: 157 | php: 158 | version: 7.1 159 | nodes: 160 | my-tests: 161 | environment: 162 | mysql: 5.6 163 | project_setup: 164 | before: 165 | # crear la base de datos, el nombre tiene que coincidir con la base 166 | # del archivo `environments/dev/common/config/main-local.php` 167 | - mysql -e "create database yii2_workflow_test" 168 | tests: 169 | before: 170 | # run migrations 171 | - composer deploy-tests 172 | override: 173 | # test coverage 174 | - 175 | # comando de coverage 176 | command: composer run-coverage 177 | coverage: 178 | file: 'tests/_output/coverage.xml' 179 | format: 'clover' 180 | idle_timeout: 1800 181 | analysis: 182 | project_setup: 183 | override: [true] 184 | 185 | tests: 186 | override: 187 | - php-scrutinizer-run -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | # faster builds on new travis setup not using sudo 3 | sudo: false 4 | 5 | language: php 6 | 7 | php: 8 | - 7.1 9 | - 7.2 10 | - 7.3 11 | - nightly 12 | 13 | matrix: 14 | fast_finish: true 15 | allow_failures: 16 | - php: nightly 17 | 18 | install: 19 | - | 20 | if [[ $TRAVIS_PHP_VERSION != '7.1' && $TRAVIS_PHP_VERSION != hhv* ]]; then 21 | # disable xdebug for performance reasons when code coverage is not needed 22 | # note: xdebug on hhvm is disabled by default 23 | phpenv config-rm xdebug.ini || echo "xdebug is not installed" 24 | fi 25 | - travis_retry composer self-update && composer --version 26 | - travis_retry composer install --prefer-dist --no-interaction 27 | 28 | before_script: 29 | - php -r "echo INTL_ICU_VERSION . \"\n\";" 30 | - php -r "echo INTL_ICU_DATA_VERSION . \"\n\";" 31 | - mysql --version 32 | 33 | # initialize database 34 | - mysql -e "create database yii2_workflow_test" 35 | 36 | # enable code coverage on PHP 7.1, only one PHP version needs to generate coverage data 37 | - | 38 | if [ $TRAVIS_PHP_VERSION = '7.1' ]; then 39 | CODECEPTION_FLAGS="--coverage-xml" 40 | fi 41 | 42 | script: 43 | - composer deploy-tests 44 | - composer run-tests 45 | 46 | after_script: 47 | - | 48 | if [ $TRAVIS_PHP_VERSION = '7.1' ]; then 49 | travis_retry wget https://scrutinizer-ci.com/ocular.phar 50 | php ocular.phar code-coverage:upload --format=php-clover tests/_output/coverage.xml 51 | fi 52 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii2 Workflow Change Log 2 | ========================== 3 | 4 | 0.4.0 July 13, 2019 5 | 6 | - Brk: Methods now use the typecast supported in php 7.1 7 | 8 | 0.3.2 December 18, 2018 9 | 10 | - fix version bump for yii2 roa tests 11 | 12 | 0.3.1 December 17, 2018 13 | ------------------------ 14 | 15 | - Bugfix: Requires yii2 roa dev-master 16 | 17 | 0.3.0 October 18, 2018 18 | ------------------------ 19 | 20 | - Enh: Add CHANGELOG.md 21 | - Enh: Nested expand support as defined by yii2.0.14 22 | - Enh: Autogenerated curies using `tecnocen\roa\behaviors\Curies` 23 | - Bugfix: Logic to autogenerated the initial worklog on a process. 24 | - Bugfix: Add Primary Key on 25 | `tecnocen\workflow\migrations\Assignment` 26 | - BRK: Requires yii2 roa 0.4.1 27 | - Enh: Roa models implemtn `tecnocen\roa\hal\Contract` 28 | 29 | 0.2.0 December 31, 2017 30 | ------------------------ 31 | 32 | - Enh #3: Add tests 33 | - Enh: Process and Worklog 34 | 35 | 0.1.0 December 30, 2017 36 | ----------------------------- 37 | 38 | - Initial release. 39 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | This code of conduct outlines our expectations for participants within the **Tecnocen.com - Open Source** community, as well as steps to reporting unacceptable behavior. We are committed to providing a welcoming and inspiring community for all and expect our code of conduct to be honored. Anyone who violates this code of conduct may be banned from the community. 3 | 4 | Our open source community strives to: 5 | 6 | * **Be friendly and patient.** 7 | * **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. This includes, but is not limited to members of any race, ethnicity, culture, national origin, colour, immigration status, social and economic class, educational level, sex, sexual orientation, gender identity and expression, age, size, family status, political belief, religion, and mental and physical ability. 8 | * **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. Any decision you take will affect users and colleagues, and you should take those consequences into account when making decisions. Remember that we're a world-wide community, so you might not be communicating in someone else's primary language. 9 | * **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. We might all experience some frustration now and then, but we cannot allow that frustration to turn into a personal attack. It’s important to remember that a community where people feel uncomfortable or threatened is not a productive one. 10 | * **Be careful in the words that we choose**: we are a community of professionals, and we conduct ourselves professionally. Be kind to others. Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. 11 | * **Try to understand why we disagree**: Disagreements, both social and technical, happen all the time. It is important that we resolve disagreements and differing views constructively. Remember that we’re different. The strength of our community comes from its diversity, people from a wide range of backgrounds. Different people have different perspectives on issues. Being unable to understand why someone holds a viewpoint doesn’t mean that they’re wrong. Don’t forget that it is human to err and blaming each other doesn’t get us anywhere. Instead, focus on helping to resolve issues and learning from mistakes. 12 | 13 | ## Definitions 14 | 15 | Harassment includes, but is not limited to: 16 | 17 | - Offensive comments related to gender, gender identity and expression, sexual orientation, disability, mental illness, neuro(a)typicality, physical appearance, body size, race, age, regional discrimination, political or religious affiliation 18 | - Unwelcome comments regarding a person’s lifestyle choices and practices, including those related to food, health, parenting, drugs, and employment 19 | - Deliberate misgendering. This includes deadnaming or persistently using a pronoun that does not correctly reflect a person's gender identity. You must address people by the name they give you when not addressing them by their username or handle 20 | - Physical contact and simulated physical contact (eg, textual descriptions like “*hug*” or “*backrub*”) without consent or after a request to stop 21 | - Threats of violence, both physical and psychological 22 | - Incitement of violence towards any individual, including encouraging a person to commit suicide or to engage in self-harm 23 | - Deliberate intimidation 24 | - Stalking or following 25 | - Harassing photography or recording, including logging online activity for harassment purposes 26 | - Sustained disruption of discussion 27 | - Unwelcome sexual attention, including gratuitous or off-topic sexual images or behaviour 28 | - Pattern of inappropriate social contact, such as requesting/assuming inappropriate levels of intimacy with others 29 | - Continued one-on-one communication after requests to cease 30 | - Deliberate “outing” of any aspect of a person’s identity without their consent except as necessary to protect others from intentional abuse 31 | - Publication of non-harassing private communication 32 | 33 | Our open source community prioritizes marginalized people’s safety over privileged people’s comfort. We will not act on complaints regarding: 34 | 35 | - ‘Reverse’ -isms, including ‘reverse racism,’ ‘reverse sexism,’ and ‘cisphobia’ 36 | - Reasonable communication of boundaries, such as “leave me alone,” “go away,” or “I’m not discussing this with you” 37 | - Refusal to explain or debate social justice concepts 38 | - Communicating in a ‘tone’ you don’t find congenial 39 | - Criticizing racist, sexist, cissexist, or otherwise oppressive behavior or assumptions 40 | 41 | 42 | ### Diversity Statement 43 | 44 | We encourage everyone to participate and are committed to building a community for all. Although we will fail at times, we seek to treat everyone both as fairly and equally as possible. Whenever a participant has made a mistake, we expect them to take responsibility for it. If someone has been harmed or offended, it is our responsibility to listen carefully and respectfully, and do our best to right the wrong. 45 | 46 | Although this list cannot be exhaustive, we explicitly honor diversity in age, gender, gender identity or expression, culture, ethnicity, language, national origin, political beliefs, profession, race, religion, sexual orientation, socioeconomic status, and technical ability. We will not tolerate discrimination based on any of the protected 47 | characteristics above, including participants with disabilities. 48 | 49 | ### Reporting Issues 50 | 51 | If you experience or witness unacceptable behavior—or have any other concerns—please report it by contacting us via **opensource@tecnocen.com**. All reports will be handled with discretion. In your report please include: 52 | 53 | - Your contact information. 54 | - Names (real, nicknames, or pseudonyms) of any individuals involved. If there are additional witnesses, please 55 | include them as well. Your account of what occurred, and if you believe the incident is ongoing. If there is a publicly available record (e.g. a mailing list archive or a public IRC logger), please include a link. 56 | - Any additional information that may be helpful. 57 | 58 | After filing a report, a representative will contact you personally, review the incident, follow up with any additional questions, and make a decision as to how to respond. If the person who is harassing you is part of the response team, they will recuse themselves from handling your incident. If the complaint originates from a member of the response team, it will be handled by a different member of the response team. We will respect confidentiality requests for the purpose of protecting victims of abuse. 59 | 60 | Anyone asked to stop unacceptable behavior is expected to comply immediately. If an individual engages in unacceptable behavior, the representative may take any action they deem appropriate, up to and including a permanent ban from our community without warning. 61 | 62 | This Code Of Conduct follows the [template](http://todogroup.org/opencodeofconduct/) established by the [TODO Group](http://todogroup.org). 63 | 64 | ### Attribution & Acknowledgements 65 | 66 | We all stand on the shoulders of giants across many open source communities. We'd like to thank the communities and projects that established code of conducts and diversity statements as our inspiration: 67 | 68 | * [Django](https://www.djangoproject.com/conduct/reporting/) 69 | * [Python](https://www.python.org/community/diversity/) 70 | * [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) 71 | * [Contributor Covenant](http://contributor-covenant.org/) 72 | * [Geek Feminism](http://geekfeminism.org/about/code-of-conduct/) 73 | * [Citizen Code of Conduct](http://citizencodeofconduct.org/) 74 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to Tecnocen Yii2 Workflow 2 | ====================================== 3 | 4 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 5 | 6 | The following is a set of guidelines for contributing to Tecnocen and its 7 | packages, which are hosted in the 8 | [Tecnocen Organization](https://github.com/tecnocen-com) on GitHub. These 9 | are mostly guidelines, not rules. Use your best judgment, and feel free to 10 | propose changes to this document in a pull request. 11 | 12 | Forums and Support 13 | ------------------ 14 | 15 | We are currently piggy backing from 16 | [Yii2 Extensions Forum](http://www.yiiframework.com/forum/index.php/forum/13-extensions/) 17 | you can make questions there where other Yii2 devs might see it and answer it. 18 | 19 | If you need profesional support please feel free to contact support@tecnocen.com with the following information. 20 | 21 | - What steps will reproduce the problem? 22 | - What is the expected result? 23 | - What do you get instead? 24 | - Additional info 25 | 26 | 27 | Reporting Bugs 28 | -------------- 29 | 30 | Bugs are tracked as [GitHub issues](https://guides.github.com/features/issues/). 31 | Its required for you to follow this steps when submitting a bug: 32 | 33 | 1. Determine its not already reported by searching existing issues 34 | https://github.com/search?q=+is%3Aissue+user%3Atecnocen-com 35 | 36 | 2. Determine its not an issue of any of the dependencies of this library such as 37 | [Yii2](https://github.com/search?q=+is%3Aissue+user%3Ayiisoft) 38 | 39 | 3. If the bug has not been reported then provide enough information to reproduce 40 | and isolate the bug. 41 | - Use a descriptive title to differentiate the bug. 42 | - Describe the exact steps to reproduce the issue. Focus on how you did it 43 | instead of what you did. 44 | - Provide specific examples to demonstrate the steps. You can upload files, 45 | screen captures, links to copy/paste snippets, etc. 46 | - Describe observed behavior. 47 | - Describe expected behavior. 48 | - If you report a server error caused by the library include the error 49 | message you get along with the exception trace if you have it. 50 | - Provide the versions used by composer using the comand 51 | > composer show 52 | 53 | 4. Stay in touch, check the issue for feedback from other users and developers. 54 | 55 | Suggesting Enhancements 56 | ----------------------- 57 | 58 | You can suggest enhancements for the library using Github Issues aswell. Its 59 | required for you to follow this steps when suggesting a change. 60 | 61 | 1. Read the documentation and see if the enhancement does not exists already. 62 | 63 | 2. Check the description of the library and see if there are use cases for your 64 | enhacement into the library. 65 | 66 | 3. Check if there is not a package already which covers the use case described 67 | on the previous step. 68 | 69 | 4. Check it was not already suggested 70 | https://github.com/search?q=+is%3Aissue+user%3Atecnocen-com 71 | 72 | 5. If the enhancement was not suggested already then provide enough information 73 | to understand and develop the enhacenment 74 | - Use a descriptive title to differentiate the enhancement. 75 | - Describe the exact steps to utilize the enhancement. Focus on how you plant 76 | to use it and implement instead on what you want implemented. 77 | - Provide specific examples to demonstrate the steps. You can upload files, 78 | screen captures, links to copy/paste snippets, etc. 79 | - Describe expected behavior. 80 | 81 | Code Contribution 82 | ----------------- 83 | 84 | Code contributions are handled by Github Pull Requests. 85 | 86 | ### Testing Environment 87 | 88 | This library contains tools to set up a testing environment using composer 89 | scripts. 90 | P 91 | - Clone the repository from github and move to the created folder. 92 | - edit or create the file `tests/_app/config/db.local.php` with your db 93 | credentials 94 | - Prepare your local environment to run the tests with composer script 95 | > composer deploy-tests 96 | - When the command finish executing successfully you can run 97 | `composer run-tests` to run all the codeception tests of the library. 98 | 99 | You can now write changes locally and run tests to change 100 | 101 | Creating Pull Requests 102 | ---------------------- 103 | 104 | When you are ready to submit your contribution you can create a pull request. 105 | Its required for you to follow this steps when creating a pull request. 106 | 107 | 1. Use a descriptive title, include the issues and pull request #id the new 108 | pull request affects on the tile. 109 | 2. Describe the use case or bug it covers, keep it simple by focusing on one 110 | functionality at once. 111 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## **What steps will reproduce the problem?** 2 | 3 | Description 4 | 5 | ``` 6 | Add code here if u need 7 | ``` 8 | 9 | ## **What is the expected result?** 10 | 11 | Description 12 | 13 | ``` 14 | Add code here if u need 15 | ``` 16 | 17 | ## **What do you get instead?** 18 | 19 | Description 20 | 21 | ``` 22 | Add code here if u need 23 | ``` 24 | 25 | 26 | ## **Additional info** 27 | 28 | Description 29 | 30 | Table example if u need 31 | 32 | Title1 | Title2 | Title3| 33 | -- | -- | -- 34 | data1 | data2 | data3 35 | data n + 1 | data n + 2 | data n + 3 36 | 37 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-present Tecnocen.com 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | | Q | A 2 | | ------------- | --- 3 | | Is bugfix? | yes/no 4 | | New feature? | yes/no 5 | | Breaks BC? | yes/no 6 | | Tests pass? | yes/no 7 | | Fixed issues | comma-separated list of tickets # fixed by the PR, if any 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Workflow 2 | ============== 3 | Library to dynamically handle workflows in a database with ROA support. 4 | 5 | [![Latest Stable Version](https://poser.pugx.org/tecnocen/yii2-workflow/v/stable)](https://packagist.org/packages/tecnocen/yii2-workflow) 6 | [![Total Downloads](https://poser.pugx.org/tecnocen/yii2-workflow/downloads)](https://packagist.org/packages/tecnocen/yii2-workflow) 7 | 8 | 9 | Travis [![Build Status Travis](https://travis-ci.org/tecnocen-com/yii2-workflow.svg?branch=master&style=flat?style=for-the-badge)](https://travis-ci.org/tecnocen-com/yii2-workflow) 10 | 11 | ## Getting Started 12 | 13 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 14 | 15 | ### Prerequisites 16 | 17 | - Install PHP 7.1 or higher 18 | - [Composer Installed](https://getcomposer.org/doc/00-intro.md) 19 | 20 | The rest of the requirements are checked by composer when installing the 21 | repository on the next step. 22 | 23 | ### Installation 24 | ---------------- 25 | 26 | You can use composer to install the library `tecnocen/yii2-workflow` by running 27 | the command; 28 | 29 | `composer require tecnocen/yii2-workflow` 30 | 31 | or edit the `composer.json` file 32 | 33 | ```json 34 | require: { 35 | "tecnocen/yii2-workflow": "*", 36 | } 37 | ``` 38 | 39 | ### Deployment 40 | 41 | Then run the required migrations 42 | 43 | `php yii migrate/up -p=@tecnocen/workflow/migrations` 44 | 45 | Which will install the following table structure 46 | 47 | ![Database Diagram](diagram.png) 48 | 49 | 50 | #### ROA Backend Usage 51 | ----------------- 52 | 53 | The ROA support is very simple and can be done by just adding a module version 54 | to the api container which will be used to hold the resources. 55 | 56 | ```php 57 | class Api extends \tecnocen\roa\modules\ApiContainer 58 | { 59 | public $versions = [ 60 | // other versions 61 | 'w1' => ['class' => 'tecnocen\workflow\roa\modules\Version'], 62 | ]; 63 | } 64 | ``` 65 | 66 | You can then access the module to check the available resources. 67 | 68 | - workflow 69 | - workflow//stage 70 | - workflow//stage//transition 71 | - workflow//stage//transition//permission 72 | 73 | Which will implement CRUD functionalities for a workflow. 74 | 75 | #### Process and Worklog 76 | ------------------------ 77 | 78 | A `process` is an entity which changes from stage depending on a workflow. Each 79 | stage change is registered on a `worklog` for each `process` record. 80 | 81 | To create a `process` its required to create a migrations for the process and 82 | the worklog then the models to handle them, its adviced to use the provided 83 | migration templates. 84 | 85 | ```php 86 | class m170101_010101_credit extends EntityTable 87 | { 88 | public function getTableName() 89 | { 90 | return 'credit'; 91 | } 92 | 93 | public function columns() 94 | { 95 | return [ 96 | 'id' => $this->primaryKey(), 97 | 'workflow_id' => $this->normalKey(), 98 | // other columns 99 | ]; 100 | } 101 | 102 | public function foreignKeys() 103 | { 104 | return [ 105 | 'workflow_id' => ['table' => 'tecnocen_workflow']; 106 | ]; 107 | } 108 | } 109 | ``` 110 | 111 | ```php 112 | class m170101_010102_credit_worklog extends \tecnocen\workflow\migrations\WorkLog 113 | { 114 | public function getProcessTableName() 115 | { 116 | return 'credit'; 117 | } 118 | } 119 | ``` 120 | 121 | After running the migrations its necessary to create Active Record models. 122 | 123 | ```php 124 | class Credit extends \tecnocen\workflow\models\Process 125 | { 126 | protected function workflowClass() 127 | { 128 | return CreditWorklog::class; 129 | } 130 | 131 | public function getWorkflowId() 132 | { 133 | return $this->workflow_id; 134 | } 135 | 136 | public function rules() 137 | { 138 | return \yii\helpers\ArrayHelper::merge(parent::rules(), [ 139 | // other rules here 140 | ]); 141 | } 142 | } 143 | ``` 144 | 145 | ```php 146 | class CreditWorkLog extends \tecnocen\workflow\models\WorkLog 147 | { 148 | public static function processClass() 149 | { 150 | return Credit::class; 151 | } 152 | } 153 | ``` 154 | 155 | #### Worklog Resource 156 | ---------------- 157 | 158 | Each process gets a worklog about the flow of stages it goes through. 159 | 160 | On ROA you can declare each worklog as a child resource for the process resource 161 | 162 | ```php 163 | public $resources = [ 164 | 'credit', 165 | 'credit//worklog' => [ 166 | 'class' => WorklogResource::class, 167 | 'modelClass' => CreditWorklog::class, 168 | ] 169 | ]; 170 | ``` 171 | 172 | ## Running the tests 173 | 174 | This library contains tools to set up a testing environment using composer scripts, for more information see [Testing Environment](https://github.com/tecnocen-com/yii2-workflow/blob/master/CONTRIBUTING.md) section. 175 | 176 | ### Break down into end to end tests 177 | 178 | Once testing environment is setup, run the following commands. 179 | 180 | ``` 181 | composer deploy-tests 182 | ``` 183 | 184 | Run tests. 185 | 186 | ``` 187 | composer run-tests 188 | ``` 189 | 190 | Run tests with coverage. 191 | 192 | ``` 193 | composer run-coverage 194 | ``` 195 | 196 | ## Live Demo 197 | 198 | You can run a live demo on a freshly installed project to help you run testing 199 | or understand the responses returned by the server. The live demo is initialized 200 | with the command. 201 | 202 | ``` 203 | php -S localhost:8000 -t tests/_app 204 | ``` 205 | 206 | Where `:8000` is the port number which can be changed. This allows you call ROA 207 | services on a browser or REST client. 208 | 209 | ## Use Cases 210 | 211 | TO DO 212 | 213 | ## Built With 214 | 215 | * Yii 2: The Fast, Secure and Professional PHP Framework [http://www.yiiframework.com](http://www.yiiframework.com) 216 | 217 | ## Code of Conduct 218 | 219 | Please read [CODE_OF_CONDUCT.md](https://github.com/tecnocen-com/yii2-workflow/blob/master/CODE_OF_CONDUCT.md) for details on our code of conduct. 220 | 221 | ## Contributing 222 | 223 | Please read [CONTRIBUTING.md](https://github.com/tecnocen-com/yii2-workflow/blob/master/CONTRIBUTING.md) for details on the process for submitting pull requests to us. 224 | 225 | ## Versioning 226 | 227 | We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/tecnocen-com/yii2-workflow/tags). 228 | 229 | _Considering [SemVer](http://semver.org/) for versioning rules 9, 10 and 11 talk about pre-releases, they will not be used within the Tecnocen-com._ 230 | 231 | ## Authors 232 | 233 | * [**Angel Guevara**](https://github.com/Faryshta) - *Initial work* - [Tecnocen.com](https://github.com/Tecnocen-com) 234 | * [**Carlos Llamosas**](https://github.com/neverabe) - *Initial work* - [Tecnocen.com](https://github.com/Tecnocen-com) 235 | 236 | See also the list of [contributors](https://github.com/tecnocen-com/yii2-workflow/graphs/contributors) who participated in this project. 237 | 238 | ## License 239 | 240 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 241 | 242 | ## Acknowledgments 243 | 244 | * TO DO - Hat tip to anyone who's code was used 245 | * TO DO - Inspiration 246 | * TO DO - etc 247 | 248 | [![yii2-workflow](https://img.shields.io/badge/Powered__by-Tecnocen.com-orange.svg?style=for-the-badge)](https://www.tecnocen.com/) 249 | -------------------------------------------------------------------------------- /codeception.dist.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | paths: 3 | tests: tests 4 | log: tests/_output 5 | data: tests/_data 6 | helpers: tests/_support 7 | settings: 8 | bootstrap: _bootstrap.php 9 | colors: true 10 | memory_limit: 1024M 11 | modules: 12 | config: 13 | Yii2: 14 | configFile: 'tests/_app/config/test.php' 15 | cleanup: false 16 | coverage: 17 | enabled: true 18 | whitelist: 19 | include: 20 | - src/models/* 21 | - src/roa/* 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tecnocen/yii2-workflow", 3 | "description": "Yii 2 Library to configure workflows", 4 | "keywords": [ 5 | "yii2", 6 | "framework", 7 | "workflow", 8 | "roa" 9 | ], 10 | "type": "yii2-extension", 11 | "license": "MIT", 12 | "minimum-stability": "dev", 13 | "authors": [ 14 | { 15 | "name": "Angel (Faryshta) Guevara", 16 | "email": "aguevara@solmipro.com", 17 | "homepage": "http://tecnocen.com" 18 | }, 19 | { 20 | "name": "Carlos (neverabe) Llamosas", 21 | "email": "carlos@tecnocen.com", 22 | "homepage": "http://tecnocen.com" 23 | } 24 | ], 25 | "repositories": [ 26 | { 27 | "type": "composer", 28 | "url": "https://asset-packagist.org" 29 | } 30 | ], 31 | "require": { 32 | "php": "^7.1", 33 | "tecnocen/yii2-roa": "~0.5.0", 34 | "tecnocen/yii2-rmdb": "*", 35 | "yii2tech/ar-softdelete": "*" 36 | }, 37 | "require-dev": { 38 | "codeception/base": "^2.2.11", 39 | "codeception/verify": "~1.0.0", 40 | "flow/jsonpath": "~0.3", 41 | "phpunit/php-code-coverage": "5.3.*", 42 | "yiisoft/yii2-debug": "*" 43 | }, 44 | "scripts": { 45 | "deploy-tests": [ 46 | "@composer update --prefer-stable", 47 | "@php tests/_app/yii.php migrate 1 -p=@app/migrations --interactive=0", 48 | "@php tests/_app/yii.php migrate -p=@tecnocen/oauth2server/migrations/tables --interactive=0", 49 | "@php tests/_app/yii.php migrate -p=@tecnocen/workflow/migrations --interactive=0", 50 | "@php tests/_app/yii.php migrate -p=@app/migrations --interactive=0", 51 | "@php tests/_app/yii.php migrate -p=@yii/rbac/migrations --interactive=0" 52 | ], 53 | "run-tests": [ 54 | "@php vendor/bin/codecept run --steps" 55 | ], 56 | "run-coverage": [ 57 | "@php vendor/bin/codecept run --steps --coverage --coverage-xml" 58 | ] 59 | }, 60 | "autoload": { 61 | "psr-4": { 62 | "tecnocen\\workflow\\": "src/" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tecnocen-com/yii2-workflow/536f489c6c602c52fdb3184307ac4df8a67e514c/diagram.png -------------------------------------------------------------------------------- /src/migrations/Assignment.php: -------------------------------------------------------------------------------- 1 | getProcessTableName() . $this->assignmentSuffix; 26 | } 27 | 28 | /** 29 | * @inhertidoc 30 | */ 31 | public function columns() 32 | { 33 | return [ 34 | 'process_id' => $this->normalKey(), 35 | 'user_id' => $this->normalKey(), 36 | ]; 37 | } 38 | 39 | /** 40 | * @inhertidoc 41 | */ 42 | public function foreignKeys() 43 | { 44 | return [ 45 | 'process_id' => $this->getProcessTableName(), 46 | ]; 47 | } 48 | 49 | /** 50 | * @inhertidoc 51 | */ 52 | public function compositePrimaryKeys() 53 | { 54 | return ['process_id', 'user_id']; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/migrations/WorkLog.php: -------------------------------------------------------------------------------- 1 | getProcessTableName() . $this->worklogSuffix; 28 | } 29 | 30 | /** 31 | * @inhertidoc 32 | */ 33 | public function columns() 34 | { 35 | return [ 36 | 'id' => $this->primaryKey(), 37 | 'process_id' => $this->normalKey(), 38 | 'stage_id' => $this->normalKey(), 39 | 'commentary' => $this->text(), 40 | ]; 41 | } 42 | 43 | /** 44 | * @inhertidoc 45 | */ 46 | public function foreignKeys() 47 | { 48 | return [ 49 | 'stage_id' => 'workflow_stage', 50 | 'process_id' => $this->getProcessTableName(), 51 | ]; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/migrations/m170101_000001_workflow.php: -------------------------------------------------------------------------------- 1 | $this->primaryKey(), 20 | 'name' => $this->string(64)->unique()->notNull(), 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/migrations/m170101_000002_workflow_stage.php: -------------------------------------------------------------------------------- 1 | $this->primaryKey(), 20 | 'workflow_id' => $this->normalKey(), 21 | 'position_x' => $this->integer()->notNull()->defaultValue(0), 22 | 'position_y' => $this->integer()->notNull()->defaultValue(0), 23 | 'name' => $this->string(64)->notNull(), 24 | 'initial' => $this->activable(false), 25 | ]; 26 | } 27 | 28 | /** 29 | * @inhertidoc 30 | */ 31 | public function foreignKeys() 32 | { 33 | return ['workflow_id' => 'workflow']; 34 | } 35 | 36 | /** 37 | * @inhertidoc 38 | */ 39 | public function compositeUniqueKeys() 40 | { 41 | return [['workflow_id', 'name']]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/migrations/m170101_000003_workflow_transition.php: -------------------------------------------------------------------------------- 1 | $this->normalKey(), 20 | 'target_stage_id' => $this->normalKey(), 21 | 'name' => $this->string(64)->notNull(), 22 | ]; 23 | } 24 | 25 | /** 26 | * @inhertidoc 27 | */ 28 | public function foreignKeys() 29 | { 30 | return [ 31 | 'source_stage_id' => 'workflow_stage', 32 | 'target_stage_id' => 'workflow_stage', 33 | ]; 34 | } 35 | 36 | /** 37 | * @inhertidoc 38 | */ 39 | public function compositePrimaryKeys() 40 | { 41 | return ['source_stage_id', 'target_stage_id']; 42 | } 43 | 44 | /** 45 | * @inhertidoc 46 | */ 47 | public function compositeUniqueKeys() 48 | { 49 | return [['source_stage_id', 'name']]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/migrations/m170101_000004_workflow_transition_permission.php: -------------------------------------------------------------------------------- 1 | $this->normalKey(), 21 | 'target_stage_id' => $this->normalKey(), 22 | 'permission' => $this->string()->notNull(), 23 | ]; 24 | } 25 | 26 | /** 27 | * @inhertidoc 28 | */ 29 | public function foreignKeys() 30 | { 31 | return [ 32 | 'transition' => [ 33 | 'table' => 'workflow_transition', 34 | 'columns' => [ 35 | 'source_stage_id' => 'source_stage_id', 36 | 'target_stage_id' => 'target_stage_id', 37 | ] 38 | ], 39 | ]; 40 | } 41 | 42 | /** 43 | * @inhertidoc 44 | */ 45 | public function compositePrimaryKeys() 46 | { 47 | return ['source_stage_id', 'target_stage_id', 'permission']; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/models/Assignment.php: -------------------------------------------------------------------------------- 1 | ['process_id' => 'id'], 34 | 'targetClass' => $this->processClass(), 35 | ], 36 | [ 37 | ['user_id'], 38 | 'exist', 39 | 'targetAttribute' => ['user_id' => 'id'], 40 | 'targetClass' => Yii::$app->user->identityClass, 41 | ], 42 | ]; 43 | } 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function attributeLabels() 49 | { 50 | return array_merge([ 51 | 'process_id' => 'Process ID', 52 | 'user_id' => 'User ID', 53 | ], parent::attributeLabels()); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | public function init() 60 | { 61 | if (!is_subclass_of($this->processClass(), Process::class)) { 62 | throw new InvalidConfigException( 63 | static::class . '::processClass() must extend ' 64 | . Process::class 65 | ); 66 | } 67 | parent::init(); 68 | } 69 | 70 | /** 71 | * @return ActiveQuery 72 | */ 73 | public function getProcess(): ActiveQuery 74 | { 75 | return $this->hasOne($this->processClass(), ['id' => 'process_id']); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/models/Process.php: -------------------------------------------------------------------------------- 1 | autogenerateInitialWorklog || !$this->isNewRecord) { 68 | return false; 69 | } 70 | if (null === $this->initialWorkLog) { 71 | $this->initialWorkLog = $this->ensureWorkLog([ 72 | 'scenario' => WorkLog::SCENARIO_INITIAL, 73 | ]); 74 | } 75 | 76 | return true; 77 | } 78 | 79 | /** 80 | * @inheritdoc 81 | */ 82 | public function load($data, $formName = null) 83 | { 84 | if ($this->hasInitialWorklog()) { 85 | $logLoad = $this->initialWorkLog->load($data, $formName); 86 | 87 | return parent::load($data, $formName) || $logLoad; 88 | } 89 | 90 | return parent::load($data, $formName); 91 | } 92 | 93 | /** 94 | * Whether the user is asigned to the process. 95 | * 96 | * @param ?int $userId 97 | * @return bool 98 | */ 99 | public function userAssigned(?int $userId): bool 100 | { 101 | return !$this->getAssignments()->exists() || $this->getAssignments() 102 | ->andWhere(['user_id' => $userId]); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public function validate($attributeNames = null, $clearErrors = true) 109 | { 110 | $parentValidate = parent::validate($attributeNames, $clearErrors); 111 | if ($this->hasInitialWorklog()) { 112 | if ($this->initialWorkLog->validate( 113 | $attributeNames, 114 | $clearErrors 115 | )) { 116 | return $parentValidate; 117 | } 118 | 119 | $this->addErrors($this->initialWorkLog->getErrors()); 120 | 121 | return false; 122 | } 123 | 124 | return $parentValidate; 125 | } 126 | 127 | /** 128 | * @inheritdoc 129 | */ 130 | public function transactions() 131 | { 132 | return [ 133 | self::SCENARIO_DEFAULT => self::OP_INSERT, 134 | ]; 135 | } 136 | 137 | /** 138 | * @inheritdoc 139 | */ 140 | public function afterSave($insert, $changedAttributes) 141 | { 142 | parent::afterSave($insert, $changedAttributes); 143 | 144 | if ($insert) { 145 | $this->initialWorkLog->process_id = $this->id; 146 | $this->initialWorkLog->save(false); // skip validation 147 | } 148 | } 149 | 150 | /** 151 | * @inheritdoc 152 | */ 153 | public function init() 154 | { 155 | if (!is_subclass_of($this->workLogClass(), WorkLog::class)) { 156 | throw new InvalidConfigException( 157 | static::class . '::workLogClass() must extend ' 158 | . WorkLog::class 159 | ); 160 | } 161 | if (!is_subclass_of($this->assignmentClass(), Assignment::class)) { 162 | throw new InvalidConfigException( 163 | static::class . '::assignmentClass() must extend ' 164 | . Assignment::class 165 | ); 166 | } 167 | parent::init(); 168 | } 169 | 170 | /** 171 | * @return Workflow 172 | */ 173 | public function getWorkflow(): Workflow 174 | { 175 | $workflowClass = $this->workflowClass(); 176 | 177 | return $workflowClass::findOne($this->getWorkflowId()); 178 | } 179 | 180 | /** 181 | * @return ActiveQuery 182 | */ 183 | public function getAssignments(): ActiveQuery 184 | { 185 | return $this->hasMany($this->assignmentClass(), [ 186 | 'process_id' => 'id', 187 | ])->inverseOf('process'); 188 | } 189 | 190 | /** 191 | * @return ActiveQuery 192 | */ 193 | public function getWorkLogs(): ActiveQuery 194 | { 195 | return $this->hasMany($this->workLogClass(), [ 196 | 'process_id' => 'id', 197 | ])->inverseOf('process'); 198 | } 199 | 200 | /** 201 | * @return ActiveQuery 202 | * @see https://dev.mysql.com/doc/refman/5.7/en/example-maximum-column-group-row.html 203 | */ 204 | public function getActiveWorkLog(): ActiveQuery 205 | { 206 | $query = $this->getWorkLogs()->alias('WorkLog'); 207 | $query->multiple = false; 208 | $workLogClass = $this->workLogClass(); 209 | 210 | return $query->andWhere([ 211 | 'id' => $workLogClass::find() 212 | ->select(['MAX(id)']) 213 | ->alias('WorkLog_groupwise') 214 | ->andWhere('WorkLog.process_id = WorkLog_groupwise.process_id') 215 | ]); 216 | } 217 | 218 | /** 219 | * Adds record to the WorkLog effectively transitioning the stage of the 220 | * process. 221 | * 222 | * @param array|WorkLog the WorkLog the process will transit to or an array 223 | * to create said WorkLog. 224 | * @param bool $runValidation 225 | */ 226 | public function flow(&$workLog, bool $runValidation = true) 227 | { 228 | $workLog = $this->ensureWorkLog($workLog); 229 | $workLog->scenario = WorkLog::SCENARIO_FLOW; 230 | 231 | return $workLog->save($runValidation); 232 | } 233 | 234 | /** 235 | * Ensures that the provided parameter is either an array to create a valid 236 | * instance of the `workLogClass()` class. 237 | * 238 | * @param array|WorkLog $workLog 239 | * @return WorkLog extending the class defined in `workLogClass()` 240 | */ 241 | private function ensureWorkLog($workLog): WorkLog 242 | { 243 | $workLogClass = $this->workLogClass(); 244 | if (is_array($workLog)) { 245 | $workLog = new $workLogClass($workLog); 246 | } elseif (!$workLog instanceof $workLogClass) { 247 | throw new InvalidParamException( 248 | "Parameter must be an instance of {$workLogClass} or an " 249 | . 'array to configure an instance' 250 | ); 251 | } 252 | 253 | $workLog->process_id = $this->id; 254 | $workLog->populateRelation('process', $this); 255 | 256 | return $workLog; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/models/Stage.php: -------------------------------------------------------------------------------- 1 | 'integer', 49 | 'workflow_id' => 'integer', 50 | 'initial' => 'boolean', 51 | ]; 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | public function rules() 58 | { 59 | return [ 60 | [['workflow_id', 'name'], 'required'], 61 | [['initial'], 'default', 'value' => false], 62 | [['initial'], 'boolean'], 63 | [ 64 | ['workflow_id', 'position_x', 'position_y'], 65 | 'integer', 66 | 'min' => 0, 67 | ], 68 | [ 69 | ['workflow_id'], 70 | 'exist', 71 | 'skipOnError' => true, 72 | 'targetClass' => Workflow::class, 73 | 'targetAttribute' => ['workflow_id' => 'id'], 74 | ], 75 | [['name'], 'string', 'min' => 6], 76 | [['name'], 'unique', 'targetAttribute' => ['workflow_id', 'name']], 77 | ]; 78 | } 79 | 80 | /** 81 | * @inheritdoc 82 | */ 83 | public function attributeLabels() 84 | { 85 | return array_merge([ 86 | 'id' => 'ID', 87 | 'name' => 'Stage name', 88 | 'workflow_id' => 'Workflow ID', 89 | 'position_x' => 'Position X', 90 | 'position_y' => 'Position Y', 91 | ], parent::attributeLabels()); 92 | } 93 | 94 | /** 95 | * @return ActiveQuery 96 | */ 97 | public function getWorkflow(): ActiveQuery 98 | { 99 | return $this->hasOne( 100 | $this->workflowClass, 101 | ['id' => 'workflow_id'] 102 | ); 103 | } 104 | 105 | /** 106 | * @return ActiveQuery 107 | */ 108 | public function getTransitions(): ActiveQuery 109 | { 110 | return $this->hasMany( 111 | $this->transitionClass, 112 | ['source_stage_id' => 'id'] 113 | )->inverseOf('sourceStage'); 114 | } 115 | 116 | /** 117 | * @return ActiveQuery 118 | */ 119 | public function getDetailTransitions(): ActiveQuery 120 | { 121 | $query = $this->getTransitions(); 122 | $query->multiple = false; 123 | 124 | return $query->select([ 125 | 'source_stage_id', 126 | 'totalTransitions' => 'count(distinct target_stage_id)', 127 | ])->asArray() 128 | ->inverseOf(null) 129 | ->groupBy('source_stage_id'); 130 | } 131 | 132 | /** 133 | * @return int 134 | */ 135 | public function getTotalTransitions(): int 136 | { 137 | return (int)$this->detailTransitions['totalTransitions']; 138 | } 139 | 140 | /** 141 | * @return ActiveQuery sibling stages for the same workflow 142 | */ 143 | public function getSiblings(): ActiveQuery 144 | { 145 | return $this->hasMany(static::class, ['workflow_id' => 'workflow_id']) 146 | ->alias('siblings'); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/models/Transition.php: -------------------------------------------------------------------------------- 1 | 'integer', 51 | 'target_stage_id' => 'integer', 52 | ]; 53 | } 54 | 55 | /** 56 | * @inheritdoc 57 | */ 58 | public function rules() 59 | { 60 | return [ 61 | [ 62 | ['!source_stage_id', '!target_stage_id'], 63 | 'safe', 64 | 'on' => [self::SCENARIO_UPDATE], 65 | ], 66 | [['name'], 'string', 'min' => 6], 67 | [['source_stage_id', 'target_stage_id', 'name'], 'required'], 68 | [ 69 | ['source_stage_id', 'target_stage_id'], 70 | 'integer', 71 | 'on' => [self::SCENARIO_CREATE], 72 | ], 73 | [ 74 | ['source_stage_id'], 75 | 'exist', 76 | 'targetClass' => Stage::class, 77 | 'targetAttribute' => ['source_stage_id' => 'id'], 78 | 'skipOnError' => true, 79 | 'on' => [self::SCENARIO_CREATE], 80 | ], 81 | [ 82 | ['target_stage_id'], 83 | 'exist', 84 | 'targetClass' => Stage::class, 85 | 'targetAttribute' => ['target_stage_id' => 'id'], 86 | 'skipOnError' => true, 87 | 'on' => [self::SCENARIO_CREATE], 88 | ], 89 | [ 90 | ['target_stage_id'], 91 | 'exist', 92 | 'targetAttribute' => [ 93 | 'target_stage_id' => 'id', 94 | ], 95 | 'targetClass' => Stage::class, 96 | 'skipOnError' => true, 97 | 'when' => function () { 98 | return !$this->hasErrors('source_stage_id'); 99 | }, 100 | 'filter' => function ($query) { 101 | $query->innerJoinWith(['siblings'])->andWhere([ 102 | 'siblings.id' => $this->source_stage_id 103 | ]); 104 | }, 105 | 'on' => [self::SCENARIO_CREATE], 106 | 'message' => 'The stages are not associated to the same workflow.', 107 | ], 108 | 109 | [ 110 | ['target_stage_id'], 111 | 'unique', 112 | 'targetAttribute' => ['source_stage_id', 'target_stage_id'], 113 | 'on' => [self::SCENARIO_CREATE], 114 | 'message' => 'Target already in use for the source stage.' 115 | ], 116 | [ 117 | ['name'], 118 | 'unique', 119 | 'targetAttribute' => ['source_stage_id', 'name'], 120 | 'message' => 'Name already used for the source stage.' 121 | ], 122 | ]; 123 | } 124 | 125 | /** 126 | * Whether the user can execute the transition. 127 | * 128 | * @param int $userId 129 | * @param Process $process 130 | * @return bool 131 | */ 132 | public function userCan(?int $userId, Process $process): bool 133 | { 134 | if (!$this->getPermissions()->exists()) { 135 | return true; 136 | } 137 | 138 | $authManager = Yii::$app->authManager; 139 | 140 | foreach ($this->permissions as $permission) { 141 | if (!$authManager->checkAccess($userId, $permission->permission, [ 142 | 'transition' => $this, 143 | 'process' => $process 144 | ])) { 145 | return false; 146 | } 147 | } 148 | 149 | return true; 150 | } 151 | 152 | /** 153 | * @inheritdoc 154 | */ 155 | public function attributeLabels() 156 | { 157 | return array_merge([ 158 | 'source_stage_id' => 'Source Stage ID', 159 | 'target_stage_id' => 'Target Stage ID', 160 | 'name' => 'Transition Name', 161 | ], parent::attributeLabels()); 162 | } 163 | 164 | /** 165 | * @return ActiveQuery 166 | */ 167 | public function getSourceStage(): ActiveQuery 168 | { 169 | return $this->hasOne($this->stageClass, ['id' => 'source_stage_id']); 170 | } 171 | 172 | /** 173 | * @return ActiveQuery 174 | */ 175 | public function getTargetStage(): ActiveQuery 176 | { 177 | return $this->hasOne($this->stageClass, ['id' => 'target_stage_id']); 178 | } 179 | 180 | /** 181 | * @return ActiveQuery 182 | */ 183 | public function getPermissions(): ActiveQuery 184 | { 185 | return $this->hasMany( 186 | $this->permissionClass, 187 | [ 188 | 'source_stage_id' => 'source_stage_id', 189 | 'target_stage_id' => 'target_stage_id', 190 | ] 191 | )->inverseOf('transition'); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/models/TransitionPermission.php: -------------------------------------------------------------------------------- 1 | 'integer', 50 | 'target_stage_id' => 'integer', 51 | ]; 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | public function rules() 58 | { 59 | return [ 60 | [ 61 | ['!source_stage_id', '!target_stage_id', '!permission'], 62 | 'safe', 63 | 'on' => [self::SCENARIO_UPDATE], 64 | ], 65 | [['source_stage_id', 'target_stage_id', 'permission'], 'required'], 66 | [ 67 | ['source_stage_id', 'target_stage_id'], 68 | 'integer', 69 | 'on' => [self::SCENARIO_CREATE], 70 | ], 71 | [ 72 | ['permission'], 73 | 'string', 74 | 'min' => 6, 75 | 'on' => [self::SCENARIO_CREATE], 76 | ], 77 | 78 | [ 79 | ['target_stage_id'], 80 | 'exist', 81 | 'targetClass' => Transition::class, 82 | 'targetAttribute' => [ 83 | 'source_stage_id' => 'source_stage_id', 84 | 'target_stage_id' => 'target_stage_id', 85 | ], 86 | 'skipOnError' => true, 87 | 'when' => function () { 88 | return !$this->hasErrors('source_stage_id'); 89 | }, 90 | 'message' => 'There is no transaction between the stages.', 91 | 'on' => [self::SCENARIO_CREATE], 92 | ], 93 | 94 | [ 95 | ['permission'], 96 | 'unique', 97 | 'targetAttribute' => [ 98 | 'source_stage_id', 99 | 'target_stage_id', 100 | 'permission', 101 | ], 102 | 'message' => 'Permission already set for the transition.', 103 | 'on' => [self::SCENARIO_CREATE], 104 | ], 105 | ]; 106 | } 107 | 108 | /** 109 | * @inheritdoc 110 | */ 111 | public function attributeLabels() 112 | { 113 | return array_merge([ 114 | 'source_stage_id' => 'Source Stage ID', 115 | 'target_stage_id' => 'Target Stage ID', 116 | 'permission' => 'Permission', 117 | ], parent::attributeLabels()); 118 | } 119 | 120 | /** 121 | * @return ActiveQuery 122 | */ 123 | public function getSourceStage(): ActiveQuery 124 | { 125 | return $this->hasOne($this->stageClass, ['id' => 'source_stage_id']); 126 | } 127 | 128 | /** 129 | * @return ActiveQuery 130 | */ 131 | public function getTargetStage(): ActiveQuery 132 | { 133 | return $this->hasOne($this->stageClass, ['id' => 'target_stage_id']); 134 | } 135 | 136 | /** 137 | * @return ActiveQuery 138 | */ 139 | public function getTransition(): ActiveQuery 140 | { 141 | return $this->hasOne( 142 | $this->transitionClass, 143 | [ 144 | 'source_stage_id' => 'source_stage_id', 145 | 'target_stage_id' => 'target_stage_id', 146 | ] 147 | ); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/models/WorkLog.php: -------------------------------------------------------------------------------- 1 | [self::SCENARIO_INITIAL]], 35 | [['stage_id'], 'required'], 36 | [['process_id', 'stage_id'], 'integer'], 37 | [ 38 | ['process_id'], 39 | 'exist', 40 | 'targetAttribute' => ['process_id' => 'id'], 41 | 'targetClass' => $this->processClass(), 42 | 'except' => [self::SCENARIO_INITIAL], 43 | ], 44 | [ 45 | ['stage_id'], 46 | 'exist', 47 | 'targetAttribute' => ['stage_id' => 'id'], 48 | 'targetClass' => Stage::class, 49 | 'filter' => function ($query) { 50 | $query->andWhere(['initial' => true]); 51 | }, 52 | 'on' => [self::SCENARIO_INITIAL], 53 | 'message' => 'Not an initial stage for the workflow.' 54 | ], 55 | [ 56 | ['stage_id'], 57 | 'exist', 58 | 'targetAttribute' => ['stage_id' => 'target_stage_id'], 59 | 'targetClass' => Transition::class, 60 | 'filter' => function ($query) { 61 | $query->andWhere([ 62 | 'source_stage_id' => $this->process->activeWorkLog 63 | ->stage_id 64 | ]); 65 | }, 66 | 'when' => function () { 67 | return !$this->hasErrors('process_id') 68 | && null !== $this->process->activeWorkLog; 69 | }, 70 | 'on' => [self::SCENARIO_FLOW], 71 | 'message' => 'There is no transition for the current stage' 72 | ], 73 | ]; 74 | } 75 | 76 | /** 77 | * @inheritdoc 78 | */ 79 | public function beforeSave($insert) 80 | { 81 | if (!parent::beforeSave($insert)) { 82 | return false; 83 | } 84 | 85 | if (!$insert || $this->scenario == self::SCENARIO_INITIAL) { 86 | // editing a worklog or creating initial worklog 87 | return true; 88 | } 89 | 90 | $transition = Transition::find()->andWhere([ 91 | 'source_stage_id' => $this->process->activeWorkLog->stage_id, 92 | 'target_stage_id' => $this->stage_id, 93 | ])->one(); 94 | 95 | if (!isset($transition)) { 96 | throw new BadRequestHttpException('Stage not transitionable.'); 97 | } 98 | 99 | if (!$this->process->userAssigned(Yii::$app->user->id)) { 100 | throw new ForbiddenHttpException( 101 | 'You are not assigned to move this process.' 102 | ); 103 | } 104 | 105 | if (!$transition->userCan(Yii::$app->user->id, $this->process)) { 106 | throw new ForbiddenHttpException( 107 | 'You dont have permission for the transition.' 108 | ); 109 | } 110 | 111 | unset($this->process->activeWorkLog); 112 | 113 | return true; 114 | } 115 | 116 | /** 117 | * @inheritdoc 118 | */ 119 | public function attributeLabels() 120 | { 121 | return array_merge([ 122 | 'id' => 'ID', 123 | 'process_id' => 'Process ID', 124 | 'stage_id' => 'Stage ID', 125 | ], parent::attributeLabels()); 126 | } 127 | 128 | /** 129 | * @inheritdoc 130 | */ 131 | public function init() 132 | { 133 | if (!is_subclass_of($this->processClass(), Process::class)) { 134 | throw new InvalidConfigException( 135 | static::class . '::processClass() must extend ' 136 | . Process::class 137 | ); 138 | } 139 | parent::init(); 140 | } 141 | /** 142 | * @return ActiveQuery 143 | */ 144 | public function getProcess(): ActiveQuery 145 | { 146 | return $this->hasOne($this->processClass(), ['id' => 'process_id']); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/models/Workflow.php: -------------------------------------------------------------------------------- 1 | 'integer']; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function rules() 43 | { 44 | return [ 45 | [['name'], 'required'], 46 | [['name'], 'string', 'min' => 6], 47 | [['name'], 'unique'], 48 | ]; 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function attributeLabels() 55 | { 56 | return array_merge([ 57 | 'id' => 'ID', 58 | 'name' => 'Workflow name', 59 | ], parent::attributeLabels()); 60 | } 61 | 62 | /** 63 | * @return ActiveQuery 64 | */ 65 | public function getStages(): ActiveQuery 66 | { 67 | return $this->hasMany($this->stageClass, ['workflow_id' => 'id']) 68 | ->inverseOf('workflow'); 69 | } 70 | 71 | /** 72 | * @return ActiveQuery 73 | */ 74 | public function getDetailStages(): ActiveQuery 75 | { 76 | $query = $this->getStages(); 77 | $query->multiple = false; 78 | 79 | return $query->select([ 80 | 'workflow_id', 81 | 'totalStages' => 'count(distinct id)', 82 | ])->asArray() 83 | ->inverseOf(null) 84 | ->groupBy('workflow_id'); 85 | } 86 | 87 | /** 88 | * @return int 89 | */ 90 | public function getTotalStages(): int 91 | { 92 | return (int)$this->detailStages['totalStages']; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/roa/models/Stage.php: -------------------------------------------------------------------------------- 1 | 'stage', 35 | 'parentSlugRelation' => 'workflow', 36 | ]; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function getLinks() 43 | { 44 | return array_merge($this->getContractLinks(), [ 45 | 'transitions' => $this->getSelfLink() . '/transition', 46 | ]); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | public function extraFields() 53 | { 54 | return [ 55 | 'workflow', 56 | 'transitions', 57 | 'detailTransitions', 58 | 'totalTransitions', 59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/roa/models/StageSearch.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class StageSearch extends Stage implements \tecnocen\roa\ResourceSearch 15 | { 16 | /** 17 | * @inhertidoc 18 | */ 19 | public function rules() 20 | { 21 | return [ 22 | [['workflow_id', 'created_by'], 'integer'], 23 | [['name'], 'string'], 24 | ]; 25 | } 26 | 27 | /** 28 | * @inhertidoc 29 | */ 30 | public function search( 31 | array $params, 32 | ?string $formName = '' 33 | ): ?ActiveDataProvider { 34 | $this->load($params, $formName); 35 | if (!$this->validate()) { 36 | return null; 37 | } 38 | 39 | if (null === $this->workflow) { 40 | throw new NotFoundHttpException('Unexistant workflow path.'); 41 | } 42 | 43 | $class = get_parent_class(); 44 | return new ActiveDataProvider([ 45 | 'query' => $class::find()->andFilterWhere([ 46 | 'created_by' => $this->created_by, 47 | 'workflow_id' => $this->workflow_id, 48 | ]) 49 | ->andFilterWhere(['like', 'name', $this->name]), 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/roa/models/Transition.php: -------------------------------------------------------------------------------- 1 | 'transition', 35 | 'parentSlugRelation' => 'sourceStage', 36 | 'idAttribute' => 'target_stage_id', 37 | ]; 38 | } 39 | 40 | /** 41 | * @inheritdoc 42 | */ 43 | public function getLinks() 44 | { 45 | return array_merge($this->getContractLinks(), [ 46 | 'permissions' => $this->getSelfLink() . '/permission', 47 | 'target_stage' => $this->targetStage->getSelfLink(), 48 | ]); 49 | } 50 | 51 | /** 52 | * @inheritdoc 53 | */ 54 | public function extraFields() 55 | { 56 | return ['sourceStage', 'targetStage', 'permissions']; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/roa/models/TransitionPermission.php: -------------------------------------------------------------------------------- 1 | 'permission', 33 | 'resourceName' => 'permission', 34 | 'parentSlugRelation' => 'transition', 35 | ]; 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | public function extraFields() 42 | { 43 | return ['sourceStage', 'targetStage', 'transition']; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/roa/models/TransitionPermissionSearch.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TransitionPermissionSearch extends TransitionPermission implements 15 | ResourceSearch 16 | { 17 | /** 18 | * @inhertidoc 19 | */ 20 | public function rules() 21 | { 22 | return [ 23 | [['created_by', 'source_stage_id', 'target_stage_id'], 'integer'], 24 | [['permission'], 'string'], 25 | ]; 26 | } 27 | 28 | /** 29 | * @inhertidoc 30 | */ 31 | public function search( 32 | array $params, 33 | ?string $formName = '' 34 | ): ?ActiveDataProvider { 35 | $this->load($params, $formName); 36 | if (!$this->validate()) { 37 | return null; 38 | } 39 | if (null === $this->transition 40 | || $this->sourceStage->workflow_id != $params['workflow_id'] 41 | ) { 42 | throw new NotFoundHttpException('Unexistant permission path.'); 43 | } 44 | 45 | $class = get_parent_class(); 46 | return new ActiveDataProvider([ 47 | 'query' => $class::find()->andFilterWhere([ 48 | 'target_stage_id' => $this->target_stage_id, 49 | 'source_stage_id' => $this->source_stage_id, 50 | ]) 51 | ->andFilterWhere(['like', 'permission', $this->permission]), 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/roa/models/TransitionSearch.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TransitionSearch extends Transition implements ResourceSearch 15 | { 16 | 17 | /** 18 | * @inhertidoc 19 | */ 20 | public function rules() 21 | { 22 | return [ 23 | [['source_stage_id', 'created_by'], 'integer'], 24 | [['name'], 'string'], 25 | ]; 26 | } 27 | 28 | /** 29 | * @inhertidoc 30 | */ 31 | public function search( 32 | array $params, 33 | ?string $formName = '' 34 | ): ?ActiveDataProvider { 35 | $this->load($params, $formName); 36 | if (!$this->validate()) { 37 | return null; 38 | } 39 | if (null === $this->sourceStage 40 | || $this->sourceStage->workflow_id != $params['workflow_id'] 41 | ) { 42 | throw new NotFoundHttpException('Unexistant stage path.'); 43 | } 44 | 45 | $class = get_parent_class(); 46 | return new ActiveDataProvider([ 47 | 'query' => $class::find()->andFilterWhere([ 48 | 'source_stage_id' => $this->source_stage_id, 49 | 'created_by' => $this->created_by, 50 | ]) 51 | ->andFilterWhere(['like', 'name', $this->name]), 52 | ]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/roa/models/Workflow.php: -------------------------------------------------------------------------------- 1 | 'workflow', 31 | 'checkAccess' => function ($params) { 32 | if (isset($params['workflow_id']) 33 | && $this->id != $params['workflow_id'] 34 | ) { 35 | throw new NotFoundHttpException( 36 | 'Workflow not associated to element.' 37 | ); 38 | } 39 | }, 40 | ]; 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function getLinks() 47 | { 48 | return array_merge($this->getContractLinks(), [ 49 | 'stages' => $this->getSelfLink() . '/stage', 50 | ]); 51 | } 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function extraFields() 57 | { 58 | return ['stages', 'detailStages', 'totalStages']; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/roa/models/WorkflowSearch.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class WorkflowSearch extends Workflow implements ResourceSearch 14 | { 15 | /** 16 | * @inhertidoc 17 | */ 18 | public function rules() 19 | { 20 | return [ 21 | [['created_by'], 'integer'], 22 | [['name'], 'string'], 23 | ]; 24 | } 25 | 26 | /** 27 | * @inhertidoc 28 | */ 29 | public function search( 30 | array $params, 31 | ?string $formName = '' 32 | ): ?ActiveDataProvider { 33 | $this->load($params, $formName); 34 | if (!$this->validate()) { 35 | return null; 36 | } 37 | 38 | $class = get_parent_class(); 39 | return new ActiveDataProvider([ 40 | 'query' => $class::find()->andFilterWhere([ 41 | 'created_by' => $this->created_by, 42 | ]) 43 | ->andFilterWhere(['like', 'name', $this->name]), 44 | ]); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/roa/modules/Version.php: -------------------------------------------------------------------------------- 1 | /stage'; 16 | const TRANSITION_ROUTE = self::STAGE_ROUTE . '//transition'; 17 | const PERMISSION_ROUTE = self::TRANSITION_ROUTE . '//permission'; 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public $resources = [ 23 | self::WORKFLOW_ROUTE => ['class' => WorkflowResource::class], 24 | self::STAGE_ROUTE => ['class' => StageResource::class], 25 | self::TRANSITION_ROUTE => ['class' => TransitionResource::class], 26 | self::PERMISSION_ROUTE => [ 27 | 'class' => PermissionResource::class, 28 | 'urlRule' => ['tokens' => ['{id}' => '']], 29 | ], 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /src/roa/resources/PermissionResource.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class PermissionResource extends Resource 15 | { 16 | /** 17 | * @inheritdoc 18 | */ 19 | public $modelClass = TransitionPermission::class; 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | public $searchClass = TransitionPermissionSearch::class; 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public $idAttribute = 'permission'; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | public $createScenario = TransitionPermission::SCENARIO_CREATE; 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public $updateScenario = TransitionPermission::SCENARIO_UPDATE; 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public $filterParams = ['source_stage_id', 'target_stage_id']; 45 | } 46 | -------------------------------------------------------------------------------- /src/roa/resources/StageResource.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class StageResource extends Resource 15 | { 16 | /** 17 | * @inheritdoc 18 | */ 19 | public $modelClass = Stage::class; 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | public $searchClass = StageSearch::class; 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public $filterParams = ['workflow_id']; 30 | } 31 | -------------------------------------------------------------------------------- /src/roa/resources/TransitionResource.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TransitionResource extends Resource 15 | { 16 | /** 17 | * @inhertidoc 18 | */ 19 | public $modelClass = Transition::class; 20 | 21 | /** 22 | * @inhertidoc 23 | */ 24 | public $searchClass = TransitionSearch::class; 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public $idAttribute = 'target_stage_id'; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | public $filterParams = ['source_stage_id']; 35 | 36 | /** 37 | * @inheritdoc 38 | */ 39 | public $createScenario = Transition::SCENARIO_CREATE; 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public $updateScenario = Transition::SCENARIO_UPDATE; 45 | } 46 | -------------------------------------------------------------------------------- /src/roa/resources/WorkflowResource.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class WorkflowResource extends Resource 15 | { 16 | /** 17 | * @inheritdoc 18 | */ 19 | public $modelClass = Workflow::class; 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | public $searchClass = WorkflowSearch::class; 25 | } 26 | -------------------------------------------------------------------------------- /tests/_app/api/models/Credit.php: -------------------------------------------------------------------------------- 1 | 'credit', 41 | ]; 42 | } 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | public function getLinks() 48 | { 49 | return array_merge($this->getContractLinks(), [ 50 | 'worklog' => $this->getSelfLink() . '/worklog', 51 | ]); 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | public function extraFields() 58 | { 59 | return [ 60 | 'workLogs', 61 | 'activeWorkLog', 62 | ]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/_app/api/models/CreditAssignment.php: -------------------------------------------------------------------------------- 1 | 'assignment', 30 | 'parentSlugRelation' => 'process', 31 | 'idAttribute' => 'user_id' 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/_app/api/models/CreditSearch.php: -------------------------------------------------------------------------------- 1 | load($params, $formName); 33 | if (!$this->validate()) { 34 | return null; 35 | } 36 | 37 | $class = get_parent_class(); 38 | return new ActiveDataProvider([ 39 | 'query' => $class::find()->andFilterWhere([ 40 | 'created_by' => $this->created_by, 41 | ]) 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/_app/api/models/CreditWorklog.php: -------------------------------------------------------------------------------- 1 | 'worklog', 31 | 'parentSlugRelation' => 'process' 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/_app/api/modules/Version.php: -------------------------------------------------------------------------------- 1 | /worklog'; 15 | const ASSIGNMENT_ROUTE = self::CREDIT_ROUTE . '//assignment'; 16 | 17 | /** 18 | * @inheritdoc 19 | */ 20 | public $resources = [ 21 | self::CREDIT_ROUTE => ['class' => CreditResource::class], 22 | self::WORKLOG_ROUTE => ['class' => CreditWorklogResource::class], 23 | self::ASSIGNMENT_ROUTE => ['class' => CreditAssignmentResource::class], 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /tests/_app/api/resources/CreditAssignmentResource.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class CreditAssignmentResource extends Resource 13 | { 14 | /** 15 | * @inheritdoc 16 | */ 17 | public $modelClass = CreditAssignment::class; 18 | /** 19 | * @inheritdoc 20 | */ 21 | public $filterParams = ['process_id']; 22 | /** 23 | * @inheritdoc 24 | */ 25 | public $idAttribute = 'user_id'; 26 | } 27 | -------------------------------------------------------------------------------- /tests/_app/api/resources/CreditResource.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditResource extends Resource 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = Credit::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $searchClass = CreditSearch::class; 24 | } 25 | -------------------------------------------------------------------------------- /tests/_app/api/resources/CreditWorklogResource.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class CreditWorklogResource extends Resource 13 | { 14 | /** 15 | * @inheritdoc 16 | */ 17 | public $createScenario = CreditWorklog::SCENARIO_FLOW; 18 | 19 | /** 20 | * @inheritdoc 21 | */ 22 | public $modelClass = CreditWorklog::class; 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | public $filterParams = ['process_id']; 28 | } 29 | -------------------------------------------------------------------------------- /tests/_app/assets/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/_app/config/.gitignore: -------------------------------------------------------------------------------- 1 | db.local.php 2 | -------------------------------------------------------------------------------- /tests/_app/config/common.php: -------------------------------------------------------------------------------- 1 | dirname(__DIR__), 5 | 'language' => 'en-US', 6 | 'aliases' => [ 7 | '@tests' => dirname(dirname(__DIR__)), 8 | '@tecnocen/workflow' => dirname(dirname(dirname(__DIR__))) . '/src', 9 | '@tecnocen/oauth2server' => VENDOR_DIR . '/tecnocen/yii2-oauth2-server/src', 10 | ], 11 | 'components' => [ 12 | 'db' => require __DIR__ . '/db.php', 13 | 'authManager' => [ 14 | 'class' => yii\rbac\DbManager::class, 15 | ], 16 | ], 17 | ]; 18 | -------------------------------------------------------------------------------- /tests/_app/config/console.php: -------------------------------------------------------------------------------- 1 | 'yii2-test-console', 9 | 'components' => [ 10 | 'log' => null, 11 | 'cache' => null, 12 | ], 13 | 'controllerMap' => [ 14 | 'migrate' => [ 15 | 'class' => controllers\MigrateController::class, 16 | 'migrationPath' => null, 17 | 'migrationNamespaces' => [], 18 | ], 19 | ], 20 | ] 21 | ); 22 | -------------------------------------------------------------------------------- /tests/_app/config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=localhost;dbname=yii2_workflow_test', 6 | 'username' => 'root', 7 | 'password' => '', 8 | 'charset' => 'utf8', 9 | ]; 10 | 11 | if (file_exists(__DIR__ . '/db.local.php')) { 12 | $db = array_merge($db, require(__DIR__ . '/db.local.php')); 13 | } 14 | 15 | return $db; 16 | -------------------------------------------------------------------------------- /tests/_app/config/test.php: -------------------------------------------------------------------------------- 1 | 'yii2-workflow-tests', 14 | 'bootstrap' => ['api'], 15 | 'modules' => [ 16 | 'api' => [ 17 | 'class' => tecnocen\roa\modules\ApiContainer::class, 18 | 'identityClass' => app\models\User::class, 19 | 'versions' => [ 20 | 'w1' => [ 21 | 'class' => WorkflowVersion::class, 22 | ], 23 | 'v1' => [ 24 | 'class' => V1::class, 25 | ], 26 | ], 27 | ], 28 | 'rmdb' => [ 29 | 'class' => tecnocen\rmdb\Module::class, 30 | ], 31 | ], 32 | 'components' => [ 33 | 'mailer' => [ 34 | 'useFileTransport' => true, 35 | ], 36 | 'user' => ['identityClass' => app\models\User::class], 37 | 'urlManager' => [ 38 | 'showScriptName' => true, 39 | 'enablePrettyUrl' => true, 40 | ], 41 | 'request' => [ 42 | 'cookieValidationKey' => 'test', 43 | 'enableCsrfValidation' => false, 44 | ], 45 | ], 46 | 'params' => [], 47 | ] 48 | ); 49 | -------------------------------------------------------------------------------- /tests/_app/config/web.php: -------------------------------------------------------------------------------- 1 | 'yii2-workflow-demo', 7 | 'bootstrap' => ['debug'], 8 | 'aliases' => [ 9 | '@vendor' => VENDOR_DIR, 10 | '@bower' => VENDOR_DIR . '/bower-asset', 11 | ], 12 | 'modules' => [ 13 | 'debug' => [ 14 | 'class' => yii\debug\Module::class, 15 | ], 16 | ], 17 | 'components' => [ 18 | 'assetManager' => [ 19 | 'basePath' => dirname(__DIR__) . '/assets', 20 | ], 21 | ] 22 | ] 23 | ); 24 | -------------------------------------------------------------------------------- /tests/_app/controllers/SiteController.php: -------------------------------------------------------------------------------- 1 | render('index'); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/_app/fixtures/AuthItemFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class AuthItemFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public function init() 19 | { 20 | $this->tableName = \Yii::$app->authManager->itemTable; 21 | parent::init(); 22 | } 23 | 24 | /** 25 | * @inheritdoc 26 | */ 27 | public $dataFile = __DIR__ . '/data/auth_item.php'; 28 | } 29 | -------------------------------------------------------------------------------- /tests/_app/fixtures/CreditAssignmentFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditAssignmentFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = CreditAssignment::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/credit_assignment.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = ['app\fixtures\CreditFixture']; 29 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/CreditFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = Credit::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/credit.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = ['app\fixtures\TransitionPermissionFixture']; 29 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/CreditWorklogFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditWorklogFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = CreditWorklog::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/credit_worklog.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = ['app\fixtures\CreditFixture']; 29 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/OauthAccessTokensFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StageFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = Stage::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/stage.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = ['app\fixtures\WorkflowFixture']; 29 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/TransitionFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class TransitionFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = Transition::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/transition.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = ['app\fixtures\StageFixture']; 29 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/TransitionPermissionFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class TransitionPermissionFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = TransitionPermission::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/transition_permission.php'; 24 | 25 | /** 26 | * @inheritdoc 27 | */ 28 | public $depends = [ 29 | TransitionFixture::class, 30 | AuthItemFixture::class, 31 | ]; 32 | } 33 | -------------------------------------------------------------------------------- /tests/_app/fixtures/UserFixture.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class WorkflowFixture extends ActiveFixture 14 | { 15 | /** 16 | * @inheritdoc 17 | */ 18 | public $modelClass = Workflow::class; 19 | 20 | /** 21 | * @inheritdoc 22 | */ 23 | public $dataFile = __DIR__ . '/data/workflow.php'; 24 | } -------------------------------------------------------------------------------- /tests/_app/fixtures/data/access_tokens.php: -------------------------------------------------------------------------------- 1 | OauthAccessTokensFixture::SIMPLE_TOKEN, 9 | 'client_id' => 'testclient', 10 | 'user_id' => 1, 11 | 'expires' => new DbExpression('NOW() + INTERVAL 24 HOUR'), 12 | ], 13 | ]; 14 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/auth_item.php: -------------------------------------------------------------------------------- 1 | 'admin', 10 | 'type' => Item::TYPE_ROLE, 11 | 'created_at' => $now, 12 | 'updated_at' => $now, 13 | ], 14 | ]; 15 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/clients.php: -------------------------------------------------------------------------------- 1 | 'testclient', 6 | 'client_secret' => 'testpass', 7 | 'redirect_uri' => 'http://fake/', 8 | 'grant_types' => 'client_credentials authorization_code ' 9 | . 'password implicit' 10 | ], 11 | ]; 12 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/credit.php: -------------------------------------------------------------------------------- 1 | 1, 8 | 'created_by' => 1, 9 | 'created_at' => $now, 10 | 'updated_by' => 1, 11 | 'updated_at' => $now, 12 | ], 13 | [ 14 | 'workflow_id' => 1, 15 | 'created_by' => 1, 16 | 'created_at' => $now, 17 | 'updated_by' => 1, 18 | 'updated_at' => $now, 19 | ], 20 | [ 21 | 'workflow_id' => 1, 22 | 'created_by' => 1, 23 | 'created_at' => $now, 24 | 'updated_by' => 1, 25 | 'updated_at' => $now, 26 | ], 27 | [ 28 | 'workflow_id' => 2, 29 | 'created_by' => 1, 30 | 'created_at' => $now, 31 | 'updated_by' => 1, 32 | 'updated_at' => $now, 33 | ], 34 | [ 35 | 'workflow_id' => 2, 36 | 'created_by' => 1, 37 | 'created_at' => $now, 38 | 'updated_by' => 1, 39 | 'updated_at' => $now, 40 | ], 41 | [ 42 | 'workflow_id' => 2, 43 | 'created_by' => 1, 44 | 'created_at' => $now, 45 | 'updated_by' => 1, 46 | 'updated_at' => $now, 47 | ], 48 | [ 49 | 'workflow_id' => 2, 50 | 'created_by' => 1, 51 | 'created_at' => $now, 52 | 'updated_by' => 1, 53 | 'updated_at' => $now, 54 | ], 55 | ]; 56 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/credit_assignment.php: -------------------------------------------------------------------------------- 1 | 1, 8 | 'user_id' => 1, 9 | 'created_by' => 1, 10 | 'created_at' => $now, 11 | ], 12 | ]; 13 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/credit_worklog.php: -------------------------------------------------------------------------------- 1 | 1, 8 | 'stage_id' => 4, 9 | 'commentary' => 'stage 4', 10 | 'created_by' => 1, 11 | 'created_at' => $now, 12 | ], 13 | [ 14 | 'process_id' => 4, 15 | 'stage_id' => 4, 16 | 'commentary' => 'stage 4', 17 | 'created_by' => 1, 18 | 'created_at' => $now, 19 | ], 20 | [ 21 | 'process_id' => 4, 22 | 'stage_id' => 5, 23 | 'commentary' => 'stage 5', 24 | 'created_by' => 1, 25 | 'created_at' => $now, 26 | ], 27 | [ 28 | 'process_id' => 4, 29 | 'stage_id' => 6, 30 | 'commentary' => 'stage 6', 31 | 'created_by' => 1, 32 | 'created_at' => $now, 33 | ], 34 | [ 35 | 'process_id' => 4, 36 | 'stage_id' => 7, 37 | 'commentary' => 'stage 7', 38 | 'created_by' => 1, 39 | 'created_at' => $now, 40 | ], 41 | ]; 42 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/stage.php: -------------------------------------------------------------------------------- 1 | 'Stage 1 - Wf 1', 8 | 'workflow_id' => 1, 9 | 'initial' => true, 10 | 'position_x' => 0, 11 | 'position_y' => 0, 12 | 'created_by' => 1, 13 | 'created_at' => $now, 14 | 'updated_by' => 1, 15 | 'updated_at' => $now, 16 | ], 17 | [ 18 | 'name' => 'Stage 2 - Wf 1', 19 | 'workflow_id' => 1, 20 | 'position_x' => 1, 21 | 'position_y' => 1, 22 | 'created_by' => 1, 23 | 'created_at' => $now, 24 | 'updated_by' => 1, 25 | 'updated_at' => $now, 26 | ], 27 | [ 28 | 'name' => 'Stage 3 - Wf 1', 29 | 'workflow_id' => 1, 30 | 'position_x' => 2, 31 | 'position_y' => 2, 32 | 'created_by' => 1, 33 | 'created_at' => $now, 34 | 'updated_by' => 1, 35 | 'updated_at' => $now, 36 | ], 37 | [ 38 | 'name' => 'Stage 1 - Wf 2', 39 | 'workflow_id' => 2, 40 | 'initial' => true, 41 | 'position_x' => 0, 42 | 'position_y' => 0, 43 | 'created_by' => 1, 44 | 'created_at' => $now, 45 | 'updated_by' => 1, 46 | 'updated_at' => $now, 47 | ], 48 | [ 49 | 'name' => 'Stage 2 - Wf 2', 50 | 'workflow_id' => 2, 51 | 'position_x' => 1, 52 | 'position_y' => 1, 53 | 'created_by' => 1, 54 | 'created_at' => $now, 55 | 'updated_by' => 1, 56 | 'updated_at' => $now, 57 | ], 58 | [ 59 | 'name' => 'Stage 3 - Wf 2', 60 | 'workflow_id' => 2, 61 | 'position_x' => 2, 62 | 'position_y' => 2, 63 | 'created_by' => 1, 64 | 'created_at' => $now, 65 | 'updated_by' => 1, 66 | 'updated_at' => $now, 67 | ], 68 | [ 69 | 'name' => 'Stage 4 - Wf 2', 70 | 'workflow_id' => 2, 71 | 'position_x' => 2, 72 | 'position_y' => 2, 73 | 'created_by' => 1, 74 | 'created_at' => $now, 75 | 'updated_by' => 1, 76 | 'updated_at' => $now, 77 | ], 78 | ]; 79 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/transition.php: -------------------------------------------------------------------------------- 1 | 1, 8 | 'target_stage_id' => 2, 9 | 'name' => 'Transition 1 Stage 1 to Stage 2 Wf 1', 10 | 'created_by' => 1, 11 | 'created_at' => $now, 12 | 'updated_by' => 1, 13 | 'updated_at' => $now, 14 | ], 15 | [ 16 | 'source_stage_id' => 2, 17 | 'target_stage_id' => 3, 18 | 'name' => 'Transition 2 Stage 2 to Stage 3 Wf 1', 19 | 'created_by' => 1, 20 | 'created_at' => $now, 21 | 'updated_by' => 1, 22 | 'updated_at' => $now, 23 | ], 24 | [ 25 | 'source_stage_id' => 3, 26 | 'target_stage_id' => 1, 27 | 'name' => 'Transition 3 Stage 3 to Stage 1 Wf 1', 28 | 'created_by' => 1, 29 | 'created_at' => $now, 30 | 'updated_by' => 1, 31 | 'updated_at' => $now, 32 | ], 33 | [ 34 | 'source_stage_id' => 4, 35 | 'target_stage_id' => 5, 36 | 'name' => 'Transition 1 Stage 1 to Stage 2 Wf 2', 37 | 'created_by' => 1, 38 | 'created_at' => $now, 39 | 'updated_by' => 1, 40 | 'updated_at' => $now, 41 | ], 42 | [ 43 | 'source_stage_id' => 5, 44 | 'target_stage_id' => 6, 45 | 'name' => 'Transition 2 Stage 2 to Stage 3 Wf 2', 46 | 'created_by' => 1, 47 | 'created_at' => $now, 48 | 'updated_by' => 1, 49 | 'updated_at' => $now, 50 | ], 51 | [ 52 | 'source_stage_id' => 5, 53 | 'target_stage_id' => 7, 54 | 'name' => 'Transition 3 Stage 2 to Stage 4 Wf 2', 55 | 'created_by' => 1, 56 | 'created_at' => $now, 57 | 'updated_by' => 1, 58 | 'updated_at' => $now, 59 | ], 60 | [ 61 | 'source_stage_id' => 6, 62 | 'target_stage_id' => 7, 63 | 'name' => 'Transition 4 Stage 3 to Stage 4 Wf 2', 64 | 'created_by' => 1, 65 | 'created_at' => $now, 66 | 'updated_by' => 1, 67 | 'updated_at' => $now, 68 | ], 69 | [ 70 | 'source_stage_id' => 7, 71 | 'target_stage_id' => 4, 72 | 'name' => 'Transition 5 Stage 4 to Stage 1 Wf 2', 73 | 'created_by' => 1, 74 | 'created_at' => $now, 75 | 'updated_by' => 1, 76 | 'updated_at' => $now, 77 | ], 78 | ]; 79 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/transition_permission.php: -------------------------------------------------------------------------------- 1 | 1, 8 | 'target_stage_id' => 2, 9 | 'permission' => 'administrator', 10 | 'created_by' => 1, 11 | 'created_at' => $now, 12 | 'updated_by' => 1, 13 | 'updated_at' => $now, 14 | ], 15 | [ 16 | 'source_stage_id' => 2, 17 | 'target_stage_id' => 3, 18 | 'permission' => 'admin', 19 | 'created_by' => 1, 20 | 'created_at' => $now, 21 | 'updated_by' => 1, 22 | 'updated_at' => $now, 23 | ], 24 | [ 25 | 'source_stage_id' => 3, 26 | 'target_stage_id' => 1, 27 | 'permission' => 'admin', 28 | 'created_by' => 1, 29 | 'created_at' => $now, 30 | 'updated_by' => 1, 31 | 'updated_at' => $now, 32 | ], 33 | [ 34 | 'source_stage_id' => 5, 35 | 'target_stage_id' => 6, 36 | 'permission' => 'admin', 37 | 'created_by' => 1, 38 | 'created_at' => $now, 39 | 'updated_by' => 1, 40 | 'updated_at' => $now, 41 | ], 42 | [ 43 | 'source_stage_id' => 5, 44 | 'target_stage_id' => 7, 45 | 'permission' => 'admin', 46 | 'created_by' => 1, 47 | 'created_at' => $now, 48 | 'updated_by' => 1, 49 | 'updated_at' => $now, 50 | ], 51 | [ 52 | 'source_stage_id' => 6, 53 | 'target_stage_id' => 7, 54 | 'permission' => 'admin', 55 | 'created_by' => 1, 56 | 'created_at' => $now, 57 | 'updated_by' => 1, 58 | 'updated_at' => $now, 59 | ], 60 | [ 61 | 'source_stage_id' => 7, 62 | 'target_stage_id' => 4, 63 | 'permission' => 'admin', 64 | 'created_by' => 1, 65 | 'created_at' => $now, 66 | 'updated_by' => 1, 67 | 'updated_at' => $now, 68 | ], 69 | ]; 70 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/user.php: -------------------------------------------------------------------------------- 1 | 'erau', 6 | 'auth_key' => 'tUu1qHcde0diwUol3xeI-18MuHkkprQI', 7 | // password_0 8 | 'password_hash' => '$2y$13$nJ1WDlBaGcbCdbNC5.5l4.sgy.OMEKCqtDQOdQ2OWpgiKRWYyzzne', 9 | 'password_reset_token' => 'RkD_Jw0_8HEedzLk7MM-ZKEFfYR7VbMr_1392559490', 10 | ], 11 | ]; 12 | -------------------------------------------------------------------------------- /tests/_app/fixtures/data/workflow.php: -------------------------------------------------------------------------------- 1 | 'workflow 1', 8 | 'created_by' => 1, 9 | 'created_at' => $now, 10 | 'updated_by' => 1, 11 | 'updated_at' => $now, 12 | ], 13 | [ 14 | 'name' => 'workflow 2', 15 | 'created_by' => 1, 16 | 'created_at' => $now, 17 | 'updated_by' => 1, 18 | 'updated_at' => $now, 19 | ], 20 | ]; 21 | -------------------------------------------------------------------------------- /tests/_app/index.php: -------------------------------------------------------------------------------- 1 | run(); 6 | -------------------------------------------------------------------------------- /tests/_app/migrations/m130101_000001_user.php: -------------------------------------------------------------------------------- 1 | $this->primaryKey(), 14 | 'username' => $this->string()->notNull()->unique(), 15 | 'auth_key' => $this->string(32)->notNull(), 16 | 'password_hash' => $this->string()->notNull(), 17 | 'password_reset_token' => $this->string()->unique(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/_app/migrations/m171130_000002_credit.php: -------------------------------------------------------------------------------- 1 | $this->primaryKey(), 14 | 'workflow_id' => $this->normalKey(), 15 | ]; 16 | } 17 | 18 | public function foreignKeys() 19 | { 20 | return [ 21 | 'workflow_id' => ['table' => 'workflow'] 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/_app/migrations/m171130_000003_credit_worklog.php: -------------------------------------------------------------------------------- 1 | workflow_id; 37 | } 38 | 39 | /** 40 | * @inheritdoc 41 | */ 42 | public function rules() 43 | { 44 | return array_merge(parent::rules(), [ 45 | [['workflow_id'], 'required'], 46 | [ 47 | ['workflow_id'], 48 | 'exist', 49 | 'skipOnError' => true, 50 | 'targetClass' => Workflow::class, 51 | 'targetAttribute' => ['workflow_id' => 'id'], 52 | ], 53 | ]); 54 | } 55 | 56 | /** 57 | * @inheritdoc 58 | */ 59 | public function attributeLabels() 60 | { 61 | return [ 62 | 'id' => Yii::t('app', 'ID'), 63 | 'workflow_id' => Yii::t('app', 'Workflow ID'), 64 | ]; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/_app/models/CreditAssignment.php: -------------------------------------------------------------------------------- 1 | $id]); 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | public static function findIdentityByAccessToken($token, $type = null) 30 | { 31 | return static::find()->joinWith('accessTokens', false) 32 | ->andWhere(['access_token' => $token]) 33 | ->one(); 34 | } 35 | 36 | /** 37 | * Finds user by username 38 | * 39 | * @param string $username 40 | * @return static|null 41 | */ 42 | public static function findByUsername($username) 43 | { 44 | return static::findOne(['username' => $username]); 45 | } 46 | 47 | /** 48 | * Finds user by password reset token 49 | * 50 | * @param string $token password reset token 51 | * @return static|null 52 | */ 53 | public static function findByPasswordResetToken($token) 54 | { 55 | if (!static::isPasswordResetTokenValid($token)) { 56 | return null; 57 | } 58 | 59 | return static::findOne([ 60 | 'password_reset_token' => $token, 61 | 'status' => self::STATUS_ACTIVE, 62 | ]); 63 | } 64 | 65 | /** 66 | * Finds out if password reset token is valid 67 | * 68 | * @param string $token password reset token 69 | * @return bool 70 | */ 71 | public static function isPasswordResetTokenValid($token) 72 | { 73 | if (empty($token)) { 74 | return false; 75 | } 76 | 77 | $timestamp = (int)substr($token, strrpos($token, '_') + 1); 78 | $expire = Yii::$app->params['user.passwordResetTokenExpire']; 79 | 80 | return $timestamp + $expire >= time(); 81 | } 82 | 83 | /** 84 | * @inheritdoc 85 | */ 86 | public function getId() 87 | { 88 | return $this->getPrimaryKey(); 89 | } 90 | 91 | /** 92 | * @inheritdoc 93 | */ 94 | public function getAuthKey() 95 | { 96 | return $this->auth_key; 97 | } 98 | 99 | /** 100 | * @inheritdoc 101 | */ 102 | public function validateAuthKey($authKey) 103 | { 104 | return $this->getAuthKey() === $authKey; 105 | } 106 | 107 | /** 108 | * Validates password 109 | * 110 | * @param string $password password to validate 111 | * @return bool if password provided is valid for current user 112 | */ 113 | public function validatePassword($password) 114 | { 115 | return Yii::$app->security->validatePassword($password, $this->password_hash); 116 | } 117 | 118 | /** 119 | * Generates password hash from password and sets it to the model 120 | * 121 | * @param string $password 122 | */ 123 | public function setPassword($password) 124 | { 125 | $this->password_hash = Yii::$app->security->generatePasswordHash($password); 126 | } 127 | 128 | /** 129 | * Generates "remember me" authentication key 130 | */ 131 | public function generateAuthKey() 132 | { 133 | $this->auth_key = Yii::$app->security->generateRandomString(); 134 | } 135 | 136 | /** 137 | * Generates new password reset token 138 | */ 139 | public function generatePasswordResetToken() 140 | { 141 | $this->password_reset_token = Yii::$app->security->generateRandomString() . '_' . time(); 142 | } 143 | 144 | /** 145 | * Removes password reset token 146 | */ 147 | public function removePasswordResetToken() 148 | { 149 | $this->password_reset_token = null; 150 | } 151 | 152 | /** 153 | * @inheritdoc 154 | */ 155 | public function checkUserCredentials($username, $password) 156 | { 157 | $user = static::findByUsername($username); 158 | if (empty($user)) { 159 | return false; 160 | } 161 | 162 | return $user->validatePassword($password); 163 | } 164 | 165 | /** 166 | * @inheritdoc 167 | */ 168 | public function getUserDetails($username = null) 169 | { 170 | $user = $username 171 | ? static::findByUsername($username) 172 | : $this; 173 | 174 | return ['user_id' => $user->id]; 175 | } 176 | 177 | /** 178 | * @return \yii\db\ActiveQuery 179 | */ 180 | public function getAccessTokens() 181 | { 182 | return $this->hasMany(AccessToken::class, ['user_id' => 'id']) 183 | ->andOnCondition(['client_id' => 'testclient']); 184 | } 185 | 186 | 187 | public function rules() 188 | { 189 | return [ 190 | [['username'], 'string'], 191 | ]; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /tests/_app/views/layouts/main.php: -------------------------------------------------------------------------------- 1 | user->getIsGuest()) { 4 | echo \yii\helpers\Html::a('Login', ['/user/security/login']); 5 | echo \yii\helpers\Html::a('Registration', ['/user/registration/register']); 6 | } else { 7 | echo \yii\helpers\Html::a('Logout', ['/user/security/logout']); 8 | } 9 | 10 | echo $content; 11 | -------------------------------------------------------------------------------- /tests/_app/views/site/index.php: -------------------------------------------------------------------------------- 1 | Index 2 | -------------------------------------------------------------------------------- /tests/_app/yii.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 12 | exit($exitCode); 13 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditAssignmentCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | /** 21 | * @depends CreditCest:fixtures 22 | */ 23 | public function fixtures(ApiTester $I) 24 | { 25 | $I->haveFixtures([ 26 | 'credit_assignment' => [ 27 | 'class' => CreditAssignmentFixture::class, 28 | 'depends' => [], 29 | ] 30 | ]); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | * @param Example $example 36 | * @dataprovider indexDataProvider 37 | * @depends fixtures 38 | * @before authToken 39 | */ 40 | public function index(ApiTester $I, Example $example) 41 | { 42 | $I->wantTo('Retrieve list of Credit Assignment records.'); 43 | $this->internalIndex($I, $example); 44 | } 45 | 46 | /** 47 | * @return array for test `index()`. 48 | */ 49 | protected function indexDataProvider() 50 | { 51 | return [ 52 | 'list' => [ 53 | 'url' => '/v1/credit/1/assignment', 54 | 'httpCode' => HttpCode::OK, 55 | 'headers' => [ 56 | 'X-Pagination-Total-Count' => 1, 57 | ], 58 | ], 59 | ]; 60 | } 61 | 62 | /** 63 | * @param ApiTester $I 64 | * @param Example $example 65 | * @dataprovider viewDataProvider 66 | * @depends fixtures 67 | * @before authToken 68 | */ 69 | public function view(ApiTester $I, Example $example) 70 | { 71 | $I->wantTo('Retrieve Credit Assignment single record.'); 72 | $this->internalView($I, $example); 73 | } 74 | 75 | /** 76 | * @return array> data for test `view()`. 77 | */ 78 | protected function viewDataProvider() 79 | { 80 | return [ 81 | 'single record' => [ 82 | 'url' => '/v1/credit/1/assignment/1', 83 | 'httpCode' => HttpCode::OK, 84 | ], 85 | ]; 86 | } 87 | 88 | /** 89 | * @param ApiTester $I 90 | * @param Example $example 91 | * @dataprovider createDataProvider 92 | * @depends fixtures 93 | * @before authToken 94 | */ 95 | public function create(ApiTester $I, Example $example) 96 | { 97 | $I->wantTo('Create a Credit Assignment record.'); 98 | $this->internalCreate($I, $example); 99 | } 100 | 101 | /** 102 | * @return array data for test `create()`. 103 | */ 104 | protected function createDataProvider() 105 | { 106 | return [ 107 | 'create assignment' => [ 108 | 'urlParams' => [ 109 | 'process_id' => 2 110 | ], 111 | 'data' => [ 112 | 'user_id' => 1 113 | ], 114 | 'httpCode' => HttpCode::CREATED, 115 | ], 116 | 'not found process' => [ 117 | 'urlParams' => [ 118 | 'process_id' => 10 119 | ], 120 | 'data' => [ 121 | 'user_id' => 1 122 | ], 123 | 'httpCode' => HttpCode::NOT_FOUND, 124 | 'validationErrors' => [ 125 | 'message' => 'process not found', 126 | ], 127 | ], 128 | 'unprocessable user' => [ 129 | 'urlParams' => [ 130 | 'process_id' => 2 131 | ], 132 | 'data' => [ 133 | 'user_id' => 10 134 | ], 135 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 136 | 'validationErrors' => [ 137 | 'user_id' => 'User ID is invalid.', 138 | ], 139 | ], 140 | ]; 141 | } 142 | 143 | /** 144 | * @param ApiTester $I 145 | * @param Example $example 146 | * @dataprovider updateDataProvider 147 | * @depends create 148 | * @before authToken 149 | */ 150 | public function update(ApiTester $I, Example $example) 151 | { 152 | $I->wantTo('Update a Credit Assignment record.'); 153 | $this->internalUpdate($I, $example); 154 | } 155 | 156 | /** 157 | * @return array data for test `update()`. 158 | */ 159 | protected function updateDataProvider() 160 | { 161 | return [ 162 | 'update credit 1' => [ 163 | 'url' => '/v1/credit/1/assignment/1', 164 | 'data' => [ 165 | 'user_id' => 1 166 | ], 167 | 'httpCode' => HttpCode::OK, 168 | ], 169 | 'update credit error ' => [ 170 | 'url' => '/v1/credit/1/assignment/1', 171 | 'data' => [ 172 | 'user_id' => 2 173 | ], 174 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 175 | 'validationErrors' => [ 176 | 'user_id' => 'User ID is invalid.' 177 | ], 178 | ], 179 | ]; 180 | } 181 | /** 182 | * @param ApiTester $I 183 | * @param Example $example 184 | * @dataprovider deleteDataProvider 185 | * @depends fixtures 186 | * @before authToken 187 | */ 188 | public function delete(ApiTester $I, Example $example) 189 | { 190 | $I->wantTo('Delete a Credit Assignment record.'); 191 | $this->internalDelete($I, $example); 192 | } 193 | 194 | /** 195 | * @return array[] data for test `delete()`. 196 | */ 197 | protected function deleteDataProvider() 198 | { 199 | return [ 200 | 'credit assignment not found' => [ 201 | 'url' => '/v1/credit/10/assignment/5', 202 | 'httpCode' => HttpCode::NOT_FOUND, 203 | 'validationErrors' => [ 204 | 'name' => 'The record "10" does not exists.' 205 | ], 206 | ], 207 | 'credit assigment' => [ 208 | 'url' => '/v1/credit/1/assignment/1', 209 | 'httpCode' => HttpCode::NO_CONTENT, 210 | ], 211 | 'not found' => [ 212 | 'url' => '/v1/credit/1/assignment/1', 213 | 'httpCode' => HttpCode::NOT_FOUND, 214 | 'validationErrors' => [ 215 | 'name' => 'The record "1" does not exists.' 216 | ], 217 | ], 218 | ]; 219 | } 220 | /** 221 | * @inheritdoc 222 | */ 223 | protected function recordJsonType() 224 | { 225 | return [ 226 | 'process_id' => 'integer:>0|string', 227 | 'user_id' => 'integer:>0|string', 228 | ]; 229 | } 230 | 231 | /** 232 | * @inheritdoc 233 | */ 234 | protected function getRoutePattern() 235 | { 236 | return 'v1/credit//assignment'; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /tests/api/CreditCest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | /** 21 | * @depends TransitionPermissionCest:fixtures 22 | */ 23 | public function fixtures(ApiTester $I) 24 | { 25 | $I->haveFixtures([ 26 | 'credit' => [ 27 | 'class' => CreditFixture::class, 28 | 'depends' => [] 29 | ], 30 | ]); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | * @param Example $example 36 | * @dataprovider indexDataProvider 37 | * @depends fixtures 38 | * @before authToken 39 | */ 40 | public function index(ApiTester $I, Example $example) 41 | { 42 | $I->wantTo('Retrieve list of Credit records.'); 43 | $this->internalIndex($I, $example); 44 | } 45 | 46 | /** 47 | * @return array for test `index()`. 48 | */ 49 | protected function indexDataProvider() 50 | { 51 | return [ 52 | 'list' => [ 53 | 'url' => '/v1/credit', 54 | 'httpCode' => HttpCode::OK, 55 | 'headers' => [ 56 | 'X-Pagination-Total-Count' => 7, 57 | ], 58 | ], 59 | 'not found credit' => [ 60 | 'url' => '/v1/credit/15', 61 | 'httpCode' => HttpCode::NOT_FOUND, 62 | ], 63 | 'filter by author' => [ 64 | 'urlParams' => [ 65 | 'created_by' => 1, 66 | ], 67 | 'httpCode' => HttpCode::OK, 68 | 'headers' => [ 69 | 'X-Pagination-Total-Count' => 7, 70 | ], 71 | ], 72 | 'rule created_by' => [ 73 | 'urlParams' => [ 74 | 'created_by' => 'wo', 75 | ], 76 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 77 | ], 78 | ]; 79 | } 80 | 81 | /** 82 | * @param ApiTester $I 83 | * @param Example $example 84 | * @dataprovider viewDataProvider 85 | * @depends fixtures 86 | * @before authToken 87 | */ 88 | public function view(ApiTester $I, Example $example) 89 | { 90 | $I->wantTo('Retrieve Credit single record.'); 91 | $this->internalView($I, $example); 92 | } 93 | 94 | /** 95 | * @return array> data for test `view()`. 96 | */ 97 | protected function viewDataProvider() 98 | { 99 | return [ 100 | 'single record' => [ 101 | 'urlParams' => [ 102 | 'id' => 4, 103 | 'expand' => 'workLogs, activeWorkLog', 104 | ], 105 | 'httpCode' => HttpCode::OK, 106 | ], 107 | 'not found credit record' => [ 108 | 'url' => '/v1/credit/8', 109 | 'httpCode' => HttpCode::NOT_FOUND, 110 | ], 111 | ]; 112 | } 113 | 114 | /** 115 | * @param ApiTester $I 116 | * @param Example $example 117 | * @dataprovider createDataProvider 118 | * @depends CreditWorklogCest:fixtures 119 | * @before authToken 120 | */ 121 | public function create(ApiTester $I, Example $example) 122 | { 123 | $I->wantTo('Create a Credit record.'); 124 | $this->internalCreate($I, $example); 125 | if (HttpCode::CREATED == $example['httpCode']) { 126 | $worklogRoute = $I->grabDataFromResponseByJsonPath( 127 | '$._links.worklog.href' 128 | )[0]; 129 | $I->sendGET($worklogRoute); 130 | $I->seeHTTPHeader('X-PAGINATION-TOTAL-COUNT', 1); 131 | } 132 | } 133 | 134 | /** 135 | * @return array data for test `create()`. 136 | */ 137 | protected function createDataProvider() 138 | { 139 | return [ 140 | 'create credit 1' => [ 141 | 'data' => [ 142 | 'workflow_id' => 2, 143 | 'stage_id' => 4, 144 | ], 145 | 'httpCode' => HttpCode::CREATED, 146 | ], 147 | 'dont exists' => [ 148 | 'data' => [ 149 | 'workflow_id' => 123, 150 | 'stage_id' => 2, 151 | ], 152 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 153 | 'validationErrors' => [ 154 | 'workflow_id' => 'Workflow ID is invalid.', 155 | 'stage_id' => 'Not an initial stage for the workflow.' 156 | ], 157 | ], 158 | 'not blank' => [ 159 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 160 | 'validationErrors' => [ 161 | 'workflow_id' => 'Workflow ID cannot be blank.' 162 | ], 163 | ], 164 | ]; 165 | } 166 | 167 | /** 168 | * @param ApiTester $I 169 | * @param Example $example 170 | * @dataprovider updateDataProvider 171 | * @depends fixtures 172 | * @before authToken 173 | */ 174 | public function update(ApiTester $I, Example $example) 175 | { 176 | $I->wantTo('Update a Credit record.'); 177 | $this->internalUpdate($I, $example); 178 | } 179 | 180 | /** 181 | * @return array data for test `update()`. 182 | */ 183 | protected function updateDataProvider() 184 | { 185 | return [ 186 | 'update credit 1' => [ 187 | 'url' => '/v1/credit/1', 188 | 'data' => ['workflow_id' => 2], 189 | 'httpCode' => HttpCode::OK, 190 | ], 191 | 'update credit 10' => [ 192 | 'url' => '/v1/credit/10', 193 | 'data' => ['workflow_id' => 2], 194 | 'httpCode' => HttpCode::NOT_FOUND, 195 | ], 196 | 'not exists' => [ 197 | 'url' => '/v1/credit/1', 198 | 'data' => ['workflow_id' => 123], 199 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 200 | 'validationErrors' => [ 201 | 'workflow_id' => 'Workflow ID is invalid.', 202 | ], 203 | ], 204 | 'not blank' => [ 205 | 'url' => '/v1/credit/1', 206 | 'data' => ['workflow_id' => ''], 207 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 208 | 'validationErrors' => [ 209 | 'workflow_id' => 'Workflow ID cannot be blank.' 210 | ], 211 | ], 212 | ]; 213 | } 214 | 215 | /** 216 | * @param ApiTester $I 217 | * @param Example $example 218 | * @dataprovider deleteDataProvider 219 | * @depends CreditWorklogCest:create 220 | * @before authToken 221 | */ 222 | public function delete(ApiTester $I, Example $example) 223 | { 224 | $I->wantTo('Delete a Credit record.'); 225 | $this->internalDelete($I, $example); 226 | } 227 | 228 | /** 229 | * @return array[] data for test `delete()`. 230 | */ 231 | protected function deleteDataProvider() 232 | { 233 | return [ 234 | 'credit not found' => [ 235 | 'url' => '/v1/credit/10', 236 | 'httpCode' => HttpCode::NOT_FOUND, 237 | ], 238 | 'delete credit 5' => [ 239 | 'url' => '/v1/credit/5', 240 | 'httpCode' => HttpCode::NO_CONTENT, 241 | ], 242 | 'not found' => [ 243 | 'url' => '/v1/credit/5', 244 | 'httpCode' => HttpCode::NOT_FOUND, 245 | 'validationErrors' => [ 246 | 'name' => 'The record "5" does not exists.' 247 | ], 248 | ], 249 | ]; 250 | } 251 | /** 252 | * @inheritdoc 253 | */ 254 | protected function recordJsonType() 255 | { 256 | return [ 257 | 'id' => 'integer:>0', 258 | ]; 259 | } 260 | 261 | /** 262 | * @inheritdoc 263 | */ 264 | protected function getRoutePattern() 265 | { 266 | return 'v1/credit'; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /tests/api/CreditWorklogCest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class CreditWorklogCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | /** 21 | * @depends CreditCest:fixtures 22 | */ 23 | public function fixtures(ApiTester $I) 24 | { 25 | $I->haveFixtures([ 26 | 'credit_worklog' => [ 27 | 'class' => CreditWorklogFixture::class, 28 | 'depends' => [], 29 | ] 30 | ]); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | * @param Example $example 36 | * @dataprovider indexDataProvider 37 | * @depends fixtures 38 | * @before authToken 39 | */ 40 | public function index(ApiTester $I, Example $example) 41 | { 42 | $I->wantTo('Retrieve list of Credit Worklog records.'); 43 | $this->internalIndex($I, $example); 44 | } 45 | 46 | /** 47 | * @return array for test `index()`. 48 | */ 49 | protected function indexDataProvider() 50 | { 51 | return [ 52 | 'list' => [ 53 | 'url' => '/v1/credit/4/worklog', 54 | 'httpCode' => HttpCode::OK, 55 | 'headers' => [ 56 | 'X-Pagination-Total-Count' => 4, 57 | ], 58 | ], 59 | ]; 60 | } 61 | 62 | /** 63 | * @param ApiTester $I 64 | * @param Example $example 65 | * @dataprovider viewDataProvider 66 | * @depends fixtures 67 | * @before authToken 68 | */ 69 | public function view(ApiTester $I, Example $example) 70 | { 71 | $I->wantTo('Retrieve Credit Worklog single record.'); 72 | $this->internalView($I, $example); 73 | } 74 | 75 | /** 76 | * @return array> data for test `view()`. 77 | */ 78 | protected function viewDataProvider() 79 | { 80 | return [ 81 | 'single record' => [ 82 | 'url' => '/v1/credit/1/worklog/1', 83 | 'data' => [ 84 | 'expand' => 'process', 85 | ], 86 | 'httpCode' => HttpCode::OK, 87 | ], 88 | ]; 89 | } 90 | 91 | /** 92 | * @param ApiTester $I 93 | * @param Example $example 94 | * @dataprovider createDataProvider 95 | * @depends fixtures 96 | * @before authToken 97 | */ 98 | public function create(ApiTester $I, Example $example) 99 | { 100 | $I->wantTo('Create a Credit Worklog record.'); 101 | $this->internalCreate($I, $example); 102 | } 103 | 104 | /** 105 | * @return array data for test `create()`. 106 | */ 107 | protected function createDataProvider() 108 | { 109 | return [ 110 | 'create worklog' => [ 111 | 'urlParams' => [ 112 | 'process_id' => 1 113 | ], 114 | 'data' => [ 115 | 'stage_id' => 5 116 | ], 117 | 'httpCode' => HttpCode::CREATED, 118 | ], 119 | 'unprocessable worklog' => [ 120 | 'urlParams' => [ 121 | 'process_id' => 1 122 | ], 123 | 'data' => [ 124 | 'stage_id' => 1 125 | ], 126 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 127 | 'validationErrors' => [ 128 | 'stage_id' => 'There is no transition for the current stage', 129 | ], 130 | ], 131 | ]; 132 | } 133 | 134 | /** 135 | * @param ApiTester $I 136 | * @depends create 137 | * @before authToken 138 | */ 139 | public function permission(ApiTester $I) 140 | { 141 | $auth = Yii::$app->authManager; 142 | $adminRole = $auth->getRole('admin'); 143 | $I->sendPOST('/v1/credit/1/worklog', ['stage_id' => 7]); 144 | $I->seeResponseCodeIs(HttpCode::FORBIDDEN); 145 | 146 | $auth->assign($adminRole, 1); 147 | $I->sendPOST('/v1/credit/1/worklog', ['stage_id' => 7]); 148 | $I->seeResponseCodeIs(HttpCode::CREATED); 149 | 150 | $auth->revoke($adminRole, 1); 151 | } 152 | 153 | /** 154 | * @param ApiTester $I 155 | * @param Example $example 156 | * @dataprovider updateDataProvider 157 | * @depends create 158 | * @before authToken 159 | */ 160 | public function update(ApiTester $I, Example $example) 161 | { 162 | $I->wantTo('Update a Credit Worklog record.'); 163 | $this->internalUpdate($I, $example); 164 | } 165 | 166 | /** 167 | * @return array data for test `update()`. 168 | */ 169 | protected function updateDataProvider() 170 | { 171 | return [ 172 | 'update credit 1' => [ 173 | 'url' => '/v1/credit/1/worklog/1', 174 | 'data' => [ 175 | 'stage_id' => 3 176 | ], 177 | 'httpCode' => HttpCode::OK, 178 | ], 179 | ]; 180 | } 181 | 182 | /** 183 | * @inheritdoc 184 | */ 185 | protected function recordJsonType() 186 | { 187 | return [ 188 | 'id' => 'integer:>0', 189 | ]; 190 | } 191 | 192 | /** 193 | * @inheritdoc 194 | */ 195 | protected function getRoutePattern() 196 | { 197 | return 'v1/credit//worklog'; 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tests/api/StageCest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class StageCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | /** 21 | * @depends WorkflowCest:fixtures 22 | */ 23 | public function fixtures(ApiTester $I) 24 | { 25 | $I->haveFixtures([ 26 | 'stage' => [ 27 | 'class' => StageFixture::class, 28 | 'depends' => [] 29 | ], 30 | ]); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | * @param Example $example 36 | * @dataprovider indexDataProvider 37 | * @depends fixtures 38 | * @before authToken 39 | */ 40 | public function index(ApiTester $I, Example $example) 41 | { 42 | $I->wantTo('Retrieve list of Stage records.'); 43 | $this->internalIndex($I, $example); 44 | } 45 | 46 | /** 47 | * @return array for test `index()`. 48 | */ 49 | protected function indexDataProvider() 50 | { 51 | return [ 52 | 'list' => [ 53 | 'urlParams' => [ 54 | 'workflow_id' => 1, 55 | 'expand' => 'transitions' 56 | ], 57 | 'httpCode' => HttpCode::OK, 58 | 'headers' => [ 59 | 'X-Pagination-Total-Count' => 3, 60 | ], 61 | ], 62 | 'not found workflow' => [ 63 | 'urlParams' => [ 64 | 'workflow_id' => 10 65 | ], 66 | 'httpCode' => HttpCode::NOT_FOUND, 67 | ], 68 | 'filter by name' => [ 69 | 'urlParams' => [ 70 | 'workflow_id' => 1, 71 | 'name' => 'Stage 2 - Wf 1', 72 | ], 73 | 'httpCode' => HttpCode::OK, 74 | 'headers' => [ 75 | 'X-Pagination-Total-Count' => 1, 76 | ], 77 | ], 78 | 'filter by author' => [ 79 | 'urlParams' => [ 80 | 'workflow_id' => 1, 81 | 'created_by' => 1, 82 | ], 83 | 'httpCode' => HttpCode::OK, 84 | 'headers' => [ 85 | 'X-Pagination-Total-Count' => 3, 86 | ], 87 | ], 88 | 'rule created_by' => [ 89 | 'urlParams' => [ 90 | 'workflow_id' => 1, 91 | 'created_by' => 'st', 92 | ], 93 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 94 | ], 95 | ]; 96 | } 97 | 98 | /** 99 | * @param ApiTester $I 100 | * @param Example $example 101 | * @dataprovider viewDataProvider 102 | * @depends fixtures 103 | * @before authToken 104 | */ 105 | public function view(ApiTester $I, Example $example) 106 | { 107 | $I->wantTo('Retrieve Stage single record.'); 108 | $this->internalView($I, $example); 109 | } 110 | 111 | /** 112 | * @return array> data for test `view()`. 113 | */ 114 | protected function viewDataProvider() 115 | { 116 | return [ 117 | 'single record' => [ 118 | 'urlParams' => [ 119 | 'workflow_id' => 1, 120 | 'id' => 1, 121 | 'expand' => 'workflow,detailTransitions,totalTransitions' 122 | ], 123 | 'httpCode' => HttpCode::OK, 124 | 'response' => [ 125 | '_embedded' => [ 126 | 'transitions' => [ 127 | ['id' => 2], 128 | ], 129 | ], 130 | ], 131 | ], 132 | 'not found stage record' => [ 133 | 'url' => '/w1/workflow/1/stage/10', 134 | 'httpCode' => HttpCode::NOT_FOUND, 135 | ], 136 | 'not found workflow record' => [ 137 | 'url' => '/w1/workflow/10/stage/10', 138 | 'httpCode' => HttpCode::NOT_FOUND, 139 | ], 140 | ]; 141 | } 142 | 143 | /** 144 | * @param ApiTester $I 145 | * @param Example $example 146 | * @dataprovider createDataProvider 147 | * @depends fixtures 148 | * @before authToken 149 | */ 150 | public function create(ApiTester $I, Example $example) 151 | { 152 | $I->wantTo('Create a Stage record.'); 153 | $this->internalCreate($I, $example); 154 | } 155 | 156 | /** 157 | * @return array>> data for test `create()`. 158 | */ 159 | protected function createDataProvider() 160 | { 161 | return [ 162 | 'create stage 3' => [ 163 | 'urlParams' => [ 164 | 'workflow_id' => 1 165 | ], 166 | 'data' => ['name' => 'stage 3'], 167 | 'httpCode' => HttpCode::CREATED, 168 | ], 169 | 'unique' => [ 170 | 'urlParams' => [ 171 | 'workflow_id' => 1 172 | ], 173 | 'data' => ['name' => 'stage 3'], 174 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 175 | 'validationErrors' => [ 176 | 'name' => 'The combination "1"-"stage 3" of Workflow ID and Stage name has already been taken.' 177 | ], 178 | ], 179 | 'to short' => [ 180 | 'urlParams' => [ 181 | 'workflow_id' => 1 182 | ], 183 | 'data' => ['name' => 'wo'], 184 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 185 | 'validationErrors' => [ 186 | 'name' => 'Stage name should contain at least 6 characters.' 187 | ], 188 | ], 189 | 'not blank' => [ 190 | 'urlParams' => [ 191 | 'workflow_id' => 1 192 | ], 193 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 194 | 'validationErrors' => [ 195 | 'name' => 'Stage name cannot be blank.' 196 | ], 197 | ], 198 | ]; 199 | } 200 | 201 | /** 202 | * @param ApiTester $I 203 | * @param Example $example 204 | * @dataprovider updateDataProvider 205 | * @depends fixtures 206 | * @before authToken 207 | */ 208 | public function update(ApiTester $I, Example $example) 209 | { 210 | $I->wantTo('Update a Stage record.'); 211 | $this->internalUpdate($I, $example); 212 | } 213 | 214 | /** 215 | * @return array[] data for test `update()`. 216 | */ 217 | protected function updateDataProvider() 218 | { 219 | return [ 220 | 'update stage 1' => [ 221 | 'url' => '/w1/workflow/1/stage/1', 222 | 'data' => ['name' => 'stage 7'], 223 | 'httpCode' => HttpCode::OK, 224 | ], 225 | 'to short' => [ 226 | 'url' => '/w1/workflow/1/stage/1', 227 | 'data' => ['name' => 'wo'], 228 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 229 | 'validationErrors' => [ 230 | 'name' => 'Stage name should contain at least 6 characters.' 231 | ], 232 | ], 233 | ]; 234 | } 235 | 236 | /** 237 | * @param ApiTester $I 238 | * @param Example $example 239 | * @dataprovider deleteDataProvider 240 | * @depends fixtures 241 | * @before authToken 242 | */ 243 | public function delete(ApiTester $I, Example $example) 244 | { 245 | $I->wantTo('Delete a Stage record.'); 246 | $this->internalDelete($I, $example); 247 | } 248 | 249 | /** 250 | * @return array[] data for test `delete()`. 251 | */ 252 | protected function deleteDataProvider() 253 | { 254 | return [ 255 | 'workflow not found' => [ 256 | 'url' => '/w1/workflow/10/stage/1', 257 | 'httpCode' => HttpCode::NOT_FOUND, 258 | ], 259 | 'delete stage 8' => [ 260 | 'url' => '/w1/workflow/1/stage/8', 261 | 'httpCode' => HttpCode::NO_CONTENT, 262 | ], 263 | 'not found' => [ 264 | 'url' => '/w1/workflow/1/stage/8', 265 | 'httpCode' => HttpCode::NOT_FOUND, 266 | 'validationErrors' => [ 267 | 'name' => 'The record "8" does not exists.' 268 | ], 269 | ], 270 | ]; 271 | } 272 | /** 273 | * @inheritdoc 274 | */ 275 | protected function recordJsonType() 276 | { 277 | return [ 278 | 'id' => 'integer:>0', 279 | 'name' => 'string', 280 | ]; 281 | } 282 | 283 | /** 284 | * @inheritdoc 285 | */ 286 | protected function getRoutePattern() 287 | { 288 | return 'w1/workflow//stage'; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /tests/api/TransitionCest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class TransitionCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | /** 21 | * @depends StageCest:fixtures 22 | */ 23 | public function fixtures(ApiTester $I) 24 | { 25 | $I->haveFixtures([ 26 | 'transition' => [ 27 | 'class' => TransitionFixture::class, 28 | 'depends' => [], 29 | ] 30 | ]); 31 | } 32 | 33 | /** 34 | * @param ApiTester $I 35 | * @param Example $example 36 | * @dataprovider indexDataProvider 37 | * @depends fixtures 38 | * @before authToken 39 | */ 40 | public function index(ApiTester $I, Example $example) 41 | { 42 | $I->wantTo('Retrieve list of Transition records.'); 43 | $this->internalIndex($I, $example); 44 | } 45 | 46 | /** 47 | * @return array for test `index()`. 48 | */ 49 | protected function indexDataProvider() 50 | { 51 | return [ 52 | 'list' => [ 53 | 'urlParams' => [ 54 | 'workflow_id' => 2, 55 | 'stage_id' => 5, 56 | 'expand' => 'sourceStage, targetStage, permissions' 57 | ], 58 | 'httpCode' => HttpCode::OK, 59 | 'headers' => [ 60 | 'X-Pagination-Total-Count' => 2, 61 | ], 62 | ], 63 | 'not found workflow' => [ 64 | 'urlParams' => [ 65 | 'workflow_id' => 1, 66 | 'stage_id' => 5 67 | ], 68 | 'httpCode' => HttpCode::NOT_FOUND, 69 | ], 70 | 'not found stage' => [ 71 | 'urlParams' => [ 72 | 'workflow_id' => 2, 73 | 'stage_id' => 10 74 | ], 75 | 'httpCode' => HttpCode::NOT_FOUND, 76 | ], 77 | 'not found transition' => [ 78 | 'url' => '/w1/workflow/2/stage/5/transition/1', 79 | 'httpCode' => HttpCode::NOT_FOUND, 80 | ], 81 | 'filter by name' => [ 82 | 'urlParams' => [ 83 | 'workflow_id' => 2, 84 | 'stage_id' => 5, 85 | 'name' => 'Stage 2 to Stage 3' 86 | ], 87 | 'httpCode' => HttpCode::OK, 88 | 'headers' => [ 89 | 'X-Pagination-Total-Count' => 1, 90 | ], 91 | ], 92 | 'rule created_by' => [ 93 | 'urlParams' => [ 94 | 'workflow_id' => 2, 95 | 'stage_id' => 5, 96 | 'created_by' => 'tra', 97 | ], 98 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 99 | ], 100 | ]; 101 | } 102 | 103 | /** 104 | * @param ApiTester $I 105 | * @param Example $example 106 | * @dataprovider viewDataProvider 107 | * @depends fixtures 108 | * @before authToken 109 | */ 110 | public function view(ApiTester $I, Example $example) 111 | { 112 | $I->wantTo('Retrieve Transition single record.'); 113 | $this->internalView($I, $example); 114 | } 115 | 116 | /** 117 | * @return array> data for test `view()`. 118 | */ 119 | protected function viewDataProvider() 120 | { 121 | return [ 122 | 'single record' => [ 123 | 'url' => '/w1/workflow/1/stage/1/transition/2', 124 | 'httpCode' => HttpCode::OK, 125 | ], 126 | 'transition not found' => [ 127 | 'url' => '/w1/workflow/1/stage/2/transition/2', 128 | 'httpCode' => HttpCode::NOT_FOUND, 129 | ], 130 | 'stage not found' => [ 131 | 'url' => '/w1/workflow/1/stage/10/transition/2', 132 | 'httpCode' => HttpCode::NOT_FOUND, 133 | ], 134 | 'workflow not found' => [ 135 | 'url' => '/w1/workflow/10/stage/1/transition/2', 136 | 'httpCode' => HttpCode::NOT_FOUND, 137 | ], 138 | ]; 139 | } 140 | 141 | /** 142 | * @param ApiTester $I 143 | * @param Example $example 144 | * @dataprovider createDataProvider 145 | * @depends fixtures 146 | * @before authToken 147 | */ 148 | public function create(ApiTester $I, Example $example) 149 | { 150 | $I->wantTo('Create a Transition record.'); 151 | $this->internalCreate($I, $example); 152 | } 153 | 154 | /** 155 | * @return array data for test `create()`. 156 | */ 157 | protected function createDataProvider() 158 | { 159 | return [ 160 | 'create transition' => [ 161 | 'urlParams' => [ 162 | 'workflow_id' => 1, 163 | 'stage_id' => 1 164 | ], 165 | 'data' => [ 166 | 'source_stage_id' => 1, 167 | 'target_stage_id' => 3, 168 | 'name' => 'new Transition' 169 | ], 170 | 'httpCode' => HttpCode::CREATED, 171 | ], 172 | 'required data' => [ 173 | 'urlParams' => [ 174 | 'workflow_id' => 1, 175 | 'stage_id' => 1 176 | ], 177 | 'data' => [ 178 | 'source_stage_id' => 2, 179 | 'target_stage_id' => 3, 180 | ], 181 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 182 | 'validationErrors' => [ 183 | 'name' => 'Transition Name cannot be blank.' 184 | ], 185 | ], 186 | 'required data 2' => [ 187 | 'urlParams' => [ 188 | 'workflow_id' => 1, 189 | 'stage_id' => 1 190 | ], 191 | 'data' => [ 192 | 'source_stage_id' => 1, 193 | 'target_stage_id' => 4 194 | ], 195 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 196 | 'validationErrors' => [ 197 | 'name' => 'Transition Name cannot be blank.', 198 | 'target_stage_id' => 'The stages are not associated to the same workflow.' 199 | ], 200 | ], 201 | 'workflow not found' => [ 202 | 'urlParams' => [ 203 | 'workflow_id' => 10, 204 | 'stage_id' => 1 205 | ], 206 | 'data' => [ 207 | 'source_stage_id' => 1, 208 | 'target_stage_id' => 4 209 | ], 210 | 'httpCode' => HttpCode::NOT_FOUND, 211 | ], 212 | 'stage not found' => [ 213 | 'urlParams' => [ 214 | 'workflow_id' => 1, 215 | 'stage_id' => 19 216 | ], 217 | 'data' => [ 218 | 'source_stage_id' => 1, 219 | 'target_stage_id' => 4 220 | ], 221 | 'httpCode' => HttpCode::NOT_FOUND, 222 | ], 223 | 'unique source target' => [ 224 | 'urlParams' => [ 225 | 'workflow_id' => 1, 226 | 'stage_id' => 1 227 | ], 228 | 'data' => [ 229 | 'source_stage_id' => 1, 230 | 'target_stage_id' => 2, 231 | 'name' => 'not unique' 232 | ], 233 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 234 | 'validationErrors' => [ 235 | 'target_stage_id' => 'Target already in use for the source stage.' 236 | ], 237 | ], 238 | 'unique source name' => [ 239 | 'urlParams' => [ 240 | 'workflow_id' => 1, 241 | 'stage_id' => 1 242 | ], 243 | 'data' => [ 244 | 'source_stage_id' => 1, 245 | 'target_stage_id' => 3, 246 | 'name' => 'Transition 1 Stage 1 to Stage 2 Wf 1' 247 | ], 248 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 249 | 'validationErrors' => [ 250 | 'name' => 'Name already used for the source stage.' 251 | ], 252 | ], 253 | ]; 254 | } 255 | 256 | /** 257 | * @param ApiTester $I 258 | * @param Example $example 259 | * @dataprovider updateDataProvider 260 | * @depends fixtures 261 | * @before authToken 262 | */ 263 | public function update(ApiTester $I, Example $example) 264 | { 265 | $I->wantTo('Update a Transition record.'); 266 | $this->internalUpdate($I, $example); 267 | } 268 | 269 | /** 270 | * @return array>> data for test `update()`. 271 | */ 272 | protected function updateDataProvider() 273 | { 274 | return [ 275 | 'update transition' => [ 276 | 'url' => '/w1/workflow/1/stage/1/transition/2', 277 | 'data' => ['name' => 'update transition'], 278 | 'httpCode' => HttpCode::OK, 279 | ], 280 | 'to short' => [ 281 | 'url' => '/w1/workflow/1/stage/1/transition/2', 282 | 'data' => ['name' => 'tr'], 283 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 284 | 'validationErrors' => [ 285 | 'name' => 'Transition Name should contain at least 6 characters.' 286 | ], 287 | ], 288 | ]; 289 | } 290 | 291 | /** 292 | * @param ApiTester $I 293 | * @param Example $example 294 | * @dataprovider deleteDataProvider 295 | * @depends fixtures 296 | * @before authToken 297 | */ 298 | public function delete(ApiTester $I, Example $example) 299 | { 300 | $I->wantTo('Delete a Transition record.'); 301 | $this->internalDelete($I, $example); 302 | } 303 | 304 | /** 305 | * @return array>> data for test `delete()`. 306 | */ 307 | protected function deleteDataProvider() 308 | { 309 | return [ 310 | 'workflow not found' => [ 311 | 'url' => '/w1/workflow/10/stage/1/transition/2', 312 | 'httpCode' => HttpCode::NOT_FOUND, 313 | ], 314 | 'stage not found' => [ 315 | 'url' => '/w1/workflow/1/stage/10/transition/2', 316 | 'httpCode' => HttpCode::NOT_FOUND, 317 | ], 318 | 'transition not found' => [ 319 | 'url' => '/w1/workflow/1/stage/1/transition/10', 320 | 'httpCode' => HttpCode::NOT_FOUND, 321 | ], 322 | 'delete transition 1-3' => [ 323 | 'url' => '/w1/workflow/1/stage/1/transition/3', 324 | 'httpCode' => HttpCode::NO_CONTENT, 325 | ], 326 | 'not found' => [ 327 | 'url' => '/w1/workflow/1/stage/1/transition/3', 328 | 'httpCode' => HttpCode::NOT_FOUND, 329 | 'validationErrors' => [ 330 | 'name' => 'The record "3" does not exists.' 331 | ], 332 | ], 333 | ]; 334 | } 335 | /** 336 | * @inheritdoc 337 | */ 338 | protected function recordJsonType() 339 | { 340 | return [ 341 | 'source_stage_id' => 'integer:>0', 342 | 'target_stage_id' => 'integer:>0', 343 | 'name' => 'string', 344 | ]; 345 | } 346 | 347 | /** 348 | * @inheritdoc 349 | */ 350 | protected function getRoutePattern() 351 | { 352 | return 'w1/workflow//stage//transition'; 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /tests/api/TransitionPermissionCest.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class TransitionPermissionCest extends \tecnocen\roa\test\AbstractResourceCest 15 | { 16 | protected function authToken(ApiTester $I) 17 | { 18 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 19 | } 20 | 21 | /** 22 | * @depends TransitionCest:fixtures 23 | */ 24 | public function fixtures(ApiTester $I) 25 | { 26 | $I->haveFixtures([ 27 | 'auth_item' => AuthItemFixture::class, 28 | 'transition_permission' => [ 29 | 'class' => TransitionPermissionFixture::class, 30 | 'depends' => [], 31 | ], 32 | ]); 33 | } 34 | 35 | /** 36 | * @param ApiTester $I 37 | * @param Example $example 38 | * @dataprovider indexDataProvider 39 | * @depends fixtures 40 | * @before authToken 41 | */ 42 | public function index(ApiTester $I, Example $example) 43 | { 44 | $I->wantTo('Retrieve list of Transition Permission records.'); 45 | $this->internalIndex($I, $example); 46 | } 47 | 48 | /** 49 | * @return array for test `index()`. 50 | */ 51 | protected function indexDataProvider() 52 | { 53 | return [ 54 | 'list' => [ 55 | 'urlParams' => [ 56 | 'workflow_id' => 1, 57 | 'stage_id' => 1, 58 | 'target_id' => 2, 59 | 'expand' => 'sourceStage, transition' 60 | ], 61 | 'httpCode' => HttpCode::OK, 62 | 'headers' => [ 63 | 'X-Pagination-Total-Count' => 1, 64 | ], 65 | ], 66 | 'list with target stage' => [ 67 | 'urlParams' => [ 68 | 'workflow_id' => 2, 69 | 'stage_id' => 5, 70 | 'target_id' => 7 , 71 | 'expand' => 'targetStage' 72 | ], 73 | 'httpCode' => HttpCode::OK, 74 | 'headers' => [ 75 | 'X-Pagination-Total-Count' => 1, 76 | ], 77 | ], 78 | 'not found workflow' => [ 79 | 'urlParams' => [ 80 | 'workflow_id' => 10, 81 | 'stage_id' => 1, 82 | 'target_id' => 2 83 | ], 84 | 'httpCode' => HttpCode::NOT_FOUND, 85 | ], 86 | 'not found stage' => [ 87 | 'urlParams' => [ 88 | 'workflow_id' => 1, 89 | 'stage_id' => 10, 90 | 'target_id' => 2 91 | ], 92 | 'httpCode' => HttpCode::NOT_FOUND, 93 | ], 94 | 'not found transition' => [ 95 | 'urlParams' => [ 96 | 'workflow_id' => 1, 97 | 'stage_id' => 1, 98 | 'target_id' => 10 99 | ], 100 | 'httpCode' => HttpCode::NOT_FOUND, 101 | ], 102 | 'filter by name' => [ 103 | 'urlParams' => [ 104 | 'workflow_id' => 1, 105 | 'stage_id' => 1, 106 | 'target_id' => 2, 107 | 'permission' => 'administrator', 108 | 'expand' => 'transition' 109 | ], 110 | 'httpCode' => HttpCode::OK, 111 | 'headers' => [ 112 | 'X-Pagination-Total-Count' => 1, 113 | ], 114 | ], 115 | 'rule created_by' => [ 116 | 'urlParams' => [ 117 | 'workflow_id' => 1, 118 | 'stage_id' => 1, 119 | 'target_id' => 2, 120 | 'created_by' => 'per', 121 | ], 122 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 123 | ], 124 | ]; 125 | } 126 | 127 | /** 128 | * @param ApiTester $I 129 | * @param Example $example 130 | * @dataprovider viewDataProvider 131 | * @depends fixtures 132 | * @before authToken 133 | */ 134 | public function view(ApiTester $I, Example $example) 135 | { 136 | $I->wantTo('Retrieve Transition Permission single record.'); 137 | $this->internalView($I, $example); 138 | } 139 | 140 | /** 141 | * @return array> data for test `view()`. 142 | */ 143 | protected function viewDataProvider() 144 | { 145 | return [ 146 | 'not allowed' => [ 147 | 'url' => '/w1/workflow/1/stage/1/transition/2/permission/administrator', 148 | 'httpCode' => HttpCode::OK, 149 | ] 150 | ]; 151 | } 152 | 153 | /** 154 | * @param ApiTester $I 155 | * @param Example $example 156 | * @dataprovider createDataProvider 157 | * @depends fixtures 158 | * @before authToken 159 | */ 160 | public function create(ApiTester $I, Example $example) 161 | { 162 | $I->wantTo('Create a Transition Permission record.'); 163 | $this->internalCreate($I, $example); 164 | } 165 | 166 | /** 167 | * @return array data for test `create()`. 168 | */ 169 | protected function createDataProvider() 170 | { 171 | return [ 172 | 'create transition permission' => [ 173 | 'urlParams' => [ 174 | 'workflow_id' => 1, 175 | 'stage_id' => 1, 176 | 'target_id' => 2 177 | ], 178 | 'data' => [ 179 | 'permission' => 'credit' 180 | ], 181 | 'httpCode' => HttpCode::CREATED, 182 | ], 183 | 'required data' => [ 184 | 'urlParams' => [ 185 | 'workflow_id' => 1, 186 | 'stage_id' => 1, 187 | 'target_id' => 2 188 | ], 189 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 190 | 'validationErrors' => [ 191 | 'permission' => 'Permission cannot be blank.' 192 | ], 193 | ], 194 | 'workflow not found' => [ 195 | 'urlParams' => [ 196 | 'workflow_id' => 10, 197 | 'stage_id' => 1, 198 | 'target_id' => 2 199 | ], 200 | 'httpCode' => HttpCode::NOT_FOUND, 201 | ], 202 | 'stage not found' => [ 203 | 'urlParams' => [ 204 | 'workflow_id' => 1, 205 | 'stage_id' => 19, 206 | 'target_id' => 2 207 | ], 208 | 'data' => [ 209 | 'permission' => 'administrator', 210 | ], 211 | 'httpCode' => HttpCode::NOT_FOUND, 212 | ], 213 | 'to short' => [ 214 | 'urlParams' => [ 215 | 'workflow_id' => 1, 216 | 'stage_id' => 1, 217 | 'target_id' => 2 218 | ], 219 | 'data' => [ 220 | 'permission' => 'ad' 221 | ], 222 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 223 | 'validationErrors' => [ 224 | 'permission' => 'Permission should contain at least 6 characters.' 225 | ], 226 | ], 227 | 'unique transition permission' => [ 228 | 'urlParams' => [ 229 | 'workflow_id' => 1, 230 | 'stage_id' => 1, 231 | 'target_id' => 2 232 | ], 233 | 'data' => [ 234 | 'permission' => 'administrator' 235 | ], 236 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 237 | 'validationErrors' => [ 238 | 'permission' => 'Permission already set for the transition.' 239 | ], 240 | ], 241 | ]; 242 | } 243 | 244 | /** 245 | * @param ApiTester $I 246 | * @param Example $example 247 | * @dataprovider updateDataProvider 248 | * @depends fixtures 249 | * @before authToken 250 | */ 251 | public function update(ApiTester $I, Example $example) 252 | { 253 | $I->wantTo('Update a Transition Permission record.'); 254 | $this->internalUpdate($I, $example); 255 | } 256 | 257 | /** 258 | * @return array>> data for test `update()`. 259 | */ 260 | protected function updateDataProvider() 261 | { 262 | return [ 263 | 'method not allowed' => [ 264 | 'url' => '/w1/workflow/1/stage/1/transition/2/permission', 265 | 'data' => ['permission' => 'update transition'], 266 | 'httpCode' => HttpCode::METHOD_NOT_ALLOWED, 267 | ], 268 | ]; 269 | } 270 | 271 | /** 272 | * @param ApiTester $I 273 | * @param Example $example 274 | * @dataprovider deleteDataProvider 275 | * @depends fixtures 276 | * @before authToken 277 | */ 278 | public function delete(ApiTester $I, Example $example) 279 | { 280 | $I->wantTo('Delete a Transition Permission record.'); 281 | $this->internalDelete($I, $example); 282 | } 283 | 284 | /** 285 | * @return array>> data for test `delete()`. 286 | */ 287 | protected function deleteDataProvider() 288 | { 289 | return [ 290 | 'delete permission administrator' => [ 291 | 'url' => '/w1/workflow/1/stage/1/transition/2/permission/administrator', 292 | 'httpCode' => HttpCode::NO_CONTENT, 293 | ], 294 | 'cannot be blank' => [ 295 | 'url' => '/w1/workflow/1/stage/1/transition/2/permission', 296 | 'httpCode' => HttpCode::METHOD_NOT_ALLOWED, 297 | ], 298 | 'transition not found' => [ 299 | 'url' => '/w1/workflow/1/stage/1/transition/10/permission/admin', 300 | 'httpCode' => HttpCode::NOT_FOUND, 301 | ], 302 | 'stage not found' => [ 303 | 'url' => '/w1/workflow/1/stage/10/transition/2/permission/admin', 304 | 'httpCode' => HttpCode::NOT_FOUND, 305 | ], 306 | 'workflow not found' => [ 307 | 'url' => '/w1/workflow/10/stage/1/transition/2/permission/admin', 308 | 'httpCode' => HttpCode::NOT_FOUND, 309 | ], 310 | ]; 311 | } 312 | 313 | /** 314 | * @inheritdoc 315 | */ 316 | protected function recordJsonType() 317 | { 318 | return [ 319 | 'source_stage_id' => 'integer:>0', 320 | 'target_stage_id' => 'integer:>0', 321 | 'permission' => 'string', 322 | 323 | ]; 324 | } 325 | 326 | /** 327 | * @inheritdoc 328 | */ 329 | protected function getRoutePattern() 330 | { 331 | return 'w1/workflow//stage//transition//permission'; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /tests/api/WorkflowCest.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class WorkflowCest extends \tecnocen\roa\test\AbstractResourceCest 14 | { 15 | protected function authToken(ApiTester $I) 16 | { 17 | $I->amBearerAuthenticated(OauthAccessTokensFixture::SIMPLE_TOKEN); 18 | } 19 | 20 | public function fixtures(ApiTester $I) 21 | { 22 | $I->haveFixtures([ 23 | 'access_tokens' => OauthAccessTokensFixture::class, 24 | 'workflow' => WorkflowFixture::class, 25 | ]); 26 | } 27 | 28 | /** 29 | * @param ApiTester $I 30 | * @param Example $example 31 | * @dataprovider indexDataProvider 32 | * @depends fixtures 33 | * @before authToken 34 | */ 35 | public function index(ApiTester $I, Example $example) 36 | { 37 | $I->wantTo('Retrieve list of Workflow records.'); 38 | $this->internalIndex($I, $example); 39 | } 40 | 41 | /** 42 | * @return array for test `index()`. 43 | */ 44 | protected function indexDataProvider() 45 | { 46 | return [ 47 | 'list' => [ 48 | 'httpCode' => HttpCode::OK, 49 | ], 50 | 'filter by name' => [ 51 | 'urlParams' => [ 52 | 'name' => 'workflow 2', 53 | 'expand' => 'stages' 54 | ], 55 | 'httpCode' => HttpCode::OK, 56 | 'headers' => [ 57 | 'X-Pagination-Total-Count' => 1, 58 | ], 59 | ], 60 | 'filter by author' => [ 61 | 'urlParams' => ['created_by' => 1], 62 | 'httpCode' => HttpCode::OK, 63 | 'headers' => [ 64 | 'X-Pagination-Total-Count' => 2, 65 | ], 66 | ], 67 | 'rule created_by' => [ 68 | 'urlParams' => [ 69 | 'created_by' => 'wo', 70 | ], 71 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 72 | ], 73 | ]; 74 | } 75 | 76 | /** 77 | * @param ApiTester $I 78 | * @param Example $example 79 | * @dataprovider viewDataProvider 80 | * @depends StageCest:fixtures 81 | * @before authToken 82 | */ 83 | public function view(ApiTester $I, Example $example) 84 | { 85 | $I->wantTo('Retrieve Workflow single record.'); 86 | $this->internalView($I, $example); 87 | if (isset($example['response'])) { 88 | $I->seeResponseContainsJson($example['response']); 89 | } 90 | } 91 | 92 | /** 93 | * @return array[] data for test `view()`. 94 | */ 95 | protected function viewDataProvider() 96 | { 97 | return [ 98 | 'expand stages' => [ 99 | 'urlParams' => [ 100 | 'id' => '1', 101 | 'expand' => 'stages, totalStages, detailStages' 102 | ], 103 | 'httpCode' => HttpCode::OK, 104 | 'response' => [ 105 | '_embedded' => [ 106 | 'stages' => [ 107 | ['id' => 1], 108 | ], 109 | 'totalStages' => 3, 110 | ], 111 | ], 112 | ], 113 | 'field total stages' => [ 114 | 'urlParams' => [ 115 | 'id' => '1', 116 | 'fields' => 'id,name' 117 | ], 118 | 'httpCode' => HttpCode::OK, 119 | 'response' => [ 120 | 'id' => 1, 121 | 'name' => 'workflow 1', 122 | ], 123 | ], 124 | ]; 125 | } 126 | 127 | /** 128 | * @param ApiTester $I 129 | * @param Example $example 130 | * @dataprovider createDataProvider 131 | * @depends fixtures 132 | * @before authToken 133 | */ 134 | public function create(ApiTester $I, Example $example) 135 | { 136 | $I->wantTo('Create a Workflow record.'); 137 | $this->internalCreate($I, $example); 138 | } 139 | 140 | /** 141 | * @return array>> data for test `create()`. 142 | */ 143 | protected function createDataProvider() 144 | { 145 | return [ 146 | 'create workflow 3' => [ 147 | 'data' => ['name' => 'workflow 3'], 148 | 'httpCode' => HttpCode::CREATED, 149 | ], 150 | 'unique' => [ 151 | 'data' => ['name' => 'workflow 3'], 152 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 153 | 'validationErrors' => [ 154 | 'name' => 'Workflow name "workflow 3" has already been taken.' 155 | ], 156 | ], 157 | 'to short' => [ 158 | 'data' => ['name' => 'wo'], 159 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 160 | 'validationErrors' => [ 161 | 'name' => 'Workflow name should contain at least 6 characters.' 162 | ], 163 | ], 164 | 'not blank' => [ 165 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 166 | 'validationErrors' => [ 167 | 'name' => 'Workflow name cannot be blank.' 168 | ], 169 | ], 170 | ]; 171 | } 172 | 173 | /** 174 | * @param ApiTester $I 175 | * @param Example $example 176 | * @dataprovider updateDataProvider 177 | * @depends fixtures 178 | * @before authToken 179 | */ 180 | public function update(ApiTester $I, Example $example) 181 | { 182 | $I->wantTo('Update a Workflow record.'); 183 | $this->internalUpdate($I, $example); 184 | } 185 | 186 | /** 187 | * @return array[] data for test `update()`. 188 | */ 189 | protected function updateDataProvider() 190 | { 191 | return [ 192 | 'update workflow 1' => [ 193 | 'urlParams' => ['id' => '1'], 194 | 'data' => ['name' => 'workflow 7'], 195 | 'httpCode' => HttpCode::OK, 196 | ], 197 | 'to short' => [ 198 | 'urlParams' => ['id' => '1'], 199 | 'data' => ['name' => 'wo'], 200 | 'httpCode' => HttpCode::UNPROCESSABLE_ENTITY, 201 | 'validationErrors' => [ 202 | 'name' => 'Workflow name should contain at least 6 characters.' 203 | ], 204 | ], 205 | ]; 206 | } 207 | 208 | /** 209 | * @param ApiTester $I 210 | * @param Example $example 211 | * @dataprovider deleteDataProvider 212 | * @depends fixtures 213 | * @before authToken 214 | */ 215 | public function delete(ApiTester $I, Example $example) 216 | { 217 | $I->wantTo('Delete a Workflow record.'); 218 | $this->internalDelete($I, $example); 219 | } 220 | 221 | /** 222 | * @return array[] data for test `delete()`. 223 | */ 224 | protected function deleteDataProvider() 225 | { 226 | return [ 227 | 'delete workflow 1' => [ 228 | 'urlParams' => ['id' => '1'], 229 | 'httpCode' => HttpCode::NO_CONTENT, 230 | ], 231 | 'not found' => [ 232 | 'urlParams' => ['id' => '1'], 233 | 'httpCode' => HttpCode::NOT_FOUND, 234 | 'validationErrors' => [ 235 | 'name' => 'The record "1" does not exists.' 236 | ], 237 | ], 238 | ]; 239 | } 240 | /** 241 | * @inheritdoc 242 | */ 243 | protected function recordJsonType() 244 | { 245 | return [ 246 | 'id' => 'integer:>0', 247 | 'name' => 'string', 248 | ]; 249 | } 250 | 251 | /** 252 | * @inheritdoc 253 | */ 254 | protected function getRoutePattern() 255 | { 256 | return 'w1/workflow'; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /tests/api/_bootstrap.php: -------------------------------------------------------------------------------- 1 | haveFixtures([ 15 | 'credit_worklog' => [ 16 | 'class' => CreditWorklogFixture::class, 17 | ], 18 | ]); 19 | } 20 | 21 | /** 22 | * @dataprovider failedValidationData 23 | * @depends fixtures 24 | */ 25 | public function failedValidation(UnitTester $I, Example $example) 26 | { 27 | $credit = new Credit(); 28 | $credit->load($example['data'], ''); 29 | $I->assertFalse($credit->validate()); 30 | foreach ($example['errors'] as $attribute => $message) { 31 | $I->assertTrue($credit->hasErrors($attribute)); 32 | $I->assertEquals($message, $credit->getFirstError($attribute)); 33 | } 34 | } 35 | 36 | protected function failedValidationData() 37 | { 38 | return [ 39 | [ 40 | 'data' => [], 41 | 'errors' => [ 42 | 'workflow_id' => 'Workflow ID cannot be blank.', 43 | 'stage_id' => 'Stage ID cannot be blank.', 44 | ], 45 | ], 46 | [ 47 | 'data' => [ 48 | 'workflow_id' => 10, 49 | 'stage_id' => 10, 50 | ], 51 | 'errors' => [ 52 | 'workflow_id' => 'Workflow ID is invalid.', 53 | 'stage_id' => 'Not an initial stage for the workflow.', 54 | ], 55 | ], 56 | ]; 57 | } 58 | 59 | /** 60 | * @dataprovider saveData 61 | */ 62 | public function save(UnitTester $I, Example $example) 63 | { 64 | $credit = new Credit(); 65 | $credit->load($example['data'], ''); 66 | $credit->save(); 67 | $I->assertEmpty($credit->getFirstErrors()); 68 | $I->assertTrue($credit->save()); 69 | $I->assertNotEmpty($credit->workLogs); 70 | $I->assertNotEmpty($credit->activeWorkLog); 71 | $I->assertEquals( 72 | $example['data']['stage_id'], 73 | $credit->activeWorkLog->stage_id 74 | ); 75 | } 76 | 77 | protected function saveData() 78 | { 79 | return [ 80 | [ 81 | 'data' => [ 82 | 'workflow_id' => 1, 83 | 'stage_id' => 1, 84 | ], 85 | ], 86 | ]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /tests/unit/models/CreditWorklogCest.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | class CreditWorklogCest 18 | { 19 | /** 20 | * @depends models\CreditCest:save 21 | */ 22 | public function validate(UnitTester $I) 23 | { 24 | $creditWorklog = new CreditWorklog(); 25 | 26 | $creditWorklog->process_id = 4; 27 | $I->assertTrue($creditWorklog->validate(['process_id'])); 28 | 29 | $creditWorklog->stage_id = 5; 30 | $I->assertTrue($creditWorklog->validate(['stage_id'])); 31 | 32 | } 33 | 34 | /** 35 | * @depends models\CreditCest:save 36 | */ 37 | public function save(UnitTester $I) 38 | { 39 | $creditWorklog = new CreditWorklog(); 40 | $creditWorklog->process_id = 4; // current stage_id = 7 41 | $creditWorklog->stage_id = 4; 42 | $I->expectException( 43 | ForbiddenHttpException::class, 44 | function () use ($creditWorklog) { 45 | $creditWorklog->save(); 46 | } 47 | ); 48 | 49 | $auth = Yii::$app->authManager; 50 | $adminRole = $auth->getRole('admin'); 51 | $auth->assign($adminRole, 1); 52 | Yii::$app->user->login(User::findOne(1)); 53 | 54 | $I->assertTrue($creditWorklog->save()); 55 | $auth->revoke($adminRole, 1); 56 | } 57 | } 58 | --------------------------------------------------------------------------------