├── tests ├── _data │ └── .gitkeep ├── _output │ └── .gitignore ├── acceptance │ ├── _bootstrap.php │ ├── AboutCest.php │ ├── HomeCest.php │ ├── LoginCest.php │ └── ContactCest.php ├── functional │ ├── _bootstrap.php │ ├── LoginFormCest.php │ └── ContactFormCest.php ├── unit │ ├── _bootstrap.php │ └── models │ │ ├── UserTest.php │ │ ├── LoginFormTest.php │ │ └── ContactFormTest.php ├── _bootstrap.php ├── unit.suite.yml ├── acceptance.suite.yml.example ├── functional.suite.yml ├── bin │ ├── yii.bat │ └── yii └── _support │ ├── FunctionalTester.php │ ├── UnitTester.php │ └── AcceptanceTester.php ├── runtime └── .gitignore ├── rbac ├── .gitignore ├── UpdateOnwer.php └── UserGroupRule.php ├── web ├── assets │ └── .gitignore ├── robots.txt ├── css │ ├── fix-toast.css │ ├── detailView.css │ └── site.css ├── favicon.ico ├── .htaccess ├── js │ ├── modal.js │ ├── pos │ │ ├── orderItemsComponent.js │ │ └── main.js │ └── checkout │ │ └── main.js ├── index.php └── index-test.php ├── .bowerrc ├── vagrant ├── config │ ├── .gitignore │ └── vagrant-local.example.yml ├── nginx │ ├── log │ │ └── .gitignore │ └── app.conf └── provision │ ├── always-as-root.sh │ ├── once-as-vagrant.sh │ └── once-as-root.sh ├── views ├── layouts │ ├── modal.php │ ├── login.php │ ├── center.php │ └── register.php ├── order │ ├── create.php │ └── _form.php ├── category │ ├── create.php │ ├── update.php │ ├── _form.php │ ├── index.php │ └── view.php ├── variation │ ├── create.php │ ├── update.php │ ├── _form.php │ ├── _search.php │ ├── index.php │ └── view.php ├── payment-method │ ├── _form.php │ ├── create.php │ ├── update.php │ └── index.php ├── expense │ ├── create.php │ ├── update.php │ ├── pay.php │ └── _form.php ├── product │ ├── create.php │ ├── update.php │ ├── category.php │ └── _form.php ├── company │ ├── update.php │ ├── address.php │ └── _form.php ├── employee │ ├── create.php │ ├── update.php │ ├── address.php │ ├── _form.php │ ├── change_password.php │ └── index.php ├── operation │ ├── create.php │ └── _form.php ├── site │ ├── login.php │ └── error.php ├── pos │ └── index.php ├── address │ └── _form.php └── sale │ └── _search.php ├── .github └── issue_template.md ├── config ├── test_db.php ├── params.php ├── db.php ├── test.php └── console.php ├── docker-compose.yml ├── assets ├── VueAsset.php ├── AxiosAsset.php ├── ChartAsset.php ├── FontsAsset.php ├── PosAsset.php ├── AdminLteAsset.php └── AppAsset.php ├── components ├── validators │ ├── DecimalValidator.php │ ├── ProductOutputValidator.php │ └── ProductExists.php ├── TranslationEventHandler.php ├── factories │ ├── SaleFactory.php │ ├── OrderFactory.php │ ├── PaymentMethodsDefaultFactory.php │ └── AccountFactory.php ├── traits │ ├── UpdateCountersTrait.php │ └── FilterTrait.php ├── behaviors │ └── FormatterDateBehavior.php ├── formatters │ └── AppFormatter.php └── Seller.php ├── modules └── api │ ├── Module.php │ ├── models │ └── ProductSearch.php │ └── controllers │ ├── ProductController.php │ ├── OrderController.php │ ├── AddressController.php │ ├── SaleController.php │ └── OrderItemController.php ├── migrations ├── m201009_155008_add_is_sold_sale.php ├── m200905_232204_drop_updated_at_operation.php ├── m200913_205947_add_is_paid_expense.php ├── m201102_181500_drop_note_and_is_quotation_order.php ├── m201016_130100_add_employee_id_sale.php ├── m200906_192639_add_soft_delete_operation.php ├── m200721_145607_create_address_table.php ├── m200721_145614_create_category_table.php ├── m200721_145616_create_variation_table.php ├── m200721_145621_create_sale_table.php ├── m200721_145620_create_order_table.php ├── m200721_145622_create_pay_table.php ├── m200721_145612_create_expense_table.php ├── m201005_185935_create_order_item_table.php ├── m200721_145609_create_company_table.php ├── m200721_145613_create_payment_method_table.php ├── m200721_145615_create_product_table.php ├── m200721_145618_create_operation_table.php ├── m200721_145611_create_employee_table.php └── m200721_145625_create_junction_table_for_product_and_variation_tables.php ├── yii.bat ├── .gitignore ├── yii ├── messages └── pt-BR │ ├── kvgrid.php │ └── yii.php ├── mail └── layouts │ └── html.php ├── codeception.yml ├── models ├── Migration.php ├── CategorySearch.php ├── PaySearch.php ├── ProductVariation.php ├── VariationSearch.php ├── PaymentMethodSearch.php ├── SignupForm.php ├── LoginForm.php ├── Address.php ├── EmployeeSearch.php ├── ExpenseSearch.php ├── Category.php ├── ProductSearch.php └── Expense.php ├── LICENSE.md ├── commands └── RbacController.php ├── widgets └── Alert.php ├── Vagrantfile └── composer.json /tests/_data/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /runtime/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /rbac/.gitignore: -------------------------------------------------------------------------------- 1 | items.php 2 | rules.php 3 | -------------------------------------------------------------------------------- /tests/_output/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /tests/acceptance/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'modal'], $options)); 6 | 7 | echo '
'; 8 | 9 | Modal::end(); 10 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | Descrição da Issue. 2 | 3 | ### Restrições 4 | Descrição das restrições. 5 | 6 | ### Ideas 7 | Descrição das Ideias 8 | 9 | ### Código 10 | ``` 11 | // Bloco de Código 12 | ``` 13 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 | 'admin@example.com', 5 | 'senderEmail' => 'noreply@example.com', 6 | 'senderName' => 'Example.com mailer', 7 | 'bsVersion' => '4.x', 8 | 'version' => '1.0.0' 9 | ]; 10 | -------------------------------------------------------------------------------- /assets/VueAsset.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/about')); 10 | $I->see('About', 'h1'); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /components/validators/DecimalValidator.php: -------------------------------------------------------------------------------- 1 | translatedMessage = "@MISSING: {$event->message} @"; 12 | } 13 | } -------------------------------------------------------------------------------- /components/factories/SaleFactory.php: -------------------------------------------------------------------------------- 1 | attributes = $attributes; 13 | $sale->save(); 14 | 15 | return $sale; 16 | } 17 | } -------------------------------------------------------------------------------- /vagrant/provision/always-as-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Bash helpers == 4 | 5 | function info { 6 | echo " " 7 | echo "--> $1" 8 | echo " " 9 | } 10 | 11 | #== Provision script == 12 | 13 | info "Provision-script user: `whoami`" 14 | 15 | info "Restart web-stack" 16 | service php7.0-fpm restart 17 | service nginx restart 18 | service mysql restart -------------------------------------------------------------------------------- /components/factories/OrderFactory.php: -------------------------------------------------------------------------------- 1 | company_id = Yii::$app->user->identity->company_id; 14 | $order->save(); 15 | 16 | return $order; 17 | } 18 | } -------------------------------------------------------------------------------- /tests/functional.suite.yml: -------------------------------------------------------------------------------- 1 | # Codeception Test Suite Configuration 2 | 3 | # suite for functional (integration) tests. 4 | # emulate web requests and make application process them. 5 | # (tip: better to use with frameworks). 6 | 7 | # RUN `build` COMMAND AFTER ADDING/REMOVING MODULES. 8 | #basic/web/index.php 9 | class_name: FunctionalTester 10 | modules: 11 | enabled: 12 | - Filesystem 13 | - Yii2 14 | -------------------------------------------------------------------------------- /config/db.php: -------------------------------------------------------------------------------- 1 | 'yii\db\Connection', 5 | 'dsn' => 'mysql:host=127.0.0.1;dbname=sismec', 6 | 'username' => 'sismec', 7 | 'password' => 'sismec', 8 | 'charset' => 'utf8', 9 | 'attributes' => [PDO::ATTR_CASE => PDO::CASE_LOWER], 10 | 'on afterOpen' => function($event) { 11 | $event->sender->createCommand("SET sql_mode = ''")->execute(); 12 | } 13 | ]; 14 | -------------------------------------------------------------------------------- /web/index.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /assets/FontsAsset.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/index')); 10 | $I->see('My Company'); 11 | 12 | $I->seeLink('About'); 13 | $I->click('About'); 14 | $I->wait(2); // wait for page to be opened 15 | 16 | $I->see('This is the About page.'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /modules/api/Module.php: -------------------------------------------------------------------------------- 1 | user->isGuest && Yii::$app->user->identity->is_manager) 15 | return true; 16 | 17 | return isset($params['employee_id']) ? $params['employee_id'] == $user : false; 18 | } 19 | } -------------------------------------------------------------------------------- /views/order/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Order'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Orders'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | ]) ?> 19 | 20 |
21 | -------------------------------------------------------------------------------- /assets/AdminLteAsset.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Category'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Categories'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | ]) ?> 19 | 20 |
21 | -------------------------------------------------------------------------------- /components/traits/UpdateCountersTrait.php: -------------------------------------------------------------------------------- 1 | getOldPrimaryKey(true)) > 0) { 10 | foreach ($counters as $name => $value) { 11 | $this->$name = !isset($this->$name) ? $value: $this->$name += $value; 12 | $this->update(); 13 | } 14 | return true; 15 | } 16 | return false; 17 | } 18 | } -------------------------------------------------------------------------------- /web/js/pos/orderItemsComponent.js: -------------------------------------------------------------------------------- 1 | Vue.component('order_items', { 2 | props: ['id', 'index', 'name', 'amount', 'unit_price', 'total'], 3 | template: '\ 4 | \ 5 | {{ index+1 }}\ 6 | {{ name }}\ 7 | {{ unit_price }}\ 8 | {{ amount }}\ 9 | {{ total }}\ 10 | \ 11 | \ 12 | \ 13 | \ 14 | \ 15 | \ 16 | ', 17 | }); -------------------------------------------------------------------------------- /web/index-test.php: -------------------------------------------------------------------------------- 1 | run(); 17 | -------------------------------------------------------------------------------- /views/variation/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Variation'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Variations'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 | 14 |

title) ?>

15 | 16 | render('_form', [ 17 | 'model' => $model, 18 | 'list' => $list 19 | ]) ?> 20 | 21 |
22 | -------------------------------------------------------------------------------- /migrations/m201009_155008_add_is_sold_sale.php: -------------------------------------------------------------------------------- 1 | addColumn('sale', 'is_sold', $this->tinyInteger(1)->defaultValue(0)->after('discount')); 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function safeDown() 22 | { 23 | $this->dropColumn('sale', 'is_sold'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer vendor dir 16 | /vendor 17 | 18 | # composer itself is not needed 19 | composer.phar 20 | 21 | # Mac DS_Store Files 22 | .DS_Store 23 | 24 | # phpunit itself is not needed 25 | phpunit.phar 26 | # local phpunit config 27 | /phpunit.xml 28 | 29 | tests/_output/* 30 | tests/_support/_generated 31 | 32 | #vagrant folder 33 | /.vagrant 34 | 35 | .vscode -------------------------------------------------------------------------------- /tests/bin/yii.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Yii command line bootstrap script for Windows. 5 | rem 6 | rem @author Qiang Xue 7 | rem @link http://www.yiiframework.com/ 8 | rem @copyright Copyright (c) 2008 Yii Software LLC 9 | rem @license http://www.yiiframework.com/license/ 10 | rem ------------------------------------------------------------- 11 | 12 | @setlocal 13 | 14 | set YII_PATH=%~dp0 15 | 16 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 17 | 18 | "%PHP_COMMAND%" "%YII_PATH%yii" %* 19 | 20 | @endlocal 21 | -------------------------------------------------------------------------------- /vagrant/config/vagrant-local.example.yml: -------------------------------------------------------------------------------- 1 | # Your personal GitHub token 2 | github_token: 3 | # Read more: https://github.com/blog/1509-personal-api-tokens 4 | # You can generate it here: https://github.com/settings/tokens 5 | 6 | # Guest OS timezone 7 | timezone: Europe/London 8 | 9 | # Are we need check box updates for every 'vagrant up'? 10 | box_check_update: false 11 | 12 | # Virtual machine name 13 | machine_name: yii2basic 14 | 15 | # Virtual machine IP 16 | ip: 192.168.83.137 17 | 18 | # Virtual machine CPU cores number 19 | cpus: 1 20 | 21 | # Virtual machine RAM 22 | memory: 1024 23 | -------------------------------------------------------------------------------- /migrations/m200905_232204_drop_updated_at_operation.php: -------------------------------------------------------------------------------- 1 | dropColumn('operation', 'updated_at'); 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function safeDown() 22 | { 23 | $this->addColumn('operations', 'updated_at', $this->dateTime()->after('created_at')); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /migrations/m200913_205947_add_is_paid_expense.php: -------------------------------------------------------------------------------- 1 | addColumn('expense', 'is_paid', $this->tinyInteger(1)->defaultValue(0)->after('payday')); 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function safeDown() 22 | { 23 | $this->dropColumn('expense', 'is_paid'); 24 | return false; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 21 | exit($exitCode); 22 | -------------------------------------------------------------------------------- /rbac/UserGroupRule.php: -------------------------------------------------------------------------------- 1 | user->isGuest) { 15 | $is_manager = Yii::$app->user->identity->is_manager; 16 | if ($item->name === 'admin') 17 | return $is_manager == 1; 18 | else if ($item->name === 'cashier') 19 | return $is_manager == 1 || $is_manager == 0; 20 | } 21 | 22 | return false; 23 | } 24 | } -------------------------------------------------------------------------------- /tests/acceptance/LoginCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/login')); 10 | $I->see('Login', 'h1'); 11 | 12 | $I->amGoingTo('try to login with correct credentials'); 13 | $I->fillField('input[name="LoginForm[username]"]', 'admin'); 14 | $I->fillField('input[name="LoginForm[password]"]', 'admin'); 15 | $I->click('login-button'); 16 | $I->wait(2); // wait for button to be clicked 17 | 18 | $I->expectTo('see user info'); 19 | $I->see('Logout'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/validators/ProductOutputValidator.php: -------------------------------------------------------------------------------- 1 | product_id)->amount; 14 | 15 | if ($model->$attribute > $amount) { 16 | $this->addError($model, $attribute, Yii::t('app', 'The quantity cannot be greater than the total quantity of the product in stock.', [ 17 | 'attribute' => $attribute 18 | ])); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/_support/FunctionalTester.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Category: {name}', [ 9 | 'name' => $model->name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Categories'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 |
16 | 17 |

title) ?>

18 | 19 | render('_form', [ 20 | 'model' => $model, 21 | ]) ?> 22 | 23 |
24 | -------------------------------------------------------------------------------- /views/variation/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Variation: {name}', [ 9 | 'name' => $model->name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Variation'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 |
16 | 17 |

title) ?>

18 | 19 | render('_form', [ 20 | 'model' => $model, 21 | ]) ?> 22 | 23 |
24 | -------------------------------------------------------------------------------- /tests/_support/UnitTester.php: -------------------------------------------------------------------------------- 1 | andWhere(['is_deleted' => 0]) 13 | ->all(); 14 | $terms = explode(' ', $term); 15 | return array_filter($query, function($product) use ($terms) { 16 | foreach ($terms as $term) { 17 | $name = (string)$product; 18 | if (!preg_match("/{$term}/i", $name)) 19 | return false; 20 | } 21 | return true; 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /migrations/m201102_181500_drop_note_and_is_quotation_order.php: -------------------------------------------------------------------------------- 1 | dropColumn('order', 'note'); 16 | $this->dropColumn('order', 'is_quotation'); 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function safeDown() 23 | { 24 | $this->addColumn('order', 'note', $this->text()); 25 | $this->addColumn('order', 'is_quotation', $this->tinyInteger(1)->defaultValue(0)); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/_support/AcceptanceTester.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | field($model, 'name')->textInput(['maxlength' => true]) ?> 17 | 18 | field($model, 'installment_limit')->textInput(['type' => 'number']) ?> 19 |
20 | 21 | 24 | 25 | 26 | 27 |
-------------------------------------------------------------------------------- /views/expense/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Expense'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Expenses'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 |
14 |
15 |
16 |

title) ?>

17 |
18 | render('_form', [ 19 | 'model' => $model, 20 | ]) ?> 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /views/product/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Product'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Products'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 |
14 |
15 |
16 |

title) ?>

17 |
18 | render('_form', [ 19 | 'model' => $model, 20 | ]) ?> 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /views/company/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Company'); 9 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 11 | ?> 12 | 13 |
14 |
15 |
16 |
17 |

title) ?>

18 |
19 | render('_form', [ 20 | 'model' => $model, 21 | ]) ?> 22 |
23 |
24 |
25 | -------------------------------------------------------------------------------- /views/employee/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Employee'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Employees'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 |
14 |
15 |
16 |

title) ?>

17 |
18 | render('_form', [ 19 | 'model' => $model, 20 | ]) ?> 21 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /migrations/m201016_130100_add_employee_id_sale.php: -------------------------------------------------------------------------------- 1 | addColumn('sale', 'employee_id', $this->integer()->unsigned()->notNull()->after('updated_at')); 16 | $this->addForeignKey('fk-sale-employee_id', 'sale', 'employee_id', 'employee', 'id', 'CASCADE'); 17 | 18 | } 19 | 20 | /** 21 | * {@inheritdoc} 22 | */ 23 | public function safeDown() 24 | { 25 | $this->dropForeignKey('fk-sale-employee_id', 'sale'); 26 | $this->dropColumn('sale', 'employee_id'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /views/payment-method/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Payment Method'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Payment Methods'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 |
13 |
14 |
15 |
16 |

title) ?>

17 |
18 | render('_form', [ 19 | 'model' => $model, 20 | ]) ?> 21 |
22 |
23 |
-------------------------------------------------------------------------------- /messages/pt-BR/kvgrid.php: -------------------------------------------------------------------------------- 1 | '', 21 | ]; 22 | -------------------------------------------------------------------------------- /views/company/address.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Company Address'); 9 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update Address'); 11 | 12 | ?> 13 | 14 |
15 |
16 |
17 |
18 |

title) ?>

19 |
20 | render('/address/_form', [ 21 | 'model' => $address, 22 | ]) ?> 23 |
24 |
25 |
26 | -------------------------------------------------------------------------------- /mail/layouts/html.php: -------------------------------------------------------------------------------- 1 | 8 | beginPage() ?> 9 | 10 | 11 | 12 | 13 | <?= Html::encode($this->title) ?> 14 | head() ?> 15 | 16 | 17 | beginBody() ?> 18 | 19 | endBody() ?> 20 | 21 | 22 | endPage() ?> 23 | -------------------------------------------------------------------------------- /vagrant/provision/once-as-vagrant.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Import script args == 4 | 5 | github_token=$(echo "$1") 6 | 7 | #== Bash helpers == 8 | 9 | function info { 10 | echo " " 11 | echo "--> $1" 12 | echo " " 13 | } 14 | 15 | #== Provision script == 16 | 17 | info "Provision-script user: `whoami`" 18 | 19 | info "Configure composer" 20 | composer config --global github-oauth.github.com ${github_token} 21 | echo "Done!" 22 | 23 | info "Install project dependencies" 24 | cd /app 25 | composer --no-progress --prefer-dist install 26 | 27 | info "Create bash-alias 'app' for vagrant user" 28 | echo 'alias app="cd /app"' | tee /home/vagrant/.bash_aliases 29 | 30 | info "Enabling colorized prompt for guest console" 31 | sed -i "s/#force_color_prompt=yes/force_color_prompt=yes/" /home/vagrant/.bashrc 32 | -------------------------------------------------------------------------------- /migrations/m200906_192639_add_soft_delete_operation.php: -------------------------------------------------------------------------------- 1 | addColumn('operation', 'deleted_at', $this->dateTime()->after('created_at')); 16 | $this->addColumn('operation', 'is_deleted', $this->tinyInteger(1)->defaultValue(0)->after('deleted_at')); 17 | } 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function safeDown() 23 | { 24 | $this->dropColumn('operation', 'deleted_at'); 25 | $this->dropColumn('operation', 'is_deleted'); 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /views/category/_form.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 'form_category', 16 | 'enableClientValidation' => false, 17 | 'enableAjaxValidation' => true, 18 | 'validationUrl' => ['validation', 'id' => $model->id] 19 | ]); ?> 20 | 21 | field($model, 'name')->textInput(['maxlength' => true]) ?> 22 | 23 |
24 | 'btn btn-success']) ?> 25 |
26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /components/traits/FilterTrait.php: -------------------------------------------------------------------------------- 1 | user->getIsGuest()) { 14 | if (self::isJoinsSet()) 15 | self::makeInnerJoin($find); 16 | 17 | $find->andWhere(['company.id' => Yii::$app->user->identity->company_id]); 18 | } 19 | 20 | return $find; 21 | } 22 | 23 | protected function isJoinsSet() 24 | { 25 | return !empty(self::JOINS) && is_array(self::JOINS); 26 | } 27 | 28 | protected function makeInnerJoin(&$find) 29 | { 30 | foreach(self::JOINS as $join) 31 | $find->innerJoin($join['table'], $join['on']); 32 | } 33 | } -------------------------------------------------------------------------------- /views/operation/create.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Create Operation'); 9 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Operations'), 'url' => ['index']]; 10 | $this->params['breadcrumbs'][] = $this->title; 11 | ?> 12 | 13 |
14 |
15 |
16 |
17 |

title) ?>

18 |
19 | render('_form', [ 20 | 'model' => $model, 21 | 'products' => $products 22 | ]) ?> 23 |
24 |
25 |
-------------------------------------------------------------------------------- /tests/bin/yii: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | [ 21 | 'db' => require __DIR__ . '/../../config/test_db.php' 22 | ] 23 | ] 24 | ); 25 | 26 | 27 | $application = new yii\console\Application($config); 28 | $exitCode = $application->run(); 29 | exit($exitCode); 30 | -------------------------------------------------------------------------------- /views/variation/_form.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 15 | 16 | controller->action->id != 'update'): ?> 17 | 18 | field($model, 'category_id')->widget(Select2::class, [ 19 | 'data' => $list, 20 | ]) ?> 21 | 22 | 23 | 24 | field($model, 'name')->textInput(['maxlength' => true]) ?> 25 | 26 |
27 | 'btn btn-success']) ?> 28 |
29 | 30 | 31 | 32 |
33 | -------------------------------------------------------------------------------- /messages/pt-BR/yii.php: -------------------------------------------------------------------------------- 1 | '', 21 | 'Delete' => '', 22 | 'Update' => '', 23 | 'View' => '', 24 | ]; 25 | -------------------------------------------------------------------------------- /views/site/login.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Login'); 11 | 12 | ?> 13 | 14 | 17 | 18 | 19 | 20 | field($model, 'email')->textInput() ?> 21 | field($model, 'password')->passwordInput() ?> 22 | 23 |
24 |
25 | field($model, 'rememberMe')->checkbox() ?> 26 |
27 |
28 | 'btn btn-primary btn-block', 'name' => 'login-button']) ?> 29 |
30 |
31 | 32 | 33 | -------------------------------------------------------------------------------- /assets/AppAsset.php: -------------------------------------------------------------------------------- 1 | 19 | * @since 2.0 20 | */ 21 | class AppAsset extends AssetBundle 22 | { 23 | public $basePath = '@webroot'; 24 | public $baseUrl = '@web'; 25 | 26 | public $css = [ 27 | 'css/site.css', 28 | 'css/fix-toast.css' 29 | ]; 30 | 31 | public $js = [ 32 | ]; 33 | 34 | public $depends = [ 35 | YiiAsset::class, 36 | BootstrapAsset::class, 37 | BootstrapPluginAsset::class, 38 | ]; 39 | } 40 | -------------------------------------------------------------------------------- /codeception.yml: -------------------------------------------------------------------------------- 1 | actor: Tester 2 | bootstrap: _bootstrap.php 3 | paths: 4 | tests: tests 5 | log: tests/_output 6 | data: tests/_data 7 | helpers: tests/_support 8 | settings: 9 | memory_limit: 1024M 10 | colors: true 11 | modules: 12 | config: 13 | Yii2: 14 | configFile: 'config/test.php' 15 | 16 | # To enable code coverage: 17 | #coverage: 18 | # #c3_url: http://localhost:8080/index-test.php/ 19 | # enabled: true 20 | # #remote: true 21 | # #remote_config: '../codeception.yml' 22 | # whitelist: 23 | # include: 24 | # - models/* 25 | # - controllers/* 26 | # - commands/* 27 | # - mail/* 28 | # blacklist: 29 | # include: 30 | # - assets/* 31 | # - config/* 32 | # - runtime/* 33 | # - vendor/* 34 | # - views/* 35 | # - web/* 36 | # - tests/* 37 | -------------------------------------------------------------------------------- /views/expense/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Expense: {name}', [ 9 | 'name' => $model->name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Expenses'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 |
16 |
17 |
18 |
19 |

title) ?>

20 |
21 | render('_form', [ 22 | 'model' => $model, 23 | ]) ?> 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /views/product/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Product: {name}', [ 9 | 'name' => $model->name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Products'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 |
16 |
17 |
18 |
19 |

title) ?>

20 |
21 | render('_form', [ 22 | 'model' => $model, 23 | ]) ?> 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /views/employee/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Employee: {name}', [ 9 | 'name' => $model->full_name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Employees'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->usual_name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 | 16 |
17 |
18 |
19 |
20 |

title) ?>

21 |
22 | render('_form', [ 23 | 'model' => $model, 24 | ]) ?> 25 |
26 |
27 |
-------------------------------------------------------------------------------- /views/payment-method/update.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Payment Method: {name}', [ 9 | 'name' => $model->name, 10 | ]); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Payment Methods'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['view', 'id' => $model->id]]; 13 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update'); 14 | ?> 15 |
16 |
17 |
18 |
19 |

title) ?>

20 |
21 | render('_form', [ 22 | 'model' => $model, 23 | ]) ?> 24 |
25 |
26 |
-------------------------------------------------------------------------------- /views/expense/pay.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Expense Pay'); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Expenses'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | ?> 15 |

title) ?>

16 | 17 |
18 | 19 | 20 | 21 | field($model, 'paid_at')->widget(DatePicker::class, [ 22 | 'readonly' => true, 23 | 'pluginOptions' => [ 24 | 'format' => 'dd/mm/yyyy' 25 | ] 26 | ]) ?> 27 | 28 |
29 | 'btn btn-success']) ?> 30 |
31 | 32 | 33 | 34 |
-------------------------------------------------------------------------------- /views/variation/_search.php: -------------------------------------------------------------------------------- 1 | 10 | 11 | 36 | -------------------------------------------------------------------------------- /views/product/category.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Select Category'); 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Products'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | ?> 15 |
16 | 17 |

title) ?>

18 | 19 |
20 | 21 | 22 | 23 | field($model, 'category_id')->widget(Select2::class, [ 24 | 'data' => $list 25 | ]) ?> 26 | 27 |
28 | 'btn btn-success']) ?> 29 |
30 | 31 | 32 | 33 |
34 | 35 |
-------------------------------------------------------------------------------- /components/factories/PaymentMethodsDefaultFactory.php: -------------------------------------------------------------------------------- 1 | Yii::t('app', 'Money'), 15 | 'installment_limit' => '01', 16 | ], 17 | [ 18 | 'name' => Yii::t('app','Credit Card'), 19 | 'installment_limit' => '12', 20 | ], 21 | [ 22 | 'name' => Yii::t('app','Debit Card'), 23 | 'installment_limit' => '01', 24 | ], 25 | ]; 26 | 27 | foreach ($payments_methods as $method) { 28 | $model = new PaymentMethod(); 29 | $model->setAttributes(array_merge($method, ['company_id' => $company_id])); 30 | $model->save(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /views/company/_form.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | field($model, 'name')->textInput(['maxlength' => true]) ?> 18 | 19 | field($model, 'trade_name')->textInput(['maxlength' => true]) ?> 20 | 21 | field($model, 'ein')->widget(MaskedInput::class, [ 22 | 'mask' => ['99.999.999/9999-99'], 23 | ]) ?> 24 | 25 | field($model, 'email')->textInput(['maxlength' => true]) ?> 26 |
27 | 28 | 31 | 32 | 33 | 34 |
35 | -------------------------------------------------------------------------------- /migrations/m200721_145607_create_address_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%address}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'zip_code' => $this->string(9)->notNull(), 18 | 'number' => $this->string(8)->notNull(), 19 | 'street' => $this->string(64)->notNull(), 20 | 'neighborhood' => $this->string(64)->notNull(), 21 | 'city' => $this->string(64)->notNull(), 22 | 'federated_unit' => $this->string(2)->notNull(), 23 | 'complement' => $this->string(128), 24 | ]); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function safeDown() 31 | { 32 | $this->dropTable('{{%address}}'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /views/order/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 | field($model, 'code')->textInput(['maxlength' => true]) ?> 16 | 17 | field($model, 'total_value')->textInput(['maxlength' => true]) ?> 18 | 19 | field($model, 'note')->textarea(['rows' => 6]) ?> 20 | 21 | field($model, 'is_quotation')->textInput() ?> 22 | 23 | field($model, 'created_at')->textInput() ?> 24 | 25 | field($model, 'updated_at')->textInput() ?> 26 | 27 | field($model, 'company_id')->textInput() ?> 28 | 29 |
30 | 'btn btn-success']) ?> 31 |
32 | 33 | 34 | 35 |
36 | -------------------------------------------------------------------------------- /models/Migration.php: -------------------------------------------------------------------------------- 1 | 180], 32 | [['version'], 'unique'], 33 | ]; 34 | } 35 | 36 | /** 37 | * {@inheritdoc} 38 | */ 39 | public function attributeLabels() 40 | { 41 | return [ 42 | 'version' => Yii::t('app', 'Version'), 43 | 'apply_time' => Yii::t('app', 'Apply Time'), 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /migrations/m200721_145614_create_category_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%category}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'name' => $this->string(64)->notNull(), 18 | 'created_at' => $this->dateTime()->notNull(), 19 | 'updated_at' => $this->dateTime(), 20 | 'company_id' => $this->integer()->unsigned()->notNull(), 21 | ]); 22 | 23 | $this->addForeignKey('fk-category-company_id', 'category', 'company_id', 'company', 'id', 'CASCADE'); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function safeDown() 30 | { 31 | $this->dropForeignKey('fk-category-company_id', 'category'); 32 | $this->dropTable('{{%category}}'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /migrations/m200721_145616_create_variation_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%variation}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'name' => $this->string(64)->notNull(), 18 | 'created_at' => $this->dateTime()->notNull(), 19 | 'updated_at' => $this->dateTime(), 20 | 'category_id' => $this->integer()->unsigned()->notNull() 21 | ]); 22 | 23 | $this->addForeignKey('fk-variation-category_id', 'variation', 'category_id', 'category', 'id', 'CASCADE'); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function safeDown() 30 | { 31 | $this->dropForeignKey('fk-variation-category_id', 'variation'); 32 | $this->dropTable('{{%variation}}'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /components/behaviors/FormatterDateBehavior.php: -------------------------------------------------------------------------------- 1 | 'formatter', 20 | ActiveRecord::EVENT_BEFORE_UPDATE => 'formatter' 21 | ]; 22 | } 23 | 24 | public function formatter() 25 | { 26 | $formatter = Yii::$app->formatter; 27 | 28 | foreach ($this->attributes as $attr => $format) { 29 | $date = $this->owner->$attr; 30 | if ($format == self::FORMAT_DATETIME) 31 | $date = $formatter->asDateTimeDefault($date); 32 | else if ($format == self::FORMAT_DATE) 33 | $date = $formatter->asDateDefault($date); 34 | $this->owner->$attr = $date; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /views/layouts/login.php: -------------------------------------------------------------------------------- 1 | 9 | beginPage() ?> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | registerCsrfMetaTags() ?> 18 | <?= Html::encode($this->title) ?> 19 | head() ?> 20 | 21 | 22 | 23 | beginBody() ?> 24 | 37 | endBody() ?> 38 | 39 | 40 | endPage() ?> -------------------------------------------------------------------------------- /migrations/m200721_145621_create_sale_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%sale}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'amount_paid' => $this->decimal(10,2), 18 | 'discount' => $this->decimal(10,2), 19 | 'is_canceled' => $this->tinyInteger(1), 20 | 'sale_at' => $this->dateTime(), 21 | 'canceled_at' => $this->dateTime(), 22 | 'updated_at' => $this->dateTime(), 23 | 'order_id' => $this->integer()->unsigned()->notNull() 24 | ]); 25 | 26 | $this->addForeignKey('fk-sale-order_id', 'sale', 'order_id', 'order', 'id', 'CASCADE'); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function safeDown() 33 | { 34 | $this->dropForeignKey('fk-sale-order_id', 'sale'); 35 | $this->dropTable('{{%sale}}'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /views/layouts/center.php: -------------------------------------------------------------------------------- 1 | registerCss( 9 | <<< CSS 10 | .lockscreen-wrapper { 11 | max-width: 600px; 12 | } 13 | CSS 14 | ) 15 | ?> 16 | beginPage() ?> 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | registerCsrfMetaTags() ?> 25 | <?= Html::encode($this->title) ?> 26 | head() ?> 27 | 28 | 29 | beginBody() ?> 30 | 31 | 32 |
33 | 36 | 37 |
38 | 39 | 40 | endBody() ?> 41 | 42 | 43 | 44 | endPage() ?> -------------------------------------------------------------------------------- /tests/acceptance/ContactCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(Url::toRoute('/site/contact')); 10 | } 11 | 12 | public function contactPageWorks(AcceptanceTester $I) 13 | { 14 | $I->wantTo('ensure that contact page works'); 15 | $I->see('Contact', 'h1'); 16 | } 17 | 18 | public function contactFormCanBeSubmitted(AcceptanceTester $I) 19 | { 20 | $I->amGoingTo('submit contact form with correct data'); 21 | $I->fillField('#contactform-name', 'tester'); 22 | $I->fillField('#contactform-email', 'tester@example.com'); 23 | $I->fillField('#contactform-subject', 'test subject'); 24 | $I->fillField('#contactform-body', 'test content'); 25 | $I->fillField('#contactform-verifycode', 'testme'); 26 | 27 | $I->click('contact-button'); 28 | 29 | $I->wait(2); // wait for button to be clicked 30 | 31 | $I->dontSeeElement('#contact-form'); 32 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /views/layouts/register.php: -------------------------------------------------------------------------------- 1 | 9 | beginPage() ?> 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | registerCsrfMetaTags() ?> 18 | <?= Html::encode($this->title) ?> 19 | head() ?> 20 | 21 | 22 | 23 | beginBody() ?> 24 |
25 | 28 | 29 |
30 |
31 | 32 | 33 | 34 |
35 | 36 |
37 | 38 |
39 |
40 | endBody() ?> 41 | 42 | 43 | endPage() ?> -------------------------------------------------------------------------------- /migrations/m200721_145620_create_order_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%order}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'code' => $this->string(20)->notNull(), 18 | 'total_value' => $this->decimal(10,2), 19 | 'note' => $this->text(), 20 | 'is_quotation' => $this->tinyInteger(1)->defaultValue(0), 21 | 'created_at' => $this->dateTime()->notNull(), 22 | 'updated_at' => $this->dateTime(), 23 | 'company_id' => $this->integer()->unsigned()->notNull() 24 | ]); 25 | 26 | $this->addForeignKey('fk-order-company_id', 'order', 'company_id', 'company', 'id', 'CASCADE'); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function safeDown() 33 | { 34 | $this->dropForeignKey('fk-order-company_id', 'order'); 35 | $this->dropTable('{{%order}}'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /views/employee/address.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Update Company Address'); 9 | // $this->params['breadcrumbs'][] = ['label' => $model->name, 'url' => ['index']]; 10 | // $this->params['breadcrumbs'][] = Yii::t('app', 'Update Address'); 11 | 12 | $this->title = Yii::t('app', 'Update Address Employee: {name}', [ 13 | 'name' => $model->full_name, 14 | ]); 15 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Employees'), 'url' => ['index']]; 16 | $this->params['breadcrumbs'][] = ['label' => $model->usual_name, 'url' => ['view', 'id' => $model->id]]; 17 | $this->params['breadcrumbs'][] = Yii::t('app', 'Update Address'); 18 | ?> 19 | 20 |
21 |
22 |
23 |
24 |

title) ?>

25 |
26 | render('/address/_form', [ 27 | 'model' => $address, 28 | ]) ?> 29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /vagrant/nginx/app.conf: -------------------------------------------------------------------------------- 1 | server { 2 | charset utf-8; 3 | client_max_body_size 128M; 4 | sendfile off; 5 | 6 | listen 80; ## listen for ipv4 7 | #listen [::]:80 default_server ipv6only=on; ## listen for ipv6 8 | 9 | server_name yii2basic.test; 10 | root /app/web/; 11 | index index.php; 12 | 13 | access_log /app/vagrant/nginx/log/yii2basic.access.log; 14 | error_log /app/vagrant/nginx/log/yii2basic.error.log; 15 | 16 | location / { 17 | # Redirect everything that isn't a real file to index.php 18 | try_files $uri $uri/ /index.php$is_args$args; 19 | } 20 | 21 | # uncomment to avoid processing of calls to non-existing static files by Yii 22 | #location ~ \.(js|css|png|jpg|gif|swf|ico|pdf|mov|fla|zip|rar)$ { 23 | # try_files $uri =404; 24 | #} 25 | #error_page 404 /404.html; 26 | 27 | location ~ \.php$ { 28 | include fastcgi_params; 29 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 30 | #fastcgi_pass 127.0.0.1:9000; 31 | fastcgi_pass unix:/var/run/php/php7.0-fpm.sock; 32 | try_files $uri =404; 33 | } 34 | 35 | location ~ /\.(ht|svn|git) { 36 | deny all; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /migrations/m200721_145622_create_pay_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%pay}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'value' => $this->decimal(10,2)->notNull(), 18 | 'installments' => $this->smallInteger()->unsigned(), 19 | 'payment_method_id' => $this->integer()->unsigned()->notNull(), 20 | 'sale_id' => $this->integer()->unsigned()->notNull(), 21 | ]); 22 | 23 | $this->addForeignKey('fk-pay-payment_method_id', 'pay', 'payment_method_id', 'payment_method', 'id', 'CASCADE'); 24 | $this->addForeignKey('fk-pay-sale_id', 'pay', 'sale_id', 'sale', 'id', 'CASCADE'); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function safeDown() 31 | { 32 | $this->dropForeignKey('fk-pay-payment_method_id', 'pay'); 33 | $this->dropForeignKey('fk-pay-sale_id', 'pay'); 34 | $this->dropTable('{{%pay}}'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /migrations/m200721_145612_create_expense_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%expense}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'name' => $this->string(64)->notNull(), 18 | 'description' => $this->text(), 19 | 'value' => $this->decimal(10,2)->notNull(), 20 | 'payday' => $this->date()->notNull(), 21 | 'paid_at' => $this->date(), 22 | 'created_at' => $this->dateTime()->notNull(), 23 | 'updated_at' => $this->dateTime(), 24 | 'company_id' => $this->integer()->unsigned()->notNull(), 25 | ]); 26 | 27 | $this->addForeignKey('fk-expense-company_id', 'expense', 'company_id', 'company', 'id', 'CASCADE'); 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function safeDown() 34 | { 35 | $this->dropForeignKey('fk-expense-company_id', 'expense'); 36 | $this->dropTable('{{%expense}}'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /config/test.php: -------------------------------------------------------------------------------- 1 | 'basic-tests', 10 | 'basePath' => dirname(__DIR__), 11 | 'aliases' => [ 12 | '@bower' => '@vendor/bower-asset', 13 | '@npm' => '@vendor/npm-asset', 14 | ], 15 | 'language' => 'en-US', 16 | 'components' => [ 17 | 'db' => $db, 18 | 'mailer' => [ 19 | 'useFileTransport' => true, 20 | ], 21 | 'assetManager' => [ 22 | 'basePath' => __DIR__ . '/../web/assets', 23 | ], 24 | 'urlManager' => [ 25 | 'showScriptName' => true, 26 | ], 27 | 'user' => [ 28 | 'identityClass' => 'app\models\User', 29 | ], 30 | 'request' => [ 31 | 'cookieValidationKey' => 'test', 32 | 'enableCsrfValidation' => false, 33 | // but if you absolutely need it set cookie domain to localhost 34 | /* 35 | 'csrfCookie' => [ 36 | 'domain' => 'localhost', 37 | ], 38 | */ 39 | ], 40 | ], 41 | 'params' => $params, 42 | ]; 43 | -------------------------------------------------------------------------------- /migrations/m201005_185935_create_order_item_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%order_item}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'amount' => $this->decimal(10,2)->notNull(), 18 | 'unit_price' => $this->decimal(10,2)->notNull(), 19 | 'order_id' => $this->integer()->unsigned()->notNull(), 20 | 'product_id' => $this->integer()->unsigned()->notNull(), 21 | ]); 22 | 23 | $this->addForeignKey('fk-pay-order_id', 'order_item', 'order_id', 'order', 'id', 'CASCADE'); 24 | $this->addForeignKey('fk-pay-product_id', 'order_item', 'product_id', 'product', 'id', 'CASCADE'); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function safeDown() 31 | { 32 | $this->dropForeignKey('fk-pay-order_id', 'order_item'); 33 | $this->dropForeignKey('fk-pay-product_id', 'order_item'); 34 | $this->dropTable('{{%order_item}}'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /migrations/m200721_145609_create_company_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%company}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'name' => $this->string(64)->notNull(), 18 | 'trade_name' => $this->string(64), 19 | 'ein' => $this->string(18)->notNull(), 20 | 'phone_number' => $this->string(16)->notNull(), 21 | 'email' => $this->string(64)->notNull(), 22 | 'created_at' => $this->dateTime()->notNull(), 23 | 'updated_at' => $this->dateTime(), 24 | 'address_id' => $this->integer()->unsigned(), 25 | ]); 26 | 27 | $this->addForeignKey('fk-company-address_id', 'company', 'address_id', 'address', 'id', 'CASCADE'); 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function safeDown() 34 | { 35 | $this->dropForeignKey('fk-company-address_id', 'company'); 36 | $this->dropTable('{{%company}}'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /views/operation/_form.php: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 14 | 15 |
16 | 17 | field($model, 'product_id')->widget(Select2::class, [ 18 | 'data' => $products, 19 | 'options' => [ 20 | 'placeholder' => Yii::t('app', 'Select Product'), 21 | ], 22 | 'pluginOptions' => [ 23 | 'allowClear' => true, 24 | ], 25 | ]) ?> 26 | 27 | field($model, 'in_out')->dropDownList([ 28 | 1 => Yii::t('app', 'Input'), 29 | 0 => Yii::t('app', 'Output'), 30 | ], ['custom' => true]) ?> 31 | 32 | field($model, 'amount')->textInput(['maxlength' => true]) ?> 33 | 34 | field($model, 'reason')->textInput(['maxlength' => true]) ?> 35 | 36 |
37 | 38 | 41 | 42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /migrations/m200721_145613_create_payment_method_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%payment_method}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'name' => $this->string(64)->notNull(), 18 | 'installment_limit' => $this->tinyInteger()->unsigned()->notNull(), 19 | 'is_deleted' => $this->tinyInteger(1)->defaultValue(0), 20 | 'created_at' => $this->dateTime()->notNull(), 21 | 'updated_at' => $this->dateTime(), 22 | 'deleted_at' => $this->dateTime(), 23 | 'company_id' => $this->integer()->unsigned()->notNull(), 24 | ]); 25 | 26 | $this->addForeignKey('fk-payment_method-company_id', 'payment_method', 'company_id', 'company', 'id', 'CASCADE'); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function safeDown() 33 | { 34 | $this->dropForeignKey('fk-payment_method-company_id', 'payment_method'); 35 | $this->dropTable('{{%payment_method}}'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/unit/models/UserTest.php: -------------------------------------------------------------------------------- 1 | username)->equals('admin'); 13 | 14 | expect_not(User::findIdentity(999)); 15 | } 16 | 17 | public function testFindUserByAccessToken() 18 | { 19 | expect_that($user = User::findIdentityByAccessToken('100-token')); 20 | expect($user->username)->equals('admin'); 21 | 22 | expect_not(User::findIdentityByAccessToken('non-existing')); 23 | } 24 | 25 | public function testFindUserByUsername() 26 | { 27 | expect_that($user = User::findByUsername('admin')); 28 | expect_not(User::findByUsername('not-admin')); 29 | } 30 | 31 | /** 32 | * @depends testFindUserByUsername 33 | */ 34 | public function testValidateUser($user) 35 | { 36 | $user = User::findByUsername('admin'); 37 | expect_that($user->validateAuthKey('test100key')); 38 | expect_not($user->validateAuthKey('test102key')); 39 | 40 | expect_that($user->validatePassword('admin')); 41 | expect_not($user->validatePassword('123456')); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/unit/models/LoginFormTest.php: -------------------------------------------------------------------------------- 1 | user->logout(); 14 | } 15 | 16 | public function testLoginNoUser() 17 | { 18 | $this->model = new LoginForm([ 19 | 'username' => 'not_existing_username', 20 | 'password' => 'not_existing_password', 21 | ]); 22 | 23 | expect_not($this->model->login()); 24 | expect_that(\Yii::$app->user->isGuest); 25 | } 26 | 27 | public function testLoginWrongPassword() 28 | { 29 | $this->model = new LoginForm([ 30 | 'username' => 'demo', 31 | 'password' => 'wrong_password', 32 | ]); 33 | 34 | expect_not($this->model->login()); 35 | expect_that(\Yii::$app->user->isGuest); 36 | expect($this->model->errors)->hasKey('password'); 37 | } 38 | 39 | public function testLoginCorrect() 40 | { 41 | $this->model = new LoginForm([ 42 | 'username' => 'demo', 43 | 'password' => 'demo', 44 | ]); 45 | 46 | expect_that($this->model->login()); 47 | expect_not(\Yii::$app->user->isGuest); 48 | expect($this->model->errors)->hasntKey('password'); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /config/console.php: -------------------------------------------------------------------------------- 1 | 'basic-console', 8 | 'basePath' => dirname(__DIR__), 9 | 'bootstrap' => ['log'], 10 | 'controllerNamespace' => 'app\commands', 11 | 'aliases' => [ 12 | '@bower' => '@vendor/bower-asset', 13 | '@npm' => '@vendor/npm-asset', 14 | '@tests' => '@app/tests', 15 | ], 16 | 'components' => [ 17 | 'cache' => [ 18 | 'class' => 'yii\caching\FileCache', 19 | ], 20 | 'log' => [ 21 | 'targets' => [ 22 | [ 23 | 'class' => 'yii\log\FileTarget', 24 | 'levels' => ['error', 'warning'], 25 | ], 26 | ], 27 | ], 28 | 'authManager' => [ 29 | 'class' => 'yii\rbac\PhpManager', 30 | ], 31 | 'db' => $db, 32 | ], 33 | 'params' => $params, 34 | /* 35 | 'controllerMap' => [ 36 | 'fixture' => [ // Fixture generation command line. 37 | 'class' => 'yii\faker\FixtureController', 38 | ], 39 | ], 40 | */ 41 | ]; 42 | 43 | if (YII_ENV_DEV) { 44 | // configuration adjustments for 'dev' environment 45 | $config['bootstrap'][] = 'gii'; 46 | $config['modules']['gii'] = [ 47 | 'class' => 'yii\gii\Module', 48 | ]; 49 | } 50 | 51 | return $config; 52 | -------------------------------------------------------------------------------- /migrations/m200721_145615_create_product_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%product}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'code' => $this->string(32), 18 | 'name' => $this->string(64)->notNull(), 19 | 'unit_price' => $this->decimal(10,2)->notNull(), 20 | 'amount' => $this->decimal(10,2)->notNull(), 21 | 'max_amount' => $this->decimal(10,2), 22 | 'min_amount' => $this->decimal(10,2), 23 | 'is_deleted' => $this->tinyInteger(1)->defaultValue(0), 24 | 'created_at' => $this->dateTime()->notNull(), 25 | 'updated_at' => $this->dateTime(), 26 | 'deleted_at' => $this->dateTime(), 27 | 'category_id' => $this->integer()->unsigned()->notNull() 28 | ]); 29 | 30 | $this->addForeignKey('fk-product-category_id', 'product', 'category_id', 'category', 'id', 'CASCADE'); 31 | } 32 | 33 | /** 34 | * {@inheritdoc} 35 | */ 36 | public function safeDown() 37 | { 38 | $this->dropForeignKey('fk-product-category_id', 'product'); 39 | $this->dropTable('{{%product}}'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /migrations/m200721_145618_create_operation_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%operation}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'in_out' => $this->tinyInteger(1)->notNull(), 18 | 'amount' => $this->decimal(10,2)->notNull(), 19 | 'reason' => $this->string(64)->notNull(), 20 | 'created_at' => $this->dateTime()->notNull(), 21 | 'updated_at' => $this->dateTime(), 22 | 'product_id' => $this->integer()->unsigned()->notNull(), 23 | 'employee_id' => $this->integer()->unsigned()->notNull(), 24 | ]); 25 | 26 | $this->addForeignKey('fk-operation-product_id', 'operation', 'product_id', 'product', 'id', 'CASCADE'); 27 | $this->addForeignKey('fk-operation-employee_id', 'operation', 'employee_id', 'employee', 'id', 'CASCADE'); 28 | } 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function safeDown() 34 | { 35 | $this->dropForeignKey('fk-operation-product_id', 'operation'); 36 | $this->dropForeignKey('fk-operation-employee_id', 'operation'); 37 | $this->dropTable('{{%operation}}'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /modules/api/controllers/ProductController.php: -------------------------------------------------------------------------------- 1 | ContentNegotiator::class, 18 | 'formats' => [ 19 | 'application/json' => Response::FORMAT_JSON, 20 | ], 21 | ], 22 | 'access' => [ 23 | 'class' => AccessControl::class, 24 | 'rules' => [ 25 | [ 26 | 'actions' => ['query'], 27 | 'allow' => true, 28 | 'roles' => ['cashier'] 29 | ], 30 | ], 31 | ], 32 | ]; 33 | } 34 | 35 | public function actionQuery($q = null, $id = null) 36 | { 37 | $out = ['results' => ['id' => '', 'name' => '', 'price' => '']]; 38 | 39 | if (!is_null($q)) { 40 | $data = ProductSearch::findInNameWithVariation(trim($q)); 41 | $out['results'] = array_map(function($product) { 42 | return $product->toArray(); 43 | }, array_values($data)); 44 | } 45 | return $out; 46 | } 47 | } -------------------------------------------------------------------------------- /views/site/error.php: -------------------------------------------------------------------------------- 1 | title = $name; 12 | 13 | $this->registerCss( 14 | <<< CSS 15 | .error-page > .error-content { 16 | margin: 0; 17 | } 18 | 19 | .error-messages { 20 | margin-left: 20px; 21 | } 22 | 23 | .headline { 24 | font-size: 100px; 25 | float: left; 26 | } 27 | CSS 28 | ); 29 | ?> 30 |
31 |
32 |
33 |

34 | 35 |

36 |
37 |

#statusCode)) ?>

38 | 39 | 40 |

41 | main page here.', [ 42 | 'url' => Url::to(['index']) 43 | ]); ?> 44 |

45 |
46 |
47 |
48 |
49 |
-------------------------------------------------------------------------------- /models/CategorySearch.php: -------------------------------------------------------------------------------- 1 | $query, 48 | ]); 49 | 50 | $this->load($params); 51 | 52 | if (!$this->validate()) { 53 | // uncomment the following line if you do not want to return any records when validation fails 54 | // $query->where('0=1'); 55 | return $dataProvider; 56 | } 57 | 58 | $query->andFilterWhere(['like', 'category.name', $this->name]); 59 | 60 | return $dataProvider; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /components/formatters/AppFormatter.php: -------------------------------------------------------------------------------- 1 | nullDisplay; 15 | 16 | $value = explode(" ", $value); 17 | $value[0] = $this->asDateDefault($value[0]); 18 | return implode(" ", $value); 19 | } 20 | 21 | public function asDateDefault($value) 22 | { 23 | if ($value === null) 24 | return $this->nullDisplay; 25 | 26 | return implode("-",array_reverse(explode("/", $value))); 27 | } 28 | 29 | public function asInputOrOutput($value) 30 | { 31 | return $value == 0 32 | ? Yii::t('app', 'Output') 33 | : Yii::t('app', 'Input'); 34 | } 35 | 36 | public function asAmount($value) 37 | { 38 | if ($value === null) 39 | return $this->nullDisplay; 40 | else if (empty($value)) 41 | $value = 0; 42 | 43 | return number_format($value, 2, ',', '.'); 44 | } 45 | 46 | public function asActive($value) 47 | { 48 | return $value 49 | ? Yii::t('app', 'No') 50 | : Yii::t('app', 'Yes'); 51 | } 52 | 53 | public function getCurrencySymbol() 54 | { 55 | $formatter = new NumberFormatter($this->locale, NumberFormatter::CURRENCY); 56 | return $formatter->getSymbol(NumberFormatter::CURRENCY_SYMBOL); 57 | } 58 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software LLC (http://www.yiisoft.com) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software LLC nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /components/Seller.php: -------------------------------------------------------------------------------- 1 | sender; 19 | 20 | Sale::getDb()->transaction(function ($db) use ($sale) { 21 | $sale->sale_at = new Expression('NOW()'); 22 | $sale->is_sold = 1; 23 | if (!$sale->update()) { 24 | foreach($sale->firstErrors as $erro) break; 25 | throw new BadRequestHttpException($erro); 26 | } 27 | 28 | $items = $sale->order->orderItems; 29 | self::descraseItemsStock($items); 30 | }); 31 | } 32 | 33 | private static function descraseItemsStock($items) 34 | { 35 | foreach ($items as $item) { 36 | $operation = new Operation(); 37 | $operation->attributes = [ 38 | 'in_out' => 0, 39 | 'amount' => $item->amount, 40 | 'reason' => Yii::t('app', 'Product sale.'), 41 | 'product_id' => $item->product_id, 42 | 'employee_id' => Yii::$app->user->id, 43 | ]; 44 | 45 | if (!$operation->save()) { 46 | foreach($operation->firstErrors as $erro) break; 47 | throw new BadRequestHttpException($erro); 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /components/factories/AccountFactory.php: -------------------------------------------------------------------------------- 1 | getDb()->beginTransaction(); 15 | 16 | try { 17 | $company = self::createCompany($company); 18 | self::createEmployee($employee, $company->id); 19 | PaymentMethodsDefaultFactory::create($company->id); 20 | 21 | $trasaction->commit(); 22 | } catch (Exception $e) { 23 | $trasaction->rollBack(); 24 | throw $e; 25 | } 26 | } 27 | 28 | private static function createCompany($attributes) 29 | { 30 | $company = new Company(); 31 | $company->setAttributes($attributes); 32 | 33 | if (!$company->save()) { 34 | foreach ($company->firstErrors as $erro) break; 35 | throw new Exception(Yii::t('app', 'Failed to register company.') . ' ' . $erro); 36 | } 37 | 38 | return $company; 39 | } 40 | 41 | private static function createEmployee($attributes, $company_id) 42 | { 43 | $employee = new Employee(); 44 | $employee->setAttributes(array_merge($attributes, ['company_id' => $company_id])); 45 | $employee->password_repeat = $employee->password; 46 | 47 | if (!$employee->save()) { 48 | foreach ($employee->firstErrors as $erro) break; 49 | throw new Exception(Yii::t('app', 'Failed to register your account.') . ' ' . $erro); 50 | } 51 | 52 | return $employee; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /modules/api/controllers/OrderController.php: -------------------------------------------------------------------------------- 1 | ContentNegotiator::class, 21 | 'formats' => [ 22 | 'application/json' => Response::FORMAT_JSON, 23 | ], 24 | ], 25 | 'access' => [ 26 | 'class' => AccessControl::class, 27 | 'rules' => [ 28 | [ 29 | 'actions' => ['index'], 30 | 'allow' => true, 31 | 'roles' => ['cashier'] 32 | ], 33 | ], 34 | ], 35 | 'verbs' => [ 36 | 'class' => VerbFilter::class, 37 | 'actions' => [ 38 | 'index' => ['GET'], 39 | ], 40 | ], 41 | ]; 42 | } 43 | 44 | public function actionIndex($id) 45 | { 46 | $order = $this->findModel($id); 47 | return $order; 48 | } 49 | 50 | protected function findModel($id) 51 | { 52 | $model = Order::find() 53 | ->andWhere(['order.id' => $id]) 54 | ->one(); 55 | 56 | if ($model !== null) 57 | return $model; 58 | 59 | throw new NotFoundHttpException(Yii::t('app', 'Order not exist.')); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/unit/models/ContactFormTest.php: -------------------------------------------------------------------------------- 1 | model = $this->getMockBuilder('app\models\ContactForm') 20 | ->setMethods(['validate']) 21 | ->getMock(); 22 | 23 | $this->model->expects($this->once()) 24 | ->method('validate') 25 | ->willReturn(true); 26 | 27 | $this->model->attributes = [ 28 | 'name' => 'Tester', 29 | 'email' => 'tester@example.com', 30 | 'subject' => 'very important letter subject', 31 | 'body' => 'body of current message', 32 | ]; 33 | 34 | expect_that($this->model->contact('admin@example.com')); 35 | 36 | // using Yii2 module actions to check email was sent 37 | $this->tester->seeEmailIsSent(); 38 | 39 | /** @var MessageInterface $emailMessage */ 40 | $emailMessage = $this->tester->grabLastSentEmail(); 41 | expect('valid email is sent', $emailMessage)->isInstanceOf('yii\mail\MessageInterface'); 42 | expect($emailMessage->getTo())->hasKey('admin@example.com'); 43 | expect($emailMessage->getFrom())->hasKey('noreply@example.com'); 44 | expect($emailMessage->getReplyTo())->hasKey('tester@example.com'); 45 | expect($emailMessage->getSubject())->equals('very important letter subject'); 46 | expect($emailMessage->toString())->stringContainsString('body of current message'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /migrations/m200721_145611_create_employee_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%employee}}', [ 16 | 'id' => $this->primaryKey()->unsigned(), 17 | 'full_name' => $this->string(128)->notNull(), 18 | 'usual_name' => $this->string(32)->notNull(), 19 | 'ssn' => $this->string(14)->notNull(), 20 | 'birthday' => $this->date()->notNull(), 21 | 'phone_number' => $this->string(16)->notNull(), 22 | 'email' => $this->string(64)->notNull(), 23 | 'password' => $this->string(255)->notNull(), 24 | 'is_manager' => $this->tinyInteger(1)->defaultValue(0), 25 | 'is_deleted' => $this->tinyInteger(1)->defaultValue(0), 26 | 'created_at' => $this->dateTime()->notNull(), 27 | 'updated_at' => $this->dateTime(), 28 | 'deleted_at' => $this->dateTime(), 29 | 'address_id' => $this->integer()->unsigned(), 30 | 'company_id' => $this->integer()->unsigned()->notNull(), 31 | ]); 32 | 33 | $this->addForeignKey('fk-employee-address_id', 'employee', 'address_id', 'address', 'id', 'CASCADE'); 34 | $this->addForeignKey('fk-employee-company_id', 'employee', 'company_id', 'company', 'id', 'CASCADE'); 35 | } 36 | 37 | /** 38 | * {@inheritdoc} 39 | */ 40 | public function safeDown() 41 | { 42 | $this->dropForeignKey('fk-employee-address_id', 'employee'); 43 | $this->dropForeignKey('fk-employee-company_id', 'employee'); 44 | $this->dropTable('{{%employee}}'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/functional/LoginFormCest.php: -------------------------------------------------------------------------------- 1 | amOnRoute('site/login'); 8 | } 9 | 10 | public function openLoginPage(\FunctionalTester $I) 11 | { 12 | $I->see('Login', 'h1'); 13 | 14 | } 15 | 16 | // demonstrates `amLoggedInAs` method 17 | public function internalLoginById(\FunctionalTester $I) 18 | { 19 | $I->amLoggedInAs(100); 20 | $I->amOnPage('/'); 21 | $I->see('Logout (admin)'); 22 | } 23 | 24 | // demonstrates `amLoggedInAs` method 25 | public function internalLoginByInstance(\FunctionalTester $I) 26 | { 27 | $I->amLoggedInAs(\app\models\User::findByUsername('admin')); 28 | $I->amOnPage('/'); 29 | $I->see('Logout (admin)'); 30 | } 31 | 32 | public function loginWithEmptyCredentials(\FunctionalTester $I) 33 | { 34 | $I->submitForm('#login-form', []); 35 | $I->expectTo('see validations errors'); 36 | $I->see('Username cannot be blank.'); 37 | $I->see('Password cannot be blank.'); 38 | } 39 | 40 | public function loginWithWrongCredentials(\FunctionalTester $I) 41 | { 42 | $I->submitForm('#login-form', [ 43 | 'LoginForm[username]' => 'admin', 44 | 'LoginForm[password]' => 'wrong', 45 | ]); 46 | $I->expectTo('see validations errors'); 47 | $I->see('Incorrect username or password.'); 48 | } 49 | 50 | public function loginSuccessfully(\FunctionalTester $I) 51 | { 52 | $I->submitForm('#login-form', [ 53 | 'LoginForm[username]' => 'admin', 54 | 'LoginForm[password]' => 'admin', 55 | ]); 56 | $I->see('Logout (admin)'); 57 | $I->dontSeeElement('form#login-form'); 58 | } 59 | } -------------------------------------------------------------------------------- /modules/api/controllers/AddressController.php: -------------------------------------------------------------------------------- 1 | ContentNegotiator::class, 19 | 'formats' => [ 20 | 'application/json' => Response::FORMAT_JSON, 21 | ], 22 | ], 23 | 'access' => [ 24 | 'class' => AccessControl::class, 25 | 'rules' => [ 26 | [ 27 | 'actions' => ['index'], 28 | 'allow' => true, 29 | 'roles' => ['cashier'] 30 | ], 31 | ], 32 | ], 33 | ]; 34 | } 35 | 36 | public function actionIndex($code) 37 | { 38 | if (preg_match('/\d{8}/', $code)) { 39 | $url = 'http://viacep.com.br/ws/' . $code . '/json'; 40 | $response = json_decode(file_get_contents($url)); 41 | 42 | if (!isset($response->erro)) { 43 | return [ 44 | 'federated_unit' => $response->uf, 45 | 'city' => $response->localidade, 46 | 'neighborhood' => $response->bairro, 47 | 'street' => $response->logradouro, 48 | 'complement' => $response->complemento, 49 | 'zip_code' => $code 50 | ]; 51 | } 52 | } 53 | 54 | throw new BadRequestHttpException(Yii::t('app', 'Zip code does not exist, try again with another zip code.')); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /models/PaySearch.php: -------------------------------------------------------------------------------- 1 | $query, 49 | ]); 50 | 51 | $this->load($params); 52 | 53 | if (!$this->validate()) { 54 | // uncomment the following line if you do not want to return any records when validation fails 55 | // $query->where('0=1'); 56 | return $dataProvider; 57 | } 58 | 59 | // grid filtering conditions 60 | $query->andFilterWhere([ 61 | 'id' => $this->id, 62 | 'value' => $this->value, 63 | 'installments' => $this->installments, 64 | 'payment_method_id' => $this->payment_method_id, 65 | 'sale_id' => $this->sale_id, 66 | ]); 67 | 68 | return $dataProvider; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /views/employee/_form.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | field($model, 'full_name')->textInput(['maxlength' => true]) ?> 19 | 20 | field($model, 'usual_name')->textInput(['maxlength' => true]) ?> 21 | 22 | field($model, 'ssn')->widget(MaskedInput::class, [ 23 | 'mask' => ['999.999.999-99'], 24 | ]) ?> 25 | 26 | field($model, 'birthday')->widget(DatePicker::class, [ 27 | 'type' => DatePicker::TYPE_INPUT, 28 | 'pluginOptions' => [ 29 | 'autoclose'=>true, 30 | 'format' => 'dd/mm/yyyy' 31 | ] 32 | ]) ?> 33 | 34 | field($model, 'email')->textInput(['maxlength' => true]) ?> 35 | 36 | field($model, 'phone_number')->widget(MaskedInput::class, [ 37 | 'mask' => ['(99) 9999-9999', '(99) 99999-9999'], 38 | ]) ?> 39 | 40 | field($model, 'is_manager')->radioList([0 => Yii::t('app', 'Cashier'), 1 => Yii::t('app', 'Manager')]) ?> 41 | 42 | controller->action->id != 'update'): ?> 43 | field($model, 'password')->passwordInput(['maxlength' => true]) ?> 44 | field($model, 'password_repeat')->passwordInput(['maxlength' => true]) ?> 45 | 46 |
47 | 48 | 51 | 52 | 53 | 54 |
55 | -------------------------------------------------------------------------------- /commands/RbacController.php: -------------------------------------------------------------------------------- 1 | authManager; 15 | 16 | // Create Rule 17 | $user_group = new UserGroupRule; 18 | $auth->add($user_group); 19 | $update_owner = new UpdateOnwer; 20 | $auth->add($update_owner); 21 | 22 | // Create Permissions 23 | $update_employee = $auth->createPermission('update_employee'); 24 | $update_employee->description = 'Update Employee'; 25 | $update_employee->ruleName = $update_owner->name; 26 | $auth->add($update_employee); 27 | 28 | // Create Roles 29 | $admin = $auth->createRole('admin'); 30 | $admin->ruleName = $user_group->name; 31 | $cashier = $auth->createRole('cashier'); 32 | $cashier->ruleName = $user_group->name; 33 | $auth->add($admin); 34 | $auth->add($cashier); 35 | 36 | // Chinding 37 | $auth->addChild($cashier, $update_employee); 38 | $auth->addChild($admin, $cashier); 39 | } 40 | 41 | private function addPermissions($permissions) 42 | { 43 | $auth = Yii::$app->authManager; 44 | 45 | foreach ($permissions as $key => $value) { 46 | $permission = $auth->createPermission($key); 47 | $permission->description = $value; 48 | $auth->add($permission); 49 | $permissions[$key] = $permission; 50 | } 51 | 52 | return $permissions; 53 | } 54 | 55 | private function childingPermissions($role, $permissions) 56 | { 57 | $auth = Yii::$app->authManager; 58 | 59 | foreach ($permissions as $key => $permission) { 60 | $auth->addChild($role, $permission); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /models/ProductVariation.php: -------------------------------------------------------------------------------- 1 | 64], 37 | [['product_id', 'variation_id'], 'unique', 'targetAttribute' => ['product_id', 'variation_id']], 38 | [['product_id'], 'exist', 'skipOnError' => true, 'targetClass' => Product::class, 'targetAttribute' => ['product_id' => 'id']], 39 | [['variation_id'], 'exist', 'skipOnError' => true, 'targetClass' => Variation::class, 'targetAttribute' => ['variation_id' => 'id']], 40 | ]; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function attributeLabels() 47 | { 48 | return [ 49 | 'name' => Yii::t('app', 'Name'), 50 | ]; 51 | } 52 | 53 | /** 54 | * Gets query for [[Product]]. 55 | * 56 | * @return \yii\db\ActiveQuery 57 | */ 58 | public function getProduct() 59 | { 60 | return $this->hasOne(Product::class, ['id' => 'product_id']); 61 | } 62 | 63 | /** 64 | * Gets query for [[Variation]]. 65 | * 66 | * @return \yii\db\ActiveQuery 67 | */ 68 | public function getVariation() 69 | { 70 | return $this->hasOne(Variation::class, ['id' => 'variation_id']); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /views/expense/_form.php: -------------------------------------------------------------------------------- 1 | 12 | 13 |
14 | 15 | 16 | 17 |
18 | field($model, 'name')->textInput(['maxlength' => true]) ?> 19 | 20 | field($model, 'description')->textarea(['rows' => 6]) ?> 21 | 22 | field($model, 'value', [ 23 | 'addon' => [ 24 | 'prepend' => [ 25 | 'content' => Yii::$app->formatter->getCurrencySymbol(), 26 | 'options' => ['class' => 'secondary'], 27 | ] 28 | ] 29 | ])->widget(NumberControl::class, [ 30 | 'maskedInputOptions' => [ 31 | 'allowMinus' => false, 32 | 'rightAlign' => false, 33 | ], 34 | 'displayOptions' => [ 35 | 'class' => 'form-control rounded-right' 36 | ] 37 | ]) ?> 38 | 39 | field($model, 'payday')->widget(DatePicker::class, [ 40 | 'type' => DatePicker::TYPE_INPUT, 41 | 'pluginOptions' => [ 42 | 'autoclose' => true, 43 | 'format' => 'dd/mm/yyyy' 44 | ] 45 | ]) ?> 46 | 47 | paid_at !== null) : ?> 48 | field($model, 'paid_at')->widget(DatePicker::class, [ 49 | 'type' => DatePicker::TYPE_INPUT, 50 | 'pluginOptions' => [ 51 | 'autoclose' => true, 52 | 'format' => 'dd/mm/yyyy' 53 | ] 54 | ]) ?> 55 | 56 |
57 | 58 | 61 | 62 | 63 | 64 |
-------------------------------------------------------------------------------- /models/VariationSearch.php: -------------------------------------------------------------------------------- 1 | $query, 49 | 'sort'=> ['defaultOrder' => ['category_id' => SORT_ASC]], 50 | ]); 51 | 52 | $dataProvider->sort->attributes = array_merge($dataProvider->sort->attributes, [ 53 | 'category_id' => [ 54 | 'asc' => ['category.name' => SORT_ASC], 55 | 'desc' => ['category.name' => SORT_DESC], 56 | ], 57 | ]); 58 | 59 | $this->load($params); 60 | 61 | if (!$this->validate()) { 62 | // uncomment the following line if you do not want to return any records when validation fails 63 | // $query->where('0=1'); 64 | return $dataProvider; 65 | } 66 | 67 | // grid filtering conditions 68 | $query->andFilterWhere([ 69 | 'category_id' => $this->category_id, 70 | ]); 71 | 72 | $query->andFilterWhere(['like', 'name', $this->name]); 73 | 74 | return $dataProvider; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /models/PaymentMethodSearch.php: -------------------------------------------------------------------------------- 1 | $query, 50 | ]); 51 | 52 | $this->load($params); 53 | 54 | if (!$this->validate()) { 55 | // uncomment the following line if you do not want to return any records when validation fails 56 | // $query->where('0=1'); 57 | return $dataProvider; 58 | } 59 | 60 | // grid filtering conditions 61 | $query->andFilterWhere([ 62 | 'id' => $this->id, 63 | 'installment_limit' => $this->installment_limit, 64 | 'is_deleted' => $this->is_deleted, 65 | 'created_at' => $this->created_at, 66 | 'updated_at' => $this->updated_at, 67 | 'deleted_at' => $this->deleted_at, 68 | 'company_id' => $this->company_id, 69 | ]); 70 | 71 | $query->andFilterWhere(['like', 'name', $this->name]); 72 | 73 | return $dataProvider; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tests/functional/ContactFormCest.php: -------------------------------------------------------------------------------- 1 | amOnPage(['site/contact']); 8 | } 9 | 10 | public function openContactPage(\FunctionalTester $I) 11 | { 12 | $I->see('Contact', 'h1'); 13 | } 14 | 15 | public function submitEmptyForm(\FunctionalTester $I) 16 | { 17 | $I->submitForm('#contact-form', []); 18 | $I->expectTo('see validations errors'); 19 | $I->see('Contact', 'h1'); 20 | $I->see('Name cannot be blank'); 21 | $I->see('Email cannot be blank'); 22 | $I->see('Subject cannot be blank'); 23 | $I->see('Body cannot be blank'); 24 | $I->see('The verification code is incorrect'); 25 | } 26 | 27 | public function submitFormWithIncorrectEmail(\FunctionalTester $I) 28 | { 29 | $I->submitForm('#contact-form', [ 30 | 'ContactForm[name]' => 'tester', 31 | 'ContactForm[email]' => 'tester.email', 32 | 'ContactForm[subject]' => 'test subject', 33 | 'ContactForm[body]' => 'test content', 34 | 'ContactForm[verifyCode]' => 'testme', 35 | ]); 36 | $I->expectTo('see that email address is wrong'); 37 | $I->dontSee('Name cannot be blank', '.help-inline'); 38 | $I->see('Email is not a valid email address.'); 39 | $I->dontSee('Subject cannot be blank', '.help-inline'); 40 | $I->dontSee('Body cannot be blank', '.help-inline'); 41 | $I->dontSee('The verification code is incorrect', '.help-inline'); 42 | } 43 | 44 | public function submitFormSuccessfully(\FunctionalTester $I) 45 | { 46 | $I->submitForm('#contact-form', [ 47 | 'ContactForm[name]' => 'tester', 48 | 'ContactForm[email]' => 'tester@example.com', 49 | 'ContactForm[subject]' => 'test subject', 50 | 'ContactForm[body]' => 'test content', 51 | 'ContactForm[verifyCode]' => 'testme', 52 | ]); 53 | $I->seeEmailIsSent(); 54 | $I->dontSeeElement('#contact-form'); 55 | $I->see('Thank you for contacting us. We will respond to you as soon as possible.'); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /views/payment-method/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Payment Methods'); 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | $gridColumns = [ 15 | ['class' => 'kartik\grid\SerialColumn'], 16 | 'name', 17 | 'installment_limit', 18 | [ 19 | 'attribute' => 'is_deleted', 20 | 'label' => Yii::t('app', 'Active'), 21 | 'value' => function ($model) { 22 | return !$model->is_deleted; 23 | }, 24 | 'class' => '\kartik\grid\BooleanColumn', 25 | 'falseLabel' => Yii::t('app', 'Yes'), 26 | 'trueLabel' => Yii::t('app', 'No'), 27 | 'width' => '100px', 28 | ], 29 | [ 30 | 'class' => 'kartik\grid\ActionColumn', 31 | 'width' => '100px', 32 | ], 33 | ]; 34 | ?> 35 |
36 |
37 |
38 | $dataProvider, 40 | 'filterModel' => $searchModel, 41 | 'columns' => $gridColumns, 42 | 'responsive' => true, 43 | 'responsiveWrap' => false, 44 | 'hover' => true, 45 | 'toolbar' => [ 46 | [ 47 | 'content' => 48 | Html::a('', Url::to(['create']), [ 49 | 'class' => 'btn btn-success', 50 | 'title' => Yii::t('app', 'Add Payment Method'), 51 | ]), 52 | 'options' => ['class' => 'btn-group mr-2'] 53 | ], 54 | '{toggleData}', 55 | ], 56 | 'panel' => [ 57 | 'type' => GridView::TYPE_DEFAULT, 58 | 'heading' => Html::encode($this->title), 59 | // 'headingOptions' => ['class' => ''], 60 | // 'footer' => false, 61 | 'afterOptions' => ['class' => ''], 62 | ], 63 | ]); ?> 64 |
65 |
66 |
67 | -------------------------------------------------------------------------------- /components/validators/ProductExists.php: -------------------------------------------------------------------------------- 1 | variations_form); 16 | 17 | if ($model->$attribute && $this->modelExists($model, $variations)) { 18 | $this->addError($model, $attribute, Yii::t('app', 'Could not save because the product already exists.')); 19 | } 20 | } 21 | 22 | protected function modelExists($model, $variations) 23 | { 24 | if (!$variations) { 25 | $product = Product::find() 26 | ->leftJoin('product_variation', 'product_variation.product_id = product.id') 27 | ->andWhere(['product.name' => $model->name]) 28 | ->andWhere(['is', 'product_variation.product_id', new Expression('NULL')]); 29 | 30 | if (!$model->isNewRecord) 31 | $product->andWhere(['not', ['product.id' => $model->id]]); 32 | 33 | return $product->exists(); 34 | } 35 | 36 | $query = Product::find() 37 | ->andWhere(['product.name' => $model->name]) 38 | ->andWhere(['product.category_id' => $model->category_id]) 39 | ->andWhere(['in', 'product_variation.name', array_values($variations)]) 40 | ->andWhere(['in', 'product_variation.variation_id', array_keys($variations)]) 41 | ->innerJoinWith('productVariations'); 42 | 43 | if (!$model->isNewRecord) 44 | $query->andWhere(['not', ['product.id' => $model->id]]); 45 | 46 | $query = $query->asArray()->all(); 47 | 48 | $product_variations = array_map(function ($product) { 49 | $array = ArrayHelper::map($product['productVariations'], 'variation_id', 'name'); 50 | ksort($array); 51 | return $array; 52 | }, $query); 53 | 54 | ksort($variations); 55 | foreach ($product_variations as $vars) { 56 | $array = array_merge(array_diff_assoc($variations, $vars), array_diff_assoc($vars, $variations)); 57 | if (!$array) return true; 58 | } 59 | 60 | return false; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /web/css/site.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | height: 100%; 4 | } 5 | 6 | .wrap { 7 | min-height: 100%; 8 | height: auto; 9 | margin: 0 auto -60px; 10 | padding: 0 0 60px; 11 | } 12 | 13 | .wrap > .container { 14 | padding: 70px 15px 20px; 15 | } 16 | 17 | .footer { 18 | height: 60px; 19 | background-color: #f5f5f5; 20 | border-top: 1px solid #ddd; 21 | padding-top: 20px; 22 | } 23 | 24 | .jumbotron { 25 | text-align: center; 26 | background-color: transparent; 27 | } 28 | 29 | .jumbotron .btn { 30 | font-size: 21px; 31 | padding: 14px 24px; 32 | } 33 | 34 | .not-set { 35 | color: #c55; 36 | font-style: italic; 37 | } 38 | 39 | /* add sorting icons to gridview sort links */ 40 | a.asc:after, a.desc:after { 41 | position: relative; 42 | top: 1px; 43 | display: inline-block; 44 | font-family: 'Glyphicons Halflings'; 45 | font-style: normal; 46 | font-weight: normal; 47 | line-height: 1; 48 | padding-left: 5px; 49 | } 50 | 51 | a.asc:after { 52 | content: /*"\e113"*/ "\e151"; 53 | } 54 | 55 | a.desc:after { 56 | content: /*"\e114"*/ "\e152"; 57 | } 58 | 59 | .sort-numerical a.asc:after { 60 | content: "\e153"; 61 | } 62 | 63 | .sort-numerical a.desc:after { 64 | content: "\e154"; 65 | } 66 | 67 | .sort-ordinal a.asc:after { 68 | content: "\e155"; 69 | } 70 | 71 | .sort-ordinal a.desc:after { 72 | content: "\e156"; 73 | } 74 | 75 | .grid-view th { 76 | white-space: nowrap; 77 | } 78 | 79 | .hint-block { 80 | display: block; 81 | margin-top: 5px; 82 | color: #999; 83 | } 84 | 85 | .error-summary { 86 | color: #a94442; 87 | background: #fdf7f7; 88 | border-left: 3px solid #eed3d7; 89 | padding: 10px 20px; 90 | margin: 0 0 15px 0; 91 | } 92 | 93 | /* align the logout "link" (button in form) of the navbar */ 94 | .nav li > form > button.logout { 95 | padding: 15px; 96 | border: none; 97 | } 98 | 99 | @media(max-width:767px) { 100 | .nav li > form > button.logout { 101 | display:block; 102 | text-align: left; 103 | width: 100%; 104 | padding: 10px 15px; 105 | } 106 | } 107 | 108 | .nav > li > form > button.logout:focus, 109 | .nav > li > form > button.logout:hover { 110 | text-decoration: none; 111 | } 112 | 113 | .nav > li > form > button.logout:focus { 114 | outline: none; 115 | } 116 | -------------------------------------------------------------------------------- /models/SignupForm.php: -------------------------------------------------------------------------------- 1 | 255], 32 | [['full_name'], 'string', 'max' => 128], 33 | [['name', 'trade_name', 'email'], 'string', 'max' => 64], 34 | [['usual_name'], 'string', 'max' => 32], 35 | [['ein'], 'string', 'max' => 18], 36 | [['ssn'], 'string', 'max' => 12], 37 | [['email'], 'email'], 38 | [['birthday'], 'safe'], 39 | [['password_repeat'], 'compare', 'compareAttribute' => 'password'], 40 | ]; 41 | } 42 | 43 | public function scenarios() 44 | { 45 | $scenarios = parent::scenarios(); 46 | $scenarios[self::SCENARIO_COMPANY] = ['name', 'ein', 'email', 'trade_name']; 47 | $scenarios[self::SCENARIO_EMPLOYEE] = ['full_name', 'usual_name', 'ssn', 'birthday', 'email', 'password', 'password_repeat']; 48 | return $scenarios; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function attributeLabels() 55 | { 56 | return [ 57 | // Company 58 | 'id' => Yii::t('app', 'ID'), 59 | 'name' => Yii::t('app', 'Name'), 60 | 'trade_name' => Yii::t('app', 'Trade Name'), 61 | 'ein' => Yii::t('app', 'Ein'), 62 | 63 | // Employee 64 | 'full_name' => Yii::t('app', 'Full Name'), 65 | 'usual_name' => Yii::t('app', 'Usual Name'), 66 | 'ssn' => Yii::t('app', 'Ssn'), 67 | 'birthday' => Yii::t('app', 'Birthday'), 68 | 'password' => Yii::t('app', 'Password'), 69 | 'password_repeat' => Yii::t('app', 'Password Repeat'), 70 | 71 | 'email' => Yii::t('app', 'Email'), 72 | ]; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /vagrant/provision/once-as-root.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #== Import script args == 4 | 5 | timezone=$(echo "$1") 6 | 7 | #== Bash helpers == 8 | 9 | function info { 10 | echo " " 11 | echo "--> $1" 12 | echo " " 13 | } 14 | 15 | #== Provision script == 16 | 17 | info "Provision-script user: `whoami`" 18 | 19 | export DEBIAN_FRONTEND=noninteractive 20 | 21 | info "Configure timezone" 22 | timedatectl set-timezone ${timezone} --no-ask-password 23 | 24 | info "Prepare root password for MySQL" 25 | debconf-set-selections <<< "mariadb-server-10.0 mysql-server/root_password password \"''\"" 26 | debconf-set-selections <<< "mariadb-server-10.0 mysql-server/root_password_again password \"''\"" 27 | echo "Done!" 28 | 29 | info "Update OS software" 30 | apt-get update 31 | apt-get upgrade -y 32 | 33 | info "Install additional software" 34 | apt-get install -y php7.0-curl php7.0-cli php7.0-intl php7.0-mysqlnd php7.0-gd php7.0-fpm php7.0-mbstring php7.0-xml unzip nginx mariadb-server-10.0 php.xdebug 35 | 36 | info "Configure MySQL" 37 | sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mariadb.conf.d/50-server.cnf 38 | mysql -uroot <<< "CREATE USER 'root'@'%' IDENTIFIED BY ''" 39 | mysql -uroot <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%'" 40 | mysql -uroot <<< "DROP USER 'root'@'localhost'" 41 | mysql -uroot <<< "FLUSH PRIVILEGES" 42 | echo "Done!" 43 | 44 | info "Configure PHP-FPM" 45 | sed -i 's/user = www-data/user = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 46 | sed -i 's/group = www-data/group = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 47 | sed -i 's/owner = www-data/owner = vagrant/g' /etc/php/7.0/fpm/pool.d/www.conf 48 | cat << EOF > /etc/php/7.0/mods-available/xdebug.ini 49 | zend_extension=xdebug.so 50 | xdebug.remote_enable=1 51 | xdebug.remote_connect_back=1 52 | xdebug.remote_port=9000 53 | xdebug.remote_autostart=1 54 | EOF 55 | echo "Done!" 56 | 57 | info "Configure NGINX" 58 | sed -i 's/user www-data/user vagrant/g' /etc/nginx/nginx.conf 59 | echo "Done!" 60 | 61 | info "Enabling site configuration" 62 | ln -s /app/vagrant/nginx/app.conf /etc/nginx/sites-enabled/app.conf 63 | echo "Done!" 64 | 65 | info "Removing default site configuration" 66 | rm /etc/nginx/sites-enabled/default 67 | echo "Done!" 68 | 69 | info "Initailize databases for MySQL" 70 | mysql -uroot <<< "CREATE DATABASE yii2basic" 71 | mysql -uroot <<< "CREATE DATABASE yii2basic_test" 72 | echo "Done!" 73 | 74 | info "Install composer" 75 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer -------------------------------------------------------------------------------- /models/LoginForm.php: -------------------------------------------------------------------------------- 1 | Yii::t('app', 'Email'), 43 | 'password' => Yii::t('app', 'Password'), 44 | 'rememberMe' => Yii::t('app', 'Remember Me'), 45 | ]; 46 | } 47 | 48 | /** 49 | * Validates the password. 50 | * This method serves as the inline validation for password. 51 | * 52 | * @param string $attribute the attribute currently being validated 53 | * @param array $params the additional name-value pairs given in the rule 54 | */ 55 | public function validatePassword($attribute, $params) 56 | { 57 | if (!$this->hasErrors()) { 58 | $user = $this->getUser(); 59 | 60 | if (!$user || !$user->validatePassword($this->password)) { 61 | $this->addError($attribute, Yii::t('app', 'Incorrect email or password.')); 62 | } 63 | } 64 | } 65 | 66 | /** 67 | * Logs in a user using the provided email and password. 68 | * @return bool whether the user is logged in successfully 69 | */ 70 | public function login() 71 | { 72 | if ($this->validate()) { 73 | return Yii::$app->user->login($this->getUser(), $this->rememberMe ? 3600*24*30 : 0); 74 | } 75 | return false; 76 | } 77 | 78 | /** 79 | * Finds user by [[email]] 80 | * 81 | * @return Employee|null 82 | */ 83 | public function getUser() 84 | { 85 | if ($this->_user === false && ($user = Employee::findByEmail($this->email))) { 86 | $this->_user = $user->is_deleted ? null : $user; 87 | } 88 | 89 | return $this->_user; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /web/js/pos/main.js: -------------------------------------------------------------------------------- 1 | const app = new Vue({ 2 | el: '.pos-index', 3 | data: { 4 | items: [], 5 | total: 0, 6 | orderId: '', 7 | }, 8 | mounted() { 9 | this.orderId = document.querySelector('#orderitem-order_id').value; 10 | this.load(); 11 | }, 12 | methods: { 13 | pushItem() { 14 | $('form').yiiActiveForm('validate', true); 15 | 16 | const form = document.querySelector('form'); 17 | const formData = new FormData(form); 18 | 19 | axios.post('/api/order-item/create', formData) 20 | .then(({ data }) => { 21 | this.items.push(data.orderItem); 22 | this.total = data.total; 23 | const $form = $('form'); 24 | $form.get(0).reset(); 25 | }) 26 | .catch(({ response }) => { 27 | const { name, message } = response.data; 28 | showToast(name, message); 29 | }); 30 | }, 31 | popItem(index) { 32 | const id = this.items[index].id; 33 | axios.delete('/api/order-item/delete', { params: { id } }) 34 | .then(({ data }) => { 35 | this.total = data.total; 36 | this.items.splice(index, 1); 37 | }) 38 | .catch(({ response }) => { 39 | const { name, message } = response.data; 40 | showToast(name, message); 41 | }); 42 | }, 43 | load() { 44 | axios.get('/api/order/index', { params: { id: this.orderId } }) 45 | .then(({ data }) => { 46 | this.items = data.orderItems; 47 | this.total = data.total_value; 48 | }) 49 | .catch(({ response }) => { 50 | const { name, message } = response.data; 51 | showToast(name, message); 52 | }); 53 | }, 54 | } 55 | }); 56 | 57 | function showToast(title, message = '') { 58 | $(document).Toasts('create', { 59 | title: title, 60 | body: message, 61 | autohide: true, 62 | delay: 5000, 63 | class: ['bg-warning', 'fix-toast'] 64 | }); 65 | } 66 | 67 | function setPrice(value) { 68 | $('#orderitem-default_price-disp').inputmask("setvalue", value); 69 | $('#orderitem-unit_price-disp').inputmask("setvalue", value); 70 | $('#orderitem-unit_price-disp').focus(); 71 | } 72 | 73 | $('form').on('submit', e => false); -------------------------------------------------------------------------------- /models/Address.php: -------------------------------------------------------------------------------- 1 | 8], 40 | [['number'], 'string', 'max' => 8], 41 | [['street', 'neighborhood', 'city'], 'string', 'max' => 64], 42 | [['federated_unit'], 'string', 'max' => 2], 43 | [['zip_code'], 'match', 'pattern' => '/\d{8}/'], 44 | [['complement'], 'string', 'max' => 128], 45 | ]; 46 | } 47 | 48 | /** 49 | * {@inheritdoc} 50 | */ 51 | public function attributeLabels() 52 | { 53 | return [ 54 | 'id' => Yii::t('app', 'ID'), 55 | 'zip_code' => Yii::t('app', 'Zip Code'), 56 | 'number' => Yii::t('app', 'Number'), 57 | 'street' => Yii::t('app', 'Street'), 58 | 'neighborhood' => Yii::t('app', 'Neighborhood'), 59 | 'city' => Yii::t('app', 'City'), 60 | 'federated_unit' => Yii::t('app', 'Federated Unit'), 61 | 'complement' => Yii::t('app', 'Complement'), 62 | ]; 63 | } 64 | 65 | /** 66 | * Gets query for [[Companies]]. 67 | * 68 | * @return \yii\db\ActiveQuery 69 | */ 70 | public function getCompany() 71 | { 72 | return $this->hasOne(Company::class, ['address_id' => 'id']); 73 | } 74 | 75 | /** 76 | * Gets query for [[Employees]]. 77 | * 78 | * @return \yii\db\ActiveQuery 79 | */ 80 | public function getEmployee() 81 | { 82 | return $this->hasOne(Employee::class, ['address_id' => 'id']); 83 | } 84 | 85 | public function __toString() 86 | { 87 | return "$this->street, $this->number, $this->neighborhood, $this->city - $this->federated_unit, $this->zip_code ($this->complement)"; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /widgets/Alert.php: -------------------------------------------------------------------------------- 1 | session->setFlash('error', 'This is the message'); 14 | * Yii::$app->session->setFlash('success', 'This is the message'); 15 | * Yii::$app->session->setFlash('info', 'This is the message'); 16 | * ``` 17 | * 18 | * Multiple messages could be set as follows: 19 | * 20 | * ```php 21 | * Yii::$app->session->setFlash('error', ['Error 1', 'Error 2']); 22 | * ``` 23 | * 24 | * @author Kartik Visweswaran 25 | * @author Alexander Makarov 26 | */ 27 | class Alert extends Widget 28 | { 29 | /** 30 | * @var array the alert types configuration for the flash messages. 31 | * This array is setup as $key => $value, where: 32 | * - key: the name of the session flash variable 33 | * - value: the bootstrap alert type (i.e. danger, success, info, warning) 34 | */ 35 | public $alertTypes = [ 36 | 'error' => 'alert-danger', 37 | 'danger' => 'alert-danger', 38 | 'success' => 'alert-success', 39 | 'info' => 'alert-info', 40 | 'warning' => 'alert-warning' 41 | ]; 42 | /** 43 | * @var array the options for rendering the close button tag. 44 | * Array will be passed to [[\yii\bootstrap\Alert::closeButton]]. 45 | */ 46 | public $closeButton = []; 47 | 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function run() 53 | { 54 | $session = Yii::$app->session; 55 | $flashes = $session->getAllFlashes(); 56 | $appendClass = isset($this->options['class']) ? ' ' . $this->options['class'] : ''; 57 | 58 | foreach ($flashes as $type => $flash) { 59 | if (!isset($this->alertTypes[$type])) { 60 | continue; 61 | } 62 | 63 | foreach ((array) $flash as $i => $message) { 64 | echo Bootstrap4Alert::widget([ 65 | 'body' => $message, 66 | 'closeButton' => $this->closeButton, 67 | 'options' => array_merge($this->options, [ 68 | 'id' => $this->getId() . '-' . $type . '-' . $i, 69 | 'class' => $this->alertTypes[$type] . $appendClass, 70 | ]), 71 | ]); 72 | } 73 | 74 | $session->removeFlash($type); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /views/employee/change_password.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Change Password: {name}', [ 11 | 'name' => $model->full_name, 12 | ]); 13 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Employees'), 'url' => ['index']]; 14 | $this->params['breadcrumbs'][] = ['label' => $model->usual_name, 'url' => ['view', 'id' => $model->id]]; 15 | $this->params['breadcrumbs'][] = Yii::t('app', 'Change Password'); 16 | 17 | $this->registerJs( 18 | <<< 'JS' 19 | function viewPassword() { 20 | const inputs = $('#employee-password, #employee-password_repeat, #employee-password_new'); 21 | for (var input of inputs) { 22 | const type = $(input).prop('type'); 23 | if (type === "password") 24 | $(input).prop('type', 'text') 25 | else 26 | $(input).prop('type', 'password') 27 | }; 28 | } 29 | JS, 30 | $this::POS_END 31 | ) 32 | ?> 33 | 34 |
35 |
36 |
37 |
38 |

title) ?>

39 |
40 |
41 | false, 43 | 'enableAjaxValidation' => true, 44 | 'validationUrl' => Url::to(['validate-password', 'id' => $model->id], ) 45 | ]); ?> 46 |
47 | field($model, 'password')->passwordInput(['maxlength' => true]) ?> 48 | field($model, 'password_new')->passwordInput(['maxlength' => true]) ?> 49 | field($model, 'password_repeat')->passwordInput(['maxlength' => true]) ?> 50 |
51 | 52 | 53 |
54 |
55 | 58 | 59 |
60 |
61 |
62 |
-------------------------------------------------------------------------------- /views/category/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Categories'); 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | $this->registerJsFile('@web/js/modal.js', ['depends' => [yii\web\JqueryAsset::class]]); 15 | 16 | $gridColumns = [ 17 | ['class' => 'kartik\grid\SerialColumn'], 18 | 'name', 19 | [ 20 | 'class' => 'kartik\grid\ActionColumn', 21 | 'deleteOptions' => [ 22 | 'data-confirm' => Yii::t('app', 'Are you sure you want to delete this category?'), 23 | ], 24 | 'buttons' => [ 25 | 'update' => function($url, $model) { 26 | return Html::a( 27 | '', 28 | $url, 29 | ['value' => $url, 'title' => Yii::t('kvgrid', 'Update'), 'class' => 'btn-modal', 'data-toggle' => 'modal'] 30 | ); 31 | } 32 | ], 33 | 'width' => '100px', 34 | ], 35 | ]; 36 | ?> 37 |
38 |
39 |
40 | render('@app/views/layouts/modal.php', ['options' => ['title' => Yii::t('app', 'Category')]]) ?> 41 | 42 | 'grid_categories', 44 | 'dataProvider' => $dataProvider, 45 | 'filterModel' => $searchModel, 46 | 'columns' => $gridColumns, 47 | 'responsive' => true, 48 | 'responsiveWrap' => false, 49 | 'hover' => true, 50 | 'toolbar' => [ 51 | [ 52 | 'content' => 53 | Html::button('', [ 54 | 'value' => Url::to(['create']), 55 | 'class' => 'btn btn-success btn-modal', 56 | 'title' => Yii::t('app', 'Add Category'), 57 | ]), 58 | 'options' => ['class' => 'btn-group mr-2'] 59 | ], 60 | '{toggleData}', 61 | ], 62 | 'panel' => [ 63 | 'type' => GridView::TYPE_DEFAULT, 64 | 'heading' => Html::encode($this->title), 65 | 'afterOptions' => ['class' => ''], 66 | ], 67 | ]); ?> 68 |
69 |
70 |
71 | -------------------------------------------------------------------------------- /models/EmployeeSearch.php: -------------------------------------------------------------------------------- 1 | $query, 50 | ]); 51 | 52 | $this->load($params); 53 | 54 | if (!$this->validate()) { 55 | // uncomment the following line if you do not want to return any records when validation fails 56 | // $query->where('0=1'); 57 | return $dataProvider; 58 | } 59 | 60 | // grid filtering conditions 61 | $query->andFilterWhere([ 62 | 'employee.birthday' => $this->getBirthDay(), 63 | 'employee.is_deleted' => $this->is_deleted, 64 | ]); 65 | 66 | $query->andFilterWhere(['like', 'employee.full_name', $this->full_name]) 67 | ->andFilterWhere(['like', 'employee.ssn', $this->ssn]) 68 | ->andFilterWhere(['like', 'employee.email', $this->email]) 69 | ->andFilterWhere(['like', 'employee.phone_number', $this->getPhoneNumber()]); 70 | 71 | return $dataProvider; 72 | } 73 | 74 | public function getBirthDay() 75 | { 76 | return $this->birthday && preg_match("/\d{2}\/\d{2}\/\d{4}/", $this->birthday) 77 | ? Yii::$app->formatter->asDateDefault($this->birthday) 78 | : $this->birthday = null; 79 | } 80 | 81 | public function getPhoneNumber() 82 | { 83 | return $this->phone_number && (preg_match("/\(\d{2}\)\ \d{4}\-\d{4}/", $this->phone_number) 84 | || preg_match("/\(\d{2}\)\ \d{5}\-\d{4}/", $this->phone_number)) ? $this->phone_number 85 | : $this->phone_number = null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /models/ExpenseSearch.php: -------------------------------------------------------------------------------- 1 | $query, 52 | 'sort'=> ['defaultOrder' => ['payday' => SORT_DESC]], 53 | ]); 54 | 55 | $this->load($params); 56 | 57 | if (!$this->validate()) { 58 | // uncomment the following line if you do not want to return any records when validation fails 59 | // $query->where('0=1'); 60 | return $dataProvider; 61 | } 62 | 63 | // grid filtering conditions 64 | $query->andFilterWhere([ 65 | 'id' => $this->id, 66 | 'value' => $this->value, 67 | 'payday' => $this->getPayday(), 68 | 'is_paid' => $this->is_paid, 69 | 'paid_at' => $this->getPaidAt(), 70 | 'created_at' => $this->created_at, 71 | 'updated_at' => $this->updated_at, 72 | 'company_id' => $this->company_id, 73 | ]); 74 | 75 | $query->andFilterWhere(['like', 'expense.name', $this->name]) 76 | ->andFilterWhere(['like', 'description', $this->description]); 77 | 78 | return $dataProvider; 79 | } 80 | 81 | public function getPaidAt() 82 | { 83 | return $this->paid_at && preg_match("/\d{2}\/\d{2}\/\d{4}/", $this->paid_at) 84 | ? Yii::$app->formatter->asDateDefault($this->paid_at) 85 | : $this->paid_at = null; 86 | } 87 | 88 | public function getPayday() 89 | { 90 | return $this->payday && preg_match("/\d{2}\/\d{2}\/\d{4}/", $this->payday) 91 | ? Yii::$app->formatter->asDateDefault($this->payday) 92 | : $this->payday = null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /migrations/m200721_145625_create_junction_table_for_product_and_variation_tables.php: -------------------------------------------------------------------------------- 1 | createTable('{{%product_variation}}', [ 20 | 'product_id' => $this->integer()->unsigned(), 21 | 'name' => $this->string(64)->notNull(), 22 | 'variation_id' => $this->integer()->unsigned(), 23 | 'PRIMARY KEY(product_id, variation_id)', 24 | ]); 25 | 26 | // creates index for column `product_id` 27 | $this->createIndex( 28 | '{{%idx-product_variation-product_id}}', 29 | '{{%product_variation}}', 30 | 'product_id' 31 | ); 32 | 33 | // add foreign key for table `{{%product}}` 34 | $this->addForeignKey( 35 | '{{%fk-product_variation-product_id}}', 36 | '{{%product_variation}}', 37 | 'product_id', 38 | '{{%product}}', 39 | 'id', 40 | 'CASCADE' 41 | ); 42 | 43 | // creates index for column `variation_id` 44 | $this->createIndex( 45 | '{{%idx-product_variation-variation_id}}', 46 | '{{%product_variation}}', 47 | 'variation_id' 48 | ); 49 | 50 | // add foreign key for table `{{%variation}}` 51 | $this->addForeignKey( 52 | '{{%fk-product_variation-variation_id}}', 53 | '{{%product_variation}}', 54 | 'variation_id', 55 | '{{%variation}}', 56 | 'id', 57 | 'CASCADE' 58 | ); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | public function safeDown() 65 | { 66 | // drops foreign key for table `{{%product}}` 67 | $this->dropForeignKey( 68 | '{{%fk-product_variation-product_id}}', 69 | '{{%product_variation}}' 70 | ); 71 | 72 | // drops index for column `product_id` 73 | $this->dropIndex( 74 | '{{%idx-product_variation-product_id}}', 75 | '{{%product_variation}}' 76 | ); 77 | 78 | // drops foreign key for table `{{%variation}}` 79 | $this->dropForeignKey( 80 | '{{%fk-product_variation-variation_id}}', 81 | '{{%product_variation}}' 82 | ); 83 | 84 | // drops index for column `variation_id` 85 | $this->dropIndex( 86 | '{{%idx-product_variation-variation_id}}', 87 | '{{%product_variation}}' 88 | ); 89 | 90 | $this->dropTable('{{%product_variation}}'); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /views/employee/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Employees'); 13 | $this->params['breadcrumbs'][] = $this->title; 14 | 15 | $gridColumns = [ 16 | ['class' => 'kartik\grid\SerialColumn'], 17 | 'full_name', 18 | [ 19 | 'attribute' => 'ssn', 20 | 'filterType' => MaskedInput::class, 21 | 'filterWidgetOptions' => [ 22 | 'mask' => ['999.999.999-99'], 23 | ], 24 | ], 25 | [ 26 | 'attribute' => 'birthday', 27 | 'filterType' => MaskedInput::class, 28 | 'filterWidgetOptions' => [ 29 | 'clientOptions' => ['alias' => 'dd/mm/yyyy'] 30 | ], 31 | 'format' => 'date' 32 | ], 33 | 'email', 34 | [ 35 | 'attribute' => 'phone_number', 36 | 'filterType' => MaskedInput::class, 37 | 'filterWidgetOptions' => [ 38 | 'mask' => ['(99) 9999-9999', '(99) 99999-9999'], 39 | ] 40 | ], 41 | [ 42 | 'attribute' => 'is_deleted', 43 | 'label' => Yii::t('app', 'Active'), 44 | 'class' => '\kartik\grid\BooleanColumn', 45 | 'trueLabel' => Yii::t('app', 'No'), 46 | 'falseLabel' => Yii::t('app', 'Yes'), 47 | 'value' => function ($model) { 48 | return !$model->is_deleted; 49 | } 50 | ], 51 | [ 52 | 'class' => 'kartik\grid\ActionColumn', 53 | 'template' => '{view} {update}', 54 | 'visibleButtons' => [ 55 | 'delete' => function ($model) { 56 | return $model->id != Yii::$app->user->id; 57 | }, 58 | ], 59 | 'width' => '100px', 60 | ], 61 | ]; 62 | 63 | ?> 64 |
65 |
66 | $dataProvider, 68 | 'filterModel' => $searchModel, 69 | 'columns' => $gridColumns, 70 | 'responsive' => true, 71 | 'responsiveWrap' => false, 72 | 'hover' => true, 73 | 'toolbar' => [ 74 | [ 75 | 'content' => 76 | Html::a('', Url::to(['create']), [ 77 | 'class' => 'btn btn-success', 78 | 'title' => Yii::t('app', 'Add Employee'), 79 | ]), 80 | 'options' => ['class' => 'btn-group mr-2'] 81 | ], 82 | '{toggleData}', 83 | ], 84 | 'panel' => [ 85 | 'type' => GridView::TYPE_DEFAULT, 86 | 'heading' => Html::encode($this->title), 87 | 'afterOptions' => ['class' => ''], 88 | ], 89 | ]); ?> 90 |
91 |

92 |

-------------------------------------------------------------------------------- /views/variation/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Variation'); 14 | $this->params['breadcrumbs'][] = $this->title; 15 | 16 | $this->registerJsFile('@web/js/modal.js', ['depends' => [yii\web\JqueryAsset::class]]); 17 | 18 | $gridColumns = [ 19 | ['class' => 'kartik\grid\SerialColumn'], 20 | 'name', 21 | [ 22 | 'attribute' => 'category_id', 23 | 'value' => function ($model) { 24 | return $model->category->name; 25 | }, 26 | 'filterType' => GridView::FILTER_SELECT2, 27 | 'filter' => ArrayHelper::map(Category::find()->orderBy('name')->asArray()->all(), 'id', 'name'), 28 | 'filterWidgetOptions' => [ 29 | 'pluginOptions' => ['allowClear' => true], 30 | ], 31 | 'filterInputOptions' => ['placeholder' => Yii::t('app', 'Select')], 32 | 'group' => true, // enable grouping 33 | ], 34 | [ 35 | 'class' => 'kartik\grid\ActionColumn', 36 | 'width' => '100px', 37 | 'buttons' => [ 38 | 'update' => function ($url, $model) { 39 | return Html::a( 40 | '', 41 | $url, 42 | ['value' => $url, 'title' => Yii::t('kvgrid', 'Update'), 'class' => 'btn-modal', 'data-toggle' => 'modal'] 43 | ); 44 | } 45 | ] 46 | ], 47 | ]; 48 | ?> 49 |
50 |
51 | render('@app/views/layouts/modal.php', ['options' => ['title' => Yii::t('app', 'Variation')]]) ?> 52 | 53 | 'grid_categories', 55 | 'dataProvider' => $dataProvider, 56 | 'filterModel' => $searchModel, 57 | 'columns' => $gridColumns, 58 | 'responsive' => true, 59 | 'responsiveWrap' => false, 60 | 'hover' => true, 61 | 'toolbar' => [ 62 | [ 63 | 'content' => 64 | Html::button('', [ 65 | 'value' => Url::to(['create']), 66 | 'class' => 'btn btn-success btn-modal', 67 | 'title' => Yii::t('app', 'Add Variation'), 68 | ]), 69 | 'options' => ['class' => 'btn-group mr-2'] 70 | ], 71 | '{toggleData}', 72 | ], 73 | 'panel' => [ 74 | 'type' => GridView::TYPE_DEFAULT, 75 | 'heading' => Html::encode($this->title), 76 | 'afterOptions' => ['class' => ''], 77 | ], 78 | ]); ?> 79 |
80 |
-------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | require 'fileutils' 3 | 4 | required_plugins = %w( vagrant-hostmanager vagrant-vbguest ) 5 | required_plugins.each do |plugin| 6 | exec "vagrant plugin install #{plugin}" unless Vagrant.has_plugin? plugin 7 | end 8 | 9 | domains = { 10 | app: 'yii2basic.test' 11 | } 12 | 13 | vagrantfile_dir_path = File.dirname(__FILE__) 14 | 15 | config = { 16 | local: vagrantfile_dir_path + '/vagrant/config/vagrant-local.yml', 17 | example: vagrantfile_dir_path + '/vagrant/config/vagrant-local.example.yml' 18 | } 19 | 20 | # copy config from example if local config not exists 21 | FileUtils.cp config[:example], config[:local] unless File.exist?(config[:local]) 22 | # read config 23 | options = YAML.load_file config[:local] 24 | 25 | # check github token 26 | if options['github_token'].nil? || options['github_token'].to_s.length != 40 27 | puts "You must place REAL GitHub token into configuration:\n/yii2-app-basic/vagrant/config/vagrant-local.yml" 28 | exit 29 | end 30 | 31 | # vagrant configurate 32 | Vagrant.configure(2) do |config| 33 | # select the box 34 | config.vm.box = 'bento/ubuntu-16.04' 35 | 36 | # should we ask about box updates? 37 | config.vm.box_check_update = options['box_check_update'] 38 | 39 | config.vm.provider 'virtualbox' do |vb| 40 | # machine cpus count 41 | vb.cpus = options['cpus'] 42 | # machine memory size 43 | vb.memory = options['memory'] 44 | # machine name (for VirtualBox UI) 45 | vb.name = options['machine_name'] 46 | end 47 | 48 | # machine name (for vagrant console) 49 | config.vm.define options['machine_name'] 50 | 51 | # machine name (for guest machine console) 52 | config.vm.hostname = options['machine_name'] 53 | 54 | # network settings 55 | config.vm.network 'private_network', ip: options['ip'] 56 | 57 | # sync: folder 'yii2-app-advanced' (host machine) -> folder '/app' (guest machine) 58 | config.vm.synced_folder './', '/app', owner: 'vagrant', group: 'vagrant' 59 | 60 | # disable folder '/vagrant' (guest machine) 61 | config.vm.synced_folder '.', '/vagrant', disabled: true 62 | 63 | # hosts settings (host machine) 64 | config.vm.provision :hostmanager 65 | config.hostmanager.enabled = true 66 | config.hostmanager.manage_host = true 67 | config.hostmanager.ignore_private_ip = false 68 | config.hostmanager.include_offline = true 69 | config.hostmanager.aliases = domains.values 70 | 71 | # quick fix for failed guest additions installations 72 | # config.vbguest.auto_update = false 73 | 74 | # provisioners 75 | config.vm.provision 'shell', path: './vagrant/provision/once-as-root.sh', args: [options['timezone']] 76 | config.vm.provision 'shell', path: './vagrant/provision/once-as-vagrant.sh', args: [options['github_token']], privileged: false 77 | config.vm.provision 'shell', path: './vagrant/provision/always-as-root.sh', run: 'always' 78 | 79 | # post-install message (vagrant console) 80 | config.vm.post_up_message = "App URL: http://#{domains[:app]}" 81 | end 82 | -------------------------------------------------------------------------------- /web/js/checkout/main.js: -------------------------------------------------------------------------------- 1 | const app = new Vue({ 2 | el: '.checkout-index', 3 | data: { 4 | items: [], 5 | totalPaid: 0, 6 | saleId: '', 7 | products: [], 8 | totalOrder: 0 9 | }, 10 | mounted() { 11 | this.saleId = document.querySelector('#pay-sale_id').value; 12 | this.load(); 13 | }, 14 | methods: { 15 | pushPay() { 16 | $('form').yiiActiveForm('validate', true); 17 | 18 | const form = document.querySelector('form'); 19 | const formData = new FormData(form); 20 | 21 | axios.post('/api/pay/create', formData) 22 | .then(({ data }) => { 23 | this.items.push(data.pay); 24 | this.totalPaid = data.total; 25 | const $form = $('form'); 26 | $form.get(0).reset(); 27 | }) 28 | .catch(({ response }) => { 29 | const { name, message } = response.data; 30 | showToast(name, message); 31 | }); 32 | }, 33 | popItem(index) { 34 | const id = this.items[index].id; 35 | axios.delete('/api/pay/delete', { params: { id } }) 36 | .then(({ data }) => { 37 | this.totalPaid = data.total; 38 | this.items.splice(index, 1); 39 | }) 40 | .catch(({ response }) => { 41 | const { name, message } = response.data; 42 | showToast(name, message); 43 | }); 44 | }, 45 | load() { 46 | axios.get('/api/sale/', { params: { id: this.saleId } }) 47 | .then(({ data }) => { 48 | this.items = data.pays; 49 | this.totalPaid = data.total; 50 | this.products = data.order.orderItems; 51 | this.totalOrder = data.order.total_value; 52 | }) 53 | .catch(({ response }) => { 54 | const { name, message } = response.data; 55 | showToast(name, message); 56 | }); 57 | }, 58 | } 59 | }); 60 | 61 | Vue.component('payment_items', { 62 | props: ['id', 'index', 'name', 'installments', 'value'], 63 | template: '\ 64 | \ 65 | {{ index+1 }}\ 66 | {{ name }}\ 67 | {{ installments }}\ 68 | {{ value }}\ 69 | \ 70 | \ 71 | \ 72 | \ 73 | \ 74 | \ 75 | ', 76 | }) 77 | 78 | function showToast(title, message = '') { 79 | $(document).Toasts('create', { 80 | title: title, 81 | body: message, 82 | autohide: true, 83 | delay: 5000, 84 | class: ['bg-warning', 'fix-toast'] 85 | }); 86 | } 87 | 88 | $('form').on('submit', e => false); -------------------------------------------------------------------------------- /models/Category.php: -------------------------------------------------------------------------------- 1 | 'company', 31 | 'on' => 'category.company_id = company.id' 32 | ] 33 | ]; 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public static function tableName() 38 | { 39 | return 'category'; 40 | } 41 | 42 | public function behaviors() 43 | { 44 | return [ 45 | [ 46 | 'class' => TimestampBehavior::class, 47 | 'value' => new Expression('NOW()'), 48 | ], 49 | ]; 50 | } 51 | 52 | /** 53 | * {@inheritdoc} 54 | */ 55 | public function rules() 56 | { 57 | return [ 58 | [['name', 'company_id'], 'required'], 59 | [['created_at', 'updated_at'], 'safe'], 60 | [['company_id'], 'integer'], 61 | [['name'], 'unique'], 62 | [['name'], 'string', 'max' => 64], 63 | [['company_id'], 'exist', 'skipOnError' => true, 'targetClass' => Company::class, 'targetAttribute' => ['company_id' => 'id']], 64 | ]; 65 | } 66 | 67 | /** 68 | * {@inheritdoc} 69 | */ 70 | public function attributeLabels() 71 | { 72 | return [ 73 | 'id' => Yii::t('app', 'ID'), 74 | 'name' => Yii::t('app', 'Name'), 75 | 'created_at' => Yii::t('app', 'Created At'), 76 | 'updated_at' => Yii::t('app', 'Updated At'), 77 | 'company_id' => Yii::t('app', 'Company'), 78 | ]; 79 | } 80 | 81 | /** 82 | * Gets query for [[Company]]. 83 | * 84 | * @return \yii\db\ActiveQuery 85 | */ 86 | public function getCompany() 87 | { 88 | return $this->hasOne(Company::class, ['id' => 'company_id']); 89 | } 90 | 91 | /** 92 | * Gets query for [[Products]]. 93 | * 94 | * @return \yii\db\ActiveQuery 95 | */ 96 | public function getProducts() 97 | { 98 | return $this->hasMany(Product::class, ['category_id' => 'id']); 99 | } 100 | 101 | /** 102 | * Gets query for [[Variations]]. 103 | * 104 | * @return \yii\db\ActiveQuery 105 | */ 106 | public function getVariations() 107 | { 108 | return $this->hasMany(Variation::class, ['category_id' => 'id']); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /views/product/_form.php: -------------------------------------------------------------------------------- 1 | 15 | 16 |
17 | 18 | 19 | 20 |
21 | field($model, 'code')->textInput(['maxlength' => true]) ?> 22 | 23 | field($model, 'name')->widget(Select2::class, [ 24 | 'data' => ArrayHelper::map($model::find()->all(), 'name', 'name'), 25 | 'options' => [ 26 | 'placeholder' => '', 27 | 'value' => $model->name, 28 | ], 29 | 'pluginOptions' => [ 30 | 'tags' => true, 31 | 'allowClear' => true, 32 | ], 33 | ]) ?> 34 | 35 | category->id); 37 | foreach ($variation_sets as $variation_set) { 38 | echo $form->field($model, "variations_form[$variation_set->id]")->widget(Select2::class, [ 39 | 'data' => ArrayHelper::map($variation_set->productVariations, 'name', 'name'), 40 | 'options' => [ 41 | 'placeholder' => Yii::t('app', 'Select'), 42 | ], 43 | 'pluginOptions' => [ 44 | 'tags' => true, 45 | 'allowClear' => true, 46 | ], 47 | ])->label($variation_set->name); 48 | } 49 | ?> 50 | 51 | field($model, 'unit_price', [ 52 | 'addon' => [ 53 | 'prepend' => [ 54 | 'content' => Yii::$app->formatter->getCurrencySymbol(), 55 | 'options' => ['class' => 'alert-secondary'], 56 | ] 57 | ] 58 | ])->widget(NumberControl::class, [ 59 | 'maskedInputOptions' => [ 60 | 'allowMinus' => false, 61 | 'rightAlign' => false, 62 | ], 63 | 'displayOptions' => [ 64 | 'class' => 'form-control rounded-right' 65 | ] 66 | ]) ?> 67 | 68 | field($model, 'max_amount')->widget(NumberControl::class, [ 69 | 'maskedInputOptions' => [ 70 | 'allowMinus' => false, 71 | 'rightAlign' => false, 72 | ], 73 | ]) ?> 74 | 75 | field($model, 'min_amount')->widget(NumberControl::class, [ 76 | 'maskedInputOptions' => [ 77 | 'allowMinus' => false, 78 | 'rightAlign' => false, 79 | ], 80 | ]) ?> 81 |
82 | 83 | 86 | 87 | 88 | 89 |
-------------------------------------------------------------------------------- /models/ProductSearch.php: -------------------------------------------------------------------------------- 1 | $query, 52 | ]); 53 | 54 | $this->load($params); 55 | 56 | $dataProvider->sort->attributes = array_merge($dataProvider->sort->attributes, [ 57 | 'category_id' => [ 58 | 'asc' => ['category.name' => SORT_ASC], 59 | 'desc' => ['category.name' => SORT_DESC], 60 | ], 61 | ]); 62 | 63 | if (!$this->validate()) { 64 | // uncomment the following line if you do not want to return any records when validation fails 65 | // $query->where('0=1'); 66 | return $dataProvider; 67 | } 68 | 69 | // grid filtering conditions 70 | $this->makeFiltersWhere($query); 71 | $this->filterName($query, $this->name); 72 | 73 | return $dataProvider; 74 | } 75 | 76 | public static function filterName(ActiveQuery &$query, $name) 77 | { 78 | $terms = explode(' ', $name); 79 | 80 | $query->leftJoin('product_variation', 'product.id = product_variation.product_id'); 81 | $query->groupBy('product.id'); 82 | 83 | $query->select(new Expression("product.*, concat_ws(' ', product.name, NULL, group_concat(product_variation.name SEPARATOR ' ')) full_name")); 84 | 85 | foreach ($terms as $term) { 86 | $query->andFilterHaving(['like', 'full_name', $term]); 87 | } 88 | } 89 | 90 | protected function makeFiltersWhere(ActiveQuery &$query) 91 | { 92 | $query->andFilterWhere([ 93 | 'unit_price' => $this->unit_price, 94 | 'amount' => $this->amount, 95 | 'is_deleted' => $this->is_deleted, 96 | ]); 97 | 98 | $query->andFilterWhere(['like', 'category.name', $this->category_id]); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /views/pos/index.php: -------------------------------------------------------------------------------- 1 | title = Yii::t('app', 'Point of Sale'); 10 | $this->params['breadcrumbs'][] = $this->title; 11 | 12 | PosAsset::register($this); 13 | Dialog::widget(); 14 | 15 | $this->registerCSS( 16 | <<< CSS 17 | a { 18 | cursor: pointer; 19 | } 20 | div .col-sm { 21 | margin-top: 1.25rem; 22 | } 23 | th:last-child, td:last-child { 24 | text-align: center; 25 | } 26 | CSS 27 | ); 28 | 29 | ?> 30 |
31 |
32 | render('_form', [ 33 | 'model' => $item 34 | ]); ?> 35 |
36 |
37 |

38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
#
{{total}}
58 |
59 |
60 |
61 |
62 | $item->order_id], [ 63 | 'class' => 'btn btn-outline-danger', 64 | 'data' => [ 65 | 'confirm' => Yii::t('app', 'Are you sure you want to delete this order?'), 66 | 'method' => 'post', 67 | ], 68 | ]) ?> 69 |
70 |
71 | $code]) ?>> 72 |
73 |

74 |

75 |
76 |
77 | 78 |
79 |
80 |
81 |
-------------------------------------------------------------------------------- /views/address/_form.php: -------------------------------------------------------------------------------- 1 | registerJs( 14 | <<< 'JS' 15 | function getZipcode(code) { 16 | axios.get('/api/address',{ 17 | params: { code: code } 18 | }).then(function (response) { 19 | const data = response.data; 20 | setForm(data); 21 | delete data['complement']; 22 | delete data['zip_code']; 23 | toggleReadOnly(data); 24 | }).catch(function (error) { 25 | const data = error.response.data; 26 | $(document).Toasts('create', { 27 | title: data.name, 28 | body: data.message, 29 | autohide: true, 30 | delay: 5000, 31 | class: 'bg-warning fix-toast' 32 | }) 33 | }); 34 | } 35 | 36 | function setForm(data) { 37 | $('#address-federated_unit').val(data.federated_unit); 38 | $('#address-city').val(data.city); 39 | $('#address-neighborhood').val(data.neighborhood); 40 | $('#address-street').val(data.street); 41 | } 42 | 43 | function toggleReadOnly(data) { 44 | Object.keys(data).forEach(function(element) { 45 | const target = $('#address-'+element); 46 | if (target.val() == '') 47 | target.prop('readonly', false); 48 | else if (!target.is('[readonly]')) 49 | target.prop('readonly', true); 50 | }) 51 | } 52 | 53 | $("#address-zip_code").keyup(function(event) { 54 | const zipcode = event.target.value; 55 | if (zipcode.length == 8) 56 | getZipcode(zipcode); 57 | else 58 | setForm({ federated_unit: '', city: '', neighborhood: '', street: ''}); 59 | }); 60 | JS, 61 | $this::POS_END 62 | ) 63 | 64 | ?> 65 | 66 |
67 | 68 | 69 | 70 |
71 | field($model, 'zip_code')->textInput(['maxlength' => true]) ?> 72 | 73 | field($model, 'federated_unit')->textInput(['maxlength' => true, 'readonly' => true]) ?> 74 | 75 | field($model, 'city')->textInput(['maxlength' => true, 'readonly' => true]) ?> 76 | 77 | field($model, 'neighborhood')->textInput(['maxlength' => true, 'readonly' => true]) ?> 78 | 79 | field($model, 'street')->textInput(['maxlength' => true, 'readonly' => true]) ?> 80 | 81 | field($model, 'number')->textInput(['maxlength' => true]) ?> 82 | 83 | field($model, 'complement')->textInput(['maxlength' => true]) ?> 84 |
85 | 86 | 89 | 90 | 91 | 92 |
93 | -------------------------------------------------------------------------------- /modules/api/controllers/SaleController.php: -------------------------------------------------------------------------------- 1 | ContentNegotiator::class, 21 | 'formats' => [ 22 | 'application/json' => Response::FORMAT_JSON, 23 | ], 24 | ], 25 | 'access' => [ 26 | 'class' => AccessControl::class, 27 | 'rules' => [ 28 | [ 29 | 'actions' => ['week'], 30 | 'allow' => true, 31 | 'roles' => ['admin'] 32 | ], 33 | [ 34 | 'actions' => ['index'], 35 | 'allow' => true, 36 | 'roles' => ['cashier'] 37 | ], 38 | ], 39 | ], 40 | 'verbs' => [ 41 | 'class' => VerbFilter::class, 42 | 'actions' => [ 43 | 'index' => ['GET'], 44 | ], 45 | ], 46 | ]; 47 | } 48 | 49 | public function actionIndex($id) 50 | { 51 | $sale = $this->findModel($id); 52 | return $sale; 53 | } 54 | 55 | public function actionWeek() 56 | { 57 | $data = [ 58 | 'dates' => [], 59 | 'amount_paid' => [ 60 | 'label' => Yii::t('app', 'Sales value'), 61 | 'values' => [] 62 | ], 63 | 'total_sale' => [ 64 | 'label' => Yii::t('app', 'Total sales'), 65 | 'values' => [] 66 | ] 67 | ]; 68 | 69 | for ($i = 7; $i > 0; $i--) { 70 | $date = date('Y-m-d', strtotime("-$i day")); 71 | $start = $date.' 00:00:00'; 72 | $end = $date.' 23:59:59'; 73 | $amount_paid = Sale::find() 74 | ->andWhere(['between', 'sale_at', $start, $end]) 75 | ->andWhere(['is_canceled' => 0])->sum('amount_paid'); 76 | 77 | $total_sale = Sale::find() 78 | ->andWhere(['between', 'sale_at', $start, $end]) 79 | ->andWhere(['is_canceled' => 0])->count(); 80 | 81 | array_push($data['dates'], Yii::$app->formatter->asDate($date)); 82 | array_push($data['amount_paid']['values'], (float) $amount_paid); 83 | array_push($data['total_sale']['values'], $total_sale); 84 | } 85 | 86 | return $data; 87 | } 88 | 89 | protected function findModel($id) 90 | { 91 | $model = Sale::find() 92 | ->andWhere(['sale.id' => $id]) 93 | ->one(); 94 | 95 | if ($model !== null) 96 | return $model; 97 | 98 | throw new NotFoundHttpException(Yii::t('app', 'Sale not exist.')); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /modules/api/controllers/OrderItemController.php: -------------------------------------------------------------------------------- 1 | ContentNegotiator::class, 24 | 'formats' => [ 25 | 'application/json' => Response::FORMAT_JSON, 26 | ], 27 | ], 28 | 'access' => [ 29 | 'class' => AccessControl::class, 30 | 'rules' => [ 31 | [ 32 | 'actions' => ['create', 'delete', 'validation'], 33 | 'allow' => true, 34 | 'roles' => ['cashier'] 35 | ], 36 | ], 37 | ], 38 | 'verbs' => [ 39 | 'class' => VerbFilter::class, 40 | 'actions' => [ 41 | 'create' => ['POST'], 42 | 'validation' => ['POST'], 43 | 'delete' => ['DELETE'], 44 | ], 45 | ], 46 | ]; 47 | } 48 | 49 | public function beforeAction($action) 50 | { 51 | if (in_array($action->id, ['delete'])) { 52 | $this->enableCsrfValidation = false; 53 | } 54 | 55 | return parent::beforeAction($action); 56 | } 57 | 58 | public function actionCreate() 59 | { 60 | $model = new OrderItem(); 61 | 62 | if ($model->load(Yii::$app->request->post()) && $model->save()) { 63 | return [ 64 | 'orderItem' => $model, 65 | 'total' => $model->order->toArray()['total_value'] 66 | ]; 67 | } 68 | 69 | foreach($model->firstErrors as $erro) break; 70 | throw new BadRequestHttpException($erro); 71 | } 72 | 73 | public function actionDelete($id) 74 | { 75 | $model = $this->findModel($id); 76 | $orderId = $model->order_id; 77 | 78 | $model->delete($id); 79 | 80 | return [ 81 | 'total' => Order::findOne($orderId)->total_value 82 | ]; 83 | } 84 | 85 | public function actionValidation() 86 | { 87 | $model = new OrderItem(); 88 | 89 | if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) { 90 | Yii::$app->response->format = Response::FORMAT_JSON; 91 | return ActiveForm::validate($model); 92 | } 93 | } 94 | 95 | protected function findModel($id) 96 | { 97 | $model = OrderItem::find() 98 | ->andWhere(['order_item.id' => $id]) 99 | ->one(); 100 | 101 | if ($model !== null) 102 | return $model; 103 | 104 | throw new NotFoundHttpException(Yii::t('app', 'Order not exist.')); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/yii2-app-basic", 3 | "description": "Yii 2 Basic Project Template", 4 | "keywords": ["yii2", "framework", "basic", "project template"], 5 | "homepage": "http://www.yiiframework.com/", 6 | "type": "project", 7 | "license": "BSD-3-Clause", 8 | "support": { 9 | "issues": "https://github.com/yiisoft/yii2/issues?state=open", 10 | "forum": "http://www.yiiframework.com/forum/", 11 | "wiki": "http://www.yiiframework.com/wiki/", 12 | "irc": "irc://irc.freenode.net/yii", 13 | "source": "https://github.com/yiisoft/yii2" 14 | }, 15 | "minimum-stability": "stable", 16 | "require": { 17 | "php": ">=5.6.0", 18 | "yiisoft/yii2": "~2.0.14", 19 | "yiisoft/yii2-bootstrap4": "^2.0", 20 | "yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0", 21 | "rmrevin/yii2-fontawesome": "~3.5", 22 | "npm-asset/axios": "~0.19.0", 23 | "almasaeed2010/adminlte": "~3.0", 24 | "npm-asset/source-sans-pro" : "^3.012", 25 | "kartik-v/yii2-widget-datepicker": "^1.4", 26 | "kartik-v/yii2-icons": "^1.4", 27 | "kartik-v/yii2-number": "@dev", 28 | "kartik-v/yii2-widget-activeform": "^1.5", 29 | "kartik-v/yii2-widget-select2": "2.1.7", 30 | "kartik-v/yii2-widget-datetimepicker": "^1.4", 31 | "kartik-v/yii2-date-range": "^1.7", 32 | "yii2tech/ar-softdelete": "^1.0", 33 | "npm-asset/vue": "^2.6", 34 | "kartik-v/yii2-grid": "@dev", 35 | "npm-asset/chart.js": "^2.9", 36 | "kartik-v/yii2-export": "^1.4" 37 | }, 38 | "require-dev": { 39 | "yiisoft/yii2-debug": "~2.1.0", 40 | "yiisoft/yii2-gii": "~2.1.0", 41 | "yiisoft/yii2-faker": "~2.0.0", 42 | "codeception/codeception": "^4.0", 43 | "codeception/verify": "~0.5.0 || ~1.1.0", 44 | "codeception/specify": "~0.4.6", 45 | "symfony/browser-kit": ">=2.7 <=4.2.4", 46 | "codeception/module-filesystem": "^1.0.0", 47 | "codeception/module-yii2": "^1.0.0", 48 | "codeception/module-asserts": "^1.0.0" 49 | }, 50 | "config": { 51 | "process-timeout": 1800, 52 | "fxp-asset": { 53 | "enabled": false 54 | } 55 | }, 56 | "scripts": { 57 | "post-install-cmd": [ 58 | "yii\\composer\\Installer::postInstall" 59 | ], 60 | "post-create-project-cmd": [ 61 | "yii\\composer\\Installer::postCreateProject", 62 | "yii\\composer\\Installer::postInstall" 63 | ] 64 | }, 65 | "extra": { 66 | "yii\\composer\\Installer::postCreateProject": { 67 | "setPermission": [ 68 | { 69 | "runtime": "0777", 70 | "web/assets": "0777", 71 | "yii": "0755" 72 | } 73 | ] 74 | }, 75 | "yii\\composer\\Installer::postInstall": { 76 | "generateCookieValidationKey": [ 77 | "config/web.php" 78 | ] 79 | }, 80 | "asset-installer-paths": { 81 | "npm-asset-library": "vendor/npm", 82 | "bower-asset-library": "vendor/bower" 83 | } 84 | }, 85 | "repositories": [ 86 | { 87 | "type": "composer", 88 | "url": "https://asset-packagist.org" 89 | } 90 | ] 91 | } 92 | -------------------------------------------------------------------------------- /models/Expense.php: -------------------------------------------------------------------------------- 1 | 'company', 35 | 'on' => 'expense.company_id = company.id' 36 | ] 37 | ]; 38 | const SCENARIO_PAID = 'paid'; 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public static function tableName() 44 | { 45 | return 'expense'; 46 | } 47 | 48 | public function behaviors() 49 | { 50 | return [ 51 | [ 52 | 'class' => TimestampBehavior::class, 53 | 'value' => new Expression('NOW()'), 54 | ], 55 | ]; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function rules() 62 | { 63 | return [ 64 | [['name', 'value', 'payday', 'company_id'], 'required'], 65 | [['paid_at', 'is_paid'], 'required', 'on' => self::SCENARIO_PAID], 66 | [['description'], 'string'], 67 | [['value'], DecimalValidator::class], 68 | [['payday', 'paid_at', 'created_at', 'updated_at'], 'safe'], 69 | [['payday'], 'default', 'value' => null], 70 | [['company_id'], 'integer'], 71 | [['is_paid'], 'boolean'], 72 | [['is_paid'], 'default', 'value' => 0], 73 | [['name'], 'string', 'max' => 64], 74 | [['company_id'], 'exist', 'skipOnError' => true, 'targetClass' => Company::class, 'targetAttribute' => ['company_id' => 'id']], 75 | ]; 76 | } 77 | 78 | public function scenarios() 79 | { 80 | $scenarios = parent::scenarios(); 81 | $scenarios[self::SCENARIO_PAID] = ['paid_at', 'is_paid']; 82 | 83 | return $scenarios; 84 | } 85 | 86 | /** 87 | * {@inheritdoc} 88 | */ 89 | public function attributeLabels() 90 | { 91 | return [ 92 | 'id' => Yii::t('app', 'ID'), 93 | 'name' => Yii::t('app', 'Name'), 94 | 'description' => Yii::t('app', 'Description'), 95 | 'value' => Yii::t('app', 'Value'), 96 | 'payday' => Yii::t('app', 'Payday'), 97 | 'is_paid' => Yii::t('app', 'Is Paid'), 98 | 'paid_at' => Yii::t('app', 'Paid At'), 99 | 'created_at' => Yii::t('app', 'Created At'), 100 | 'updated_at' => Yii::t('app', 'Updated At'), 101 | 'company_id' => Yii::t('app', 'Company'), 102 | ]; 103 | } 104 | 105 | /** 106 | * Gets query for [[Company]]. 107 | * 108 | * @return \yii\db\ActiveQuery 109 | */ 110 | public function getCompany() 111 | { 112 | return $this->hasOne(Company::class, ['id' => 'company_id']); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /views/category/view.php: -------------------------------------------------------------------------------- 1 | title = $model->name; 12 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Categories'), 'url' => ['index']]; 13 | $this->params['breadcrumbs'][] = $this->title; 14 | 15 | $this->registerCssFile('@web/css/detailView.css'); 16 | $this->registerJsFile('@web/js/modal.js', ['depends' => [yii\web\JqueryAsset::class]]); 17 | 18 | Dialog::widget(); 19 | 20 | ?> 21 |
22 | render('@app/views/layouts/modal.php', ['options' => ['title' => Yii::t('app', 'Category')]]) ?> 23 | 24 |
25 |
26 |
27 |
28 |

29 |
30 | 33 |
34 |
35 |
36 | $model, 38 | 'attributes' => [ 39 | 'name' 40 | ], 41 | ]) ?> 42 |
43 |
44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 |

52 |
53 | 56 |
57 |
58 |
59 | $model, 61 | 'attributes' => [ 62 | 'created_at:datetime', 63 | 'updated_at:datetime', 64 | ], 65 | ]) ?> 66 |
67 |
68 |
69 |
70 | 71 |
72 |
73 |

74 | $model->id], ['value' => Url::to(['update', 'id' => $model->id]), 'class' => 'btn btn-primary btn-modal', 'data-toggle' => 'modal']) ?> 75 | $model->id], [ 76 | 'class' => 'btn btn-danger', 77 | 'data' => [ 78 | 'confirm' => Yii::t('app', 'Are you sure you want to delete this category?'), 79 | 'method' => 'post', 80 | ], 81 | ]) ?> 82 |

83 |
84 |
85 |
86 | -------------------------------------------------------------------------------- /views/sale/_search.php: -------------------------------------------------------------------------------- 1 | 15 | 16 | ['index'], 18 | 'method' => 'get', 19 | ]); ?> 20 | 21 | 97 | 98 | -------------------------------------------------------------------------------- /views/variation/view.php: -------------------------------------------------------------------------------- 1 | title = $model->name; 11 | $this->params['breadcrumbs'][] = ['label' => Yii::t('app', 'Variations'), 'url' => ['index']]; 12 | $this->params['breadcrumbs'][] = $this->title; 13 | 14 | $this->registerCssFile('@web/css/detailView.css'); 15 | $this->registerJsFile('@web/js/modal.js', ['depends' => [yii\web\JqueryAsset::class]]); 16 | 17 | ?> 18 |
19 | render('@app/views/layouts/modal.php', ['options' => ['title' => Yii::t('app', 'Variation')]]) ?> 20 | 21 |
22 |
23 |
24 |
25 |

26 |
27 | 30 |
31 |
32 |
33 | $model, 35 | 'attributes' => [ 36 | 'name', 37 | [ 38 | 'attribute' => 'category.name', 39 | 'label' => Yii::t('app', 'Category') 40 | ] 41 | ], 42 | ]) ?> 43 |
44 |
45 |
46 |
47 | 48 |
49 |
50 |
51 |
52 |

53 |
54 | 57 |
58 |
59 |
60 | $model, 62 | 'attributes' => [ 63 | 'created_at:datetime', 64 | 'updated_at:datetime', 65 | ], 66 | ]) ?> 67 |
68 |
69 |
70 |
71 | 72 |
73 |
74 |

75 | $model->id], ['value' => Url::to(['update', 'id' => $model->id]), 'class' => 'btn btn-primary btn-modal', 'data-toggle' => 'modal']) ?> 76 | $model->id], [ 77 | 'class' => 'btn btn-danger', 78 | 'data' => [ 79 | 'confirm' => Yii::t('app', 'Are you sure you want to delete this employee?'), 80 | 'method' => 'post', 81 | ], 82 | ]) ?> 83 |

84 |
85 |
86 |
87 | --------------------------------------------------------------------------------