feat: 财务系统二次开发 - 营销账户账单功能
- 基础环境:Docker 部署配置,源码挂载实时同步 - 修复:super_admin 权限补全、登录重定向循环 - 开发:店铺营销账户账单 - 抖音账户(15字段汇总账单) - 开发:店铺营销账户账单 - 淘系支付宝账户(8字段流水账单) - 开发:店铺营销账户账单 - 拼多多账户(7字段流水账单) - 业务页面导航集成:config/business_navigation.php + Business\Pages 控制器 - 各平台独立表结构和视图,互不影响
This commit is contained in:
commit
1adf627c0d
16
.dockerignore
Normal file
16
.dockerignore
Normal file
@ -0,0 +1,16 @@
|
||||
.env
|
||||
.git
|
||||
.gitignore
|
||||
.idea
|
||||
.vscode
|
||||
node_modules
|
||||
database/database.sqlite
|
||||
storage/debugbar
|
||||
storage/framework/cache/data/*
|
||||
storage/framework/sessions/*
|
||||
storage/framework/views/*
|
||||
storage/logs/*
|
||||
tests
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
@ -0,0 +1,18 @@
|
||||
; This file is for unifying the coding style for different editors and IDEs.
|
||||
; More information at https://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = space
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.yml]
|
||||
indent_size = 2
|
||||
27
.gitattributes
vendored
Normal file
27
.gitattributes
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Set the default behavior, in case people don't have core.autocrlf set.
|
||||
* text eol=lf
|
||||
|
||||
# Explicitly declare text files you want to always be normalized and converted
|
||||
# to native line endings on checkout.
|
||||
*.c text
|
||||
*.h text
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.sln text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.gif binary
|
||||
*.otf binary
|
||||
*.eot binary
|
||||
*.svg binary
|
||||
*.ttf binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.sqlite binary
|
||||
|
||||
*.css linguist-vendored
|
||||
*.scss linguist-vendored
|
||||
*.js linguist-vendored
|
||||
CHANGELOG.md export-ignore
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
patreon: akaunting
|
||||
21
.github/ISSUE_TEMPLATE.md
vendored
Normal file
21
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Please, submit only real issues. Use the forum for support, feature requests, proposals, new versions, help etc. https://akaunting.com/forum
|
||||
|
||||
### Steps to reproduce the issue
|
||||
|
||||
|
||||
|
||||
### Expected result
|
||||
|
||||
|
||||
|
||||
### Actual result
|
||||
|
||||
|
||||
|
||||
### System information (Akaunting, PHP versions)
|
||||
|
||||
|
||||
|
||||
### Additional comments
|
||||
|
||||
|
||||
63
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
63
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
name: Bug report
|
||||
description: File a bug report related to Akaunting
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out this bug report!
|
||||
- type: input
|
||||
id: akaunting
|
||||
attributes:
|
||||
label: Akaunting version
|
||||
description: "Please provide the full Akaunting version of your installation."
|
||||
placeholder: "2.1.20"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: php
|
||||
attributes:
|
||||
label: PHP version
|
||||
description: "Please provide the full PHP version that is powering Akaunting."
|
||||
placeholder: "7.4.10"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: "Which operating system do you use? Please provide the version as well."
|
||||
placeholder: "Ubuntu 20.04"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Which steps do we need to take to reproduce this error?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: Expected result
|
||||
description: What is the expected result?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: actual
|
||||
attributes:
|
||||
label: Actual result
|
||||
description: What is the actual result?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: comments
|
||||
attributes:
|
||||
label: Additional comments
|
||||
description: Anything else we should know about?
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Copy and paste any relevant log output. No need for backticks.
|
||||
render: shell
|
||||
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
11
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Feature request
|
||||
url: https://akaunting.com/forum
|
||||
about: 'For ideas or feature requests, start a new discussion'
|
||||
- name: Support, help, and other
|
||||
url: https://akaunting.com/support
|
||||
about: 'This repository is only for reporting bugs'
|
||||
- name: Documentation
|
||||
url: https://github.com/akaunting/docs
|
||||
about: For documentation improvements, open a pull request at the akaunting/docs repository
|
||||
51
.github/workflows/tests.yml
vendored
Normal file
51
.github/workflows/tests.yml
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
|
||||
name: PHP ${{ matrix.php }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php: ['8.1', '8.2', '8.3']
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache Composer
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: bcmath, ctype, dom, fileinfo, intl, gd, json, mbstring, pdo, pdo_sqlite, openssl, sqlite, xml, zip
|
||||
coverage: none
|
||||
|
||||
- name: Copy .env
|
||||
run: cp .env.testing .env
|
||||
|
||||
- name: Install NPM
|
||||
run: npm install
|
||||
|
||||
- name: Compile assets
|
||||
run: npm run production
|
||||
|
||||
- name: Install Composer
|
||||
run: composer test
|
||||
|
||||
- name: Execute tests
|
||||
run: php artisan test --parallel
|
||||
106
.gitignore
vendored
Normal file
106
.gitignore
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
/node_modules
|
||||
/public/hot
|
||||
/public/storage
|
||||
/public/js
|
||||
/public/mix-manifest.json
|
||||
/storage/*.key
|
||||
/vendor
|
||||
/.idea
|
||||
/.history
|
||||
/.vscode
|
||||
/.claude
|
||||
/.vagrant
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
.env
|
||||
robots.txt
|
||||
_ide_helper.php
|
||||
_ide_helper_models.php
|
||||
.phpstorm.meta.php
|
||||
/storage/debugbar/*
|
||||
.phpunit.result.cache
|
||||
modules/*
|
||||
!modules/.gitkeep
|
||||
!modules/EcommerceFinance/
|
||||
!modules/EcommerceFinance/**
|
||||
!modules/OfflinePayments/
|
||||
!modules/OfflinePayments/**
|
||||
!modules/PaypalStandard/
|
||||
!modules/PaypalStandard/**
|
||||
.laravelstatsrc
|
||||
.DS_Store
|
||||
.workbuddy/
|
||||
runtime/.env
|
||||
49
.htaccess
Normal file
49
.htaccess
Normal file
@ -0,0 +1,49 @@
|
||||
# Prevent Directory Listing
|
||||
<IfModule autoindex>
|
||||
IndexIgnore *
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rewrite.c>
|
||||
# Prevent Directory Listing
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Prevent Direct Access to Protected Files
|
||||
<FilesMatch "(?i)(^artisan$|\.env|\.log)">
|
||||
# Apache 2.2 syntax
|
||||
<IfModule !mod_authz_core.c>
|
||||
Order deny,allow
|
||||
Deny from all
|
||||
</IfModule>
|
||||
# Apache 2.4 syntax
|
||||
<IfModule mod_authz_core.c>
|
||||
Require all denied
|
||||
</IfModule>
|
||||
</FilesMatch>
|
||||
|
||||
# Prevent Direct Access to Protected Files (OpenLiteSpeed syntax)
|
||||
RewriteCond %{REQUEST_URI} (^|/)(\.env|\.log|artisan)$ [NC]
|
||||
RewriteRule .* - [F,L]
|
||||
|
||||
# Prevent Direct Access To Protected Folders
|
||||
RewriteRule ^(app|bootstrap|config|database|overrides|resources|routes|storage|tests)/(.*) / [L,R=301]
|
||||
|
||||
# Prevent Direct Access To modules/vendor Folders Except Assets
|
||||
RewriteRule ^(modules|vendor)/(.*)\.((?!ico|gif|jpg|jpeg|png|js\b|css|less|sass|font|woff|woff2|eot|ttf|svg|xls|xlsx).)*$ / [L,R=301]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)/$ /$1 [L,R=301]
|
||||
|
||||
# Send Requests To Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
</IfModule>
|
||||
72
AGENTS.md
Normal file
72
AGENTS.md
Normal file
@ -0,0 +1,72 @@
|
||||
# CaiWuXiTong 团队通用规则
|
||||
|
||||
适用范围:本仓库所有成员、所有分支,以及使用 Codex 等 AI 编程工具完成的工作。
|
||||
|
||||
## 1. 核心原则
|
||||
|
||||
- 本项目是财务系统。金额、权限、审批、数据导入、数据库迁移和生产配置均属于高风险内容。
|
||||
- AI 是协作工具,不是审批人。AI 生成的代码、命令和结论必须由提交人检查并完成实际验证。
|
||||
- 不确定当前环境、分支、命令作用或数据范围时,停止操作并向负责人或 Codex 说明现场,不要猜。
|
||||
- 一次只完成一个明确任务;不顺手重构、升级依赖或修改无关模块。
|
||||
|
||||
## 2. 分支与提交
|
||||
|
||||
- `main` 是稳定主线,禁止直接推送、禁止强推、禁止直接修改。
|
||||
- 每项任务都必须从最新 `main` 新建分支;不得长期共用或复用旧任务分支。
|
||||
- 分支命名:新功能用 `feature/用户名-任务名`,修复用 `fix/用户名-问题名`,紧急修复用 `hotfix/用户名-问题名`。
|
||||
- 示例:`feature/dodo-login`、`fix/wangshu-amount-rounding`。
|
||||
- 一个分支只做一个任务。任务完成并合并后,删除该分支;下一项任务重新从 `main` 新建。
|
||||
- 提交前必须查看 `git status` 和 `git diff`,确认没有把别人的改动、日志、构建产物、`.env` 或敏感文件带进去。
|
||||
- 禁止使用 `git push --force`、`git reset --hard`、`git clean -fd`,除非负责人明确给出目标和操作步骤。
|
||||
|
||||
## 3. 新人使用 AI 的固定流程
|
||||
|
||||
1. 先确认任务目标、验收方式、影响范围和不能触碰的模块。
|
||||
2. 进入正确项目和分支,先执行 `git status`;看到陌生改动,先停止并说明情况。
|
||||
3. 向 AI 提供:任务目标、当前分支、报错或现象、相关文件、不可修改的范围。
|
||||
4. 要求 AI 先说明改动计划、涉及文件、风险和验证步骤;确认后再修改。
|
||||
5. 修改后查看 diff,并要求 AI 用普通语言解释“改了什么、为什么、怎样测试”。
|
||||
6. 在本地或测试环境完成最小有效验证,记录实际结果。
|
||||
7. 推送任务分支并发起 PR;不得自行绕过 PR 合并到 `main`。
|
||||
|
||||
推荐向 AI 提问的模板:
|
||||
|
||||
```text
|
||||
项目:CaiWuXiTong(Laravel/PHP 财务系统)。
|
||||
任务:<要实现的功能或要修复的问题>。
|
||||
当前分支:<分支名>。
|
||||
现场:<报错、复现步骤、相关文件>。
|
||||
边界:不要修改 <模块/表/配置>;不要执行删除、部署、生产数据库操作。
|
||||
要求:先列出改动计划、风险和验证步骤,等我确认后再改。
|
||||
```
|
||||
|
||||
## 4. PR 与合并
|
||||
|
||||
- 所有任务分支必须通过 PR 合并到 `main`。
|
||||
- PR 作者必须填写:做了什么、Codex 改了哪些文件、怎样测试、是否涉及金额/权限/数据库/配置。
|
||||
- PR 至少由另一位成员检查后才能批准;作者不得把“AI 已完成”当作测试结果。
|
||||
- 涉及金额计算、权限、导入、数据库迁移、账户或系统配置时,必须在 PR 中明确标注,并由负责人最终确认。
|
||||
- 使用仓库默认的 Squash merge(压缩合并)。合并由负责人或指定合并人执行。
|
||||
- PR 合并后分支自动删除;不要把已合并分支继续用于新任务。
|
||||
|
||||
## 5. 测试与问题处理
|
||||
|
||||
- 修改完成不等于可用。至少验证正常流程、错误输入/无权限场景和本次改动影响的旧功能。
|
||||
- 出现冲突、测试失败或 PR 显示落后 `main` 时,不要自行猜命令。先让 AI 只分析同步方案,禁止强推和 rebase;必要时找负责人处理。
|
||||
- 提交问题时必须写清:环境、账号角色、复现步骤、实际结果、期望结果和报错截图/日志(脱敏后)。
|
||||
|
||||
## 6. 数据与安全红线
|
||||
|
||||
- 不得提交 `.env`、密码、Token、SSH 私钥、数据库连接串、真实财务数据、客户数据或生产日志。
|
||||
- 不得把上述内容粘贴到 AI 对话、工单、PR、聊天群或截图中。
|
||||
- 不得在不确认环境和影响范围时执行数据库写入、迁移、删除、导入、部署或服务器命令。
|
||||
- 任何包含金额、账户、登录凭据、权限或数据库连接的信息都按敏感数据处理。
|
||||
- 发现误提交、泄露或错误操作,立即告知负责人;不要自行删除记录掩盖问题。
|
||||
|
||||
## 7. 本仓库的最小验证要求
|
||||
|
||||
- 前端改动:至少检查目标页面、正常交互和错误提示。
|
||||
- 后端改动:至少执行相关测试或启动后走一次目标接口/页面。
|
||||
- 数据库迁移:新人不得自行在共享环境执行;先在 PR 说明影响、验证方式和回滚思路。
|
||||
- 未完成验证时,PR 必须明确写“未验证”及原因,不能写“应该没问题”。
|
||||
|
||||
78
LICENSE.txt
Normal file
78
LICENSE.txt
Normal file
@ -0,0 +1,78 @@
|
||||
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
||||
“Business Source License” is a trademark of MariaDB Corporation Ab.
|
||||
|
||||
Parameters
|
||||
|
||||
Licensor: Akaunting, Inc.
|
||||
|
||||
Licensed Work: Akaunting
|
||||
|
||||
Additional Use Grant: You may make production use of the Licensed Work, provided
|
||||
you do not use it in any of the following ways:
|
||||
|
||||
(1) Use the Licensed Work with a total of more than two users or
|
||||
one company or one thousand invoices.
|
||||
|
||||
(2) Remove the name, logo, copyright, or other branding material
|
||||
from the Licensed Work to create a "rebranded" or "white labeled"
|
||||
version to distribute as part of any commercial or non-profit
|
||||
product or service.
|
||||
|
||||
(3) Use the Licensed Work for an Accounting Service. An
|
||||
"Accounting Service" is a commercial offering that allows third
|
||||
parties (other than your employees and contractors) to access
|
||||
the functionality of the Licensed Work so that such third
|
||||
parties directly benefit from the accounting or invoicing
|
||||
features of the Licensed Work.
|
||||
|
||||
Change Date: Four years from the date the Licensed Work is published.
|
||||
|
||||
Change License: GPLv3
|
||||
|
||||
For information about alternative licensing arrangements for the Licensed Work,
|
||||
please visit: https://akaunting.com/plans-on-premise
|
||||
|
||||
Notice
|
||||
|
||||
Business Source License 1.1
|
||||
|
||||
Terms
|
||||
|
||||
The Licensor hereby grants you the right to copy, modify, create derivative
|
||||
works, redistribute, and make non-production use of the Licensed Work. The
|
||||
Licensor may make an Additional Use Grant, above, permitting limited production use.
|
||||
|
||||
Effective on the Change Date, or the fourth anniversary of the first publicly
|
||||
available distribution of a specific version of the Licensed Work under this
|
||||
License, whichever comes first, the Licensor hereby grants you rights under
|
||||
the terms of the Change License, and the rights granted in the paragraph
|
||||
above terminate.
|
||||
|
||||
If your use of the Licensed Work does not comply with the requirements
|
||||
currently in effect as described in this License, you must purchase a
|
||||
commercial license from the Licensor, its affiliated entities, or authorized
|
||||
resellers, or you must refrain from using the Licensed Work.
|
||||
|
||||
All copies of the original and modified Licensed Work, and derivative works
|
||||
of the Licensed Work, are subject to this License. This License applies
|
||||
separately for each version of the Licensed Work and the Change Date may vary
|
||||
for each version of the Licensed Work released by Licensor.
|
||||
|
||||
You must conspicuously display this License on each original or modified copy
|
||||
of the Licensed Work. If you receive the Licensed Work in original or
|
||||
modified form from a third party, the terms and conditions set forth in this
|
||||
License apply to your use of that work.
|
||||
|
||||
Any use of the Licensed Work in violation of this License will automatically
|
||||
terminate your rights under this License for the current and all other
|
||||
versions of the Licensed Work.
|
||||
|
||||
This License does not grant you any right in any trademark or logo of
|
||||
Licensor or its affiliates (provided that you may use a trademark or logo of
|
||||
Licensor as expressly required by this License).
|
||||
|
||||
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
||||
AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
||||
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
||||
TITLE.
|
||||
119
LOCAL_DEPLOY.md
Normal file
119
LOCAL_DEPLOY.md
Normal file
@ -0,0 +1,119 @@
|
||||
# CaiWuXiTong 本地部署说明
|
||||
|
||||
## 项目概况
|
||||
- 基于 [Akaunting](https://akaunting.com) 二次开发的"江陵"版财务系统
|
||||
- 技术栈:PHP 8.3 + Laravel + Vue + Apache + MySQL 8.0
|
||||
- 访问端口:**18088**
|
||||
|
||||
## 文件清单(本地部署新增)
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `docker-compose.local.yml` | 本地 compose:app + mysql,bridge 网络 |
|
||||
| `deploy/Dockerfile.local` | 本地 Dockerfile:基于原 Dockerfile,增加 composer/npm 依赖安装 |
|
||||
| `deploy/local-init.sh` | 一键构建+启动+初始化脚本 |
|
||||
| `runtime/.env` | 应用环境配置(挂载到容器 `/var/www/html/.env`) |
|
||||
| `LOCAL_DEPLOY.md` | 本说明 |
|
||||
|
||||
> 原 `compose.jiangling.yml` / `deploy/Dockerfile` 未改动,保留团队主线一致性。
|
||||
|
||||
## 一键部署
|
||||
```bash
|
||||
cd CaiWuXiTong_src
|
||||
bash deploy/local-init.sh
|
||||
```
|
||||
|
||||
## 手动步骤
|
||||
```bash
|
||||
# 1. 启动
|
||||
docker compose -f docker-compose.local.yml up -d --build
|
||||
|
||||
# 2. 初始化数据库与管理员
|
||||
docker compose -f docker-compose.local.yml exec app \
|
||||
php artisan install --no-interaction \
|
||||
--db-host=mysql --db-name=akaunting \
|
||||
--db-username=akaunting --db-password=akaunting123 \
|
||||
--admin-email=admin@company.com --admin-password=admin123 \
|
||||
--locale=zh-CN
|
||||
|
||||
# 3. 访问
|
||||
open http://127.0.0.1:18088
|
||||
```
|
||||
|
||||
## 登录信息
|
||||
- **地址**:http://127.0.0.1:18088
|
||||
- **邮箱**:`admin@company.com`
|
||||
- **密码**:`admin123`
|
||||
|
||||
## 常用维护命令
|
||||
```bash
|
||||
# 查看日志
|
||||
docker compose -f docker-compose.local.yml logs -f app
|
||||
|
||||
# 重启
|
||||
docker compose -f docker-compose.local.yml restart app
|
||||
|
||||
# 进入容器
|
||||
docker compose -f docker-compose.local.yml exec app bash
|
||||
|
||||
# 进入 MySQL
|
||||
docker compose -f docker-compose.local.yml exec mysql mysql -uakaunting -pakaunting123 akaunting
|
||||
|
||||
# 清缓存
|
||||
docker compose -f docker-compose.local.yml exec app php artisan optimize:clear
|
||||
|
||||
# 重新迁移
|
||||
docker compose -f docker-compose.local.yml exec app php artisan migrate --force
|
||||
|
||||
# 停止 / 启动 / 销毁
|
||||
docker compose -f docker-compose.local.yml stop
|
||||
docker compose -f docker-compose.local.yml start
|
||||
docker compose -f docker-compose.local.yml down # 仅停容器
|
||||
docker compose -f docker-compose.local.yml down -v # 同时删除数据卷(会清空数据库!)
|
||||
```
|
||||
|
||||
## 端口映射
|
||||
| 服务 | 宿主机端口 | 容器端口 |
|
||||
|------|-----------|----------|
|
||||
| Akaunting (Apache) | 18088 | 18088 |
|
||||
| MySQL | 13306 | 3306 |
|
||||
|
||||
## 数据卷
|
||||
- `jiangling_storage` —— 应用上传文件、日志、缓存
|
||||
- `jiangling_mysql` —— MySQL 数据文件
|
||||
|
||||
## 注意事项
|
||||
- 修改 `.env` 后需重启 app 容器:`docker compose -f docker-compose.local.yml restart app`
|
||||
- 修改源码后需重新构建:`docker compose -f docker-compose.local.yml up -d --build app`
|
||||
- 此本地配置仅供开发/测试,**不要用于生产**(密码弱、APP_DEBUG=true)
|
||||
|
||||
## 重要:DB_PREFIX 必须用短前缀
|
||||
项目某个模块迁移创建了较长的唯一索引
|
||||
(`ec_stores_company_id_channel_id_external_id_deleted_at_unique`,60 字符),
|
||||
MySQL 标识符上限 64 字符,所以 `DB_PREFIX` 必须 ≤ 4 字符。本部署用 `a_`。
|
||||
|
||||
⚠️ **不要**让 `php artisan install` 自动生成前缀(它默认用 `str_random(3) . '_'` 是 4 字符,
|
||||
刚好不超),但**首次 install 必须显式传 `--db-prefix=a_`** 或其他 ≤4 字符值,
|
||||
否则可能随机到 5+ 字符前缀触发 1059 错误。
|
||||
|
||||
## 重置数据(清空重来)
|
||||
```bash
|
||||
docker compose -f docker-compose.local.yml exec mysql \
|
||||
mysql -uroot -prootpass -e "DROP DATABASE IF EXISTS akaunting; CREATE DATABASE akaunting CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
|
||||
|
||||
docker compose -f docker-compose.local.yml exec app php artisan install --no-interaction \
|
||||
--db-host=mysql --db-name=akaunting \
|
||||
--db-username=akaunting --db-password=akaunting123 \
|
||||
--db-prefix=a_ \
|
||||
--company-name="本地测试公司" --company-email=admin@company.com \
|
||||
--admin-email=admin@company.com --admin-password=admin123 \
|
||||
--locale=zh-CN
|
||||
```
|
||||
|
||||
## 部署中遇到并已解决的问题(备忘)
|
||||
1. **Docker Hub 国内访问**:daemon.json 配置 `https://docker.1ms.run` 镜像源
|
||||
2. **containerd snapshotter 导致 binary 全 0 字节**:在 Docker Desktop 设置里关闭 `UseContainerdSnapshotter`,改用 overlay2
|
||||
3. **apt 交互卡住**:Dockerfile 加 `DEBIAN_FRONTEND=noninteractive` + `/etc/apt/apt.conf.d/99force-conf`
|
||||
4. **deb.debian.org 不稳**:Dockerfile 切到清华 Debian 镜像
|
||||
5. **node-sass 需要 python/make/g++**:Dockerfile 补装 `python3 make g++`
|
||||
6. **npm run prod OOM**:改用 `npm run dev` + `NODE_OPTIONS=--max-old-space-size=2048`
|
||||
7. **ec_stores 索引名超长**:用 `--db-prefix=a_`(见上)
|
||||
23
LocalValetDriver.php
Normal file
23
LocalValetDriver.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Valet\Drivers\LaravelValetDriver;
|
||||
|
||||
class LocalValetDriver extends LaravelValetDriver
|
||||
{
|
||||
public function serves(string $sitePath, string $siteName, string $uri): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function frontControllerPath(string $sitePath, string $siteName, string $uri): string
|
||||
{
|
||||
return $sitePath.'/index.php';
|
||||
}
|
||||
|
||||
public function isStaticFile(string $sitePath, string $siteName, string $uri)
|
||||
{
|
||||
if (file_exists($sitePath.$uri)) {
|
||||
return $sitePath.$uri;
|
||||
}
|
||||
}
|
||||
}
|
||||
62
README.md
Normal file
62
README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Akaunting™
|
||||
|
||||
[](https://github.com/akaunting/akaunting/releases)
|
||||

|
||||
[](https://github.com/akaunting/akaunting/actions)
|
||||
|
||||
Online accounting software designed for small businesses and freelancers. Akaunting is built with modern technologies such as Laravel, VueJS, Tailwind, RESTful API etc. Thanks to its modular structure, Akaunting provides an awesome App Store for users and developers.
|
||||
|
||||
* [Home](https://akaunting.com) - The house of Akaunting
|
||||
* [Forum](https://akaunting.com/forum) - Ask for support
|
||||
* [Documentation](https://akaunting.com/hc/docs) - Learn how to use
|
||||
* [Developer Portal](https://developer.akaunting.com) - Generate passive income
|
||||
* [App Store](https://akaunting.com/apps) - Extend your Akaunting
|
||||
|
||||
## Requirements
|
||||
|
||||
* PHP 8.1 or higher
|
||||
* Database (e.g.: MariaDB, MySQL, PostgreSQL, SQLite)
|
||||
* Web Server (eg: Apache, Nginx, IIS)
|
||||
* [Other libraries](https://akaunting.com/hc/docs/on-premise/requirements/)
|
||||
|
||||
## Framework
|
||||
|
||||
Akaunting uses [Laravel](http://laravel.com), the best existing PHP framework, as the foundation framework and [Module](https://github.com/akaunting/module) package for Apps.
|
||||
|
||||
## Installation
|
||||
|
||||
* Clone the repository: `git clone https://github.com/akaunting/akaunting.git`
|
||||
* Install dependencies: `composer install ; npm install ; npm run dev`
|
||||
* Install Akaunting:
|
||||
|
||||
```bash
|
||||
php artisan install --db-name="akaunting" --db-username="root" --db-password="pass" --admin-email="admin@company.com" --admin-password="123456"
|
||||
```
|
||||
|
||||
* Create sample data (optional): `php artisan sample-data:seed`
|
||||
|
||||
## Contributing
|
||||
|
||||
Please, be very clear on your commit messages and Pull Requests, empty Pull Request messages may be rejected without reason.
|
||||
|
||||
When contributing code to Akaunting, you must follow the PSR coding standards. The golden rule is: Imitate the existing Akaunting code.
|
||||
|
||||
Please note that this project is released with a [Contributor Code of Conduct](https://akaunting.com/conduct). *By participating in this project you agree to abide by its terms*.
|
||||
|
||||
## Changelog
|
||||
|
||||
Please see [Releases](../../releases) for more information about what has changed recently.
|
||||
|
||||
## Security
|
||||
|
||||
Please review [our security policy](https://github.com/akaunting/akaunting/security/policy) on how to report security vulnerabilities.
|
||||
|
||||
## Credits
|
||||
|
||||
* [Denis Duliçi](https://github.com/denisdulici)
|
||||
* [Cüneyt Şentürk](https://github.com/cuneytsenturk)
|
||||
* [All Contributors](../../contributors)
|
||||
|
||||
## License
|
||||
|
||||
Akaunting is released under the [BSL license](LICENSE.txt).
|
||||
19
SECURITY.md
Normal file
19
SECURITY.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Security Policy
|
||||
|
||||
**PLEASE DON'T DISCLOSE SECURITY-RELATED ISSUES PUBLICLY, [SEE BELOW](#reporting-a-vulnerability).**
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Feel free to report security issues based on the following chart.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 1.2.x | :x: |
|
||||
| 1.3.x | :x: |
|
||||
| 2.0.x | :x: |
|
||||
| 2.1.x | :x: |
|
||||
| 3.0.x | :white_check_mark: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you discover any security related issues, please email security@akaunting.com instead of using the issue tracker. Ömer Çitak ([om3rcitak](https://github.com/om3rcitak)) is responsible of our security.
|
||||
328
app/Abstracts/BulkAction.php
Normal file
328
app/Abstracts/BulkAction.php
Normal file
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Jobs\Common\CreateMediableForDownload;
|
||||
use App\Jobs\Common\CreateZipForDownload;
|
||||
use App\Jobs\Common\DeleteContact;
|
||||
use App\Jobs\Common\UpdateContact;
|
||||
use App\Jobs\Banking\DeleteTransaction;
|
||||
use App\Traits\Jobs;
|
||||
use App\Traits\Relationships;
|
||||
use App\Traits\Translations;
|
||||
use App\Utilities\Export;
|
||||
use App\Utilities\Import;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Bus;
|
||||
use Throwable;
|
||||
|
||||
abstract class BulkAction
|
||||
{
|
||||
use Jobs, Relationships, Translations;
|
||||
|
||||
public $model = false;
|
||||
|
||||
public $actions = [
|
||||
'enable' => [
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-common-items',
|
||||
],
|
||||
'disable' => [
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-common-items',
|
||||
],
|
||||
'delete' => [
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-common-items',
|
||||
],
|
||||
'export' => [
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download'
|
||||
],
|
||||
'download' => [
|
||||
'name' => 'general.download',
|
||||
'message' => 'bulk_actions.message.download',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public $icons = [
|
||||
'enable' => 'check_circle',
|
||||
'disable' => 'hide_source',
|
||||
'delete' => 'delete',
|
||||
'duplicate' => 'file_copy',
|
||||
'export' => 'file_download',
|
||||
'download' => 'download',
|
||||
'reconcile' => 'published_with_changes',
|
||||
'unreconcile' => 'layers_clear',
|
||||
'received' => 'call_received',
|
||||
'cancelled' => 'cancel',
|
||||
'sent' => 'send',
|
||||
'approved' => 'approval',
|
||||
'refused' => 'do_not_disturb_on',
|
||||
'issued' => 'mark_email_read',
|
||||
'confirmed' => 'thumb_up_alt',
|
||||
];
|
||||
|
||||
public $messages = [
|
||||
'general' => 'bulk_actions.success.general',
|
||||
'enable' => 'messages.success.enabled',
|
||||
'disable' => 'messages.success.disabled',
|
||||
'delete' => 'messages.success.deleted',
|
||||
'duplicate' => 'messages.success.duplicated',
|
||||
'invite' => 'messages.success.invited',
|
||||
'end' => 'messages.success.ended',
|
||||
];
|
||||
|
||||
public function getSelectedRecords($request, $relationships = null)
|
||||
{
|
||||
if (empty($relationships)) {
|
||||
$model = $this->model::query();
|
||||
} else {
|
||||
$relationships = Arr::wrap($relationships);
|
||||
|
||||
$model = $this->model::with($relationships);
|
||||
}
|
||||
|
||||
return $model->find($this->getSelectedInput($request));
|
||||
}
|
||||
|
||||
public function getSelectedInput($request)
|
||||
{
|
||||
return $request->get('selected', []);
|
||||
}
|
||||
|
||||
public function getUpdateRequest($request)
|
||||
{
|
||||
foreach ($request->all() as $key => $value) {
|
||||
if (empty($value)) {
|
||||
unset($request[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response based on request type like HTML, JSON, or anything else.
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function response($view, $data = [])
|
||||
{
|
||||
$method = request()->get('handle', 'edit');
|
||||
|
||||
$handle = $this->actions[$method]['handle'] ?? 'update';
|
||||
$url = route('bulk-actions.action', $this->path);
|
||||
|
||||
$html = view('components.index.bulkaction.modal', [
|
||||
'url' => $url,
|
||||
'handle' => $handle,
|
||||
'selected' => $data['selected'] ?? $this->getSelectedInput(request()),
|
||||
'html' => view($view, $data)->render(),
|
||||
])->render();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'error' => false,
|
||||
'message' => '',
|
||||
'data' => [
|
||||
'title' => $this->findTranslation($this->text),
|
||||
'path' => $url,
|
||||
'handle' => $handle,
|
||||
],
|
||||
'html' => $html,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate the specified resource.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function duplicate($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->duplicate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable the specified resource.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function enable($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->enabled = true;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the specified resource.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function disable($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->enabled = false;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function delete($request)
|
||||
{
|
||||
$this->destroy($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function destroy($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$item->delete();
|
||||
}
|
||||
}
|
||||
|
||||
public function disableContacts($request)
|
||||
{
|
||||
$contacts = $this->getSelectedRecords($request, 'user');
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
try {
|
||||
$this->dispatch(new UpdateContact($contact, request()->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteContacts($request)
|
||||
{
|
||||
$contacts = $this->getSelectedRecords($request, 'user');
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
try {
|
||||
$this->dispatch(new DeleteContact($contact));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteTransactions($request)
|
||||
{
|
||||
$transactions = $this->getSelectedRecords($request, 'category');
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
try {
|
||||
$this->dispatch(new DeleteTransaction($transaction));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the excel file or catch errors
|
||||
*
|
||||
* @param $class
|
||||
* @param $request
|
||||
* @param $translation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function importExcel($class, $request, $translation)
|
||||
{
|
||||
return Import::fromExcel($class, $request, $translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the excel file or catch errors
|
||||
*
|
||||
* @param $class
|
||||
* @param $translation
|
||||
* @param $extension
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportExcel($class, $translation, $extension = 'xlsx')
|
||||
{
|
||||
return Export::toExcel($class, $translation, $extension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the pdf file or catch errors
|
||||
*
|
||||
* @param $class
|
||||
* @param $file_name
|
||||
* @param $translation
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function downloadPdf($selected, $class, $file_name, $translation)
|
||||
{
|
||||
try {
|
||||
if (should_queue()) {
|
||||
$batch[] = new CreateZipForDownload($selected, $class, $file_name);
|
||||
|
||||
$batch[] = new CreateMediableForDownload(user(), $file_name, $translation);
|
||||
|
||||
Bus::chain($batch)->onQueue('jobs')->dispatch();
|
||||
|
||||
$message = trans('messages.success.download_queued', ['type' => $translation]);
|
||||
|
||||
flash($message)->success();
|
||||
|
||||
return back();
|
||||
} else {
|
||||
$this->dispatch(new CreateZipForDownload($selected, $class, $file_name));
|
||||
|
||||
$folder_path = 'app' . DIRECTORY_SEPARATOR . 'temp' . DIRECTORY_SEPARATOR . company_id() . DIRECTORY_SEPARATOR . 'bulk_actions' . DIRECTORY_SEPARATOR;
|
||||
|
||||
return response()->download(get_storage_path($folder_path . $file_name . '.zip'))->deleteFileAfterSend(true);
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
report($e);
|
||||
flash($e->getMessage())->error()->important();
|
||||
|
||||
return back();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
app/Abstracts/Commands/Module.php
Normal file
86
app/Abstracts/Commands/Module.php
Normal file
@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Commands;
|
||||
|
||||
use App\Models\Module\Module as Model;
|
||||
use App\Models\Module\ModuleHistory as ModelHistory;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
|
||||
abstract class Module extends Command
|
||||
{
|
||||
public string $alias;
|
||||
|
||||
public int $company_id;
|
||||
|
||||
public string $locale;
|
||||
|
||||
public object|null $model;
|
||||
|
||||
public int|null $old_company_id;
|
||||
|
||||
protected function prepare()
|
||||
{
|
||||
$this->alias = Str::kebab($this->argument('alias'));
|
||||
$this->company_id = (int) $this->argument('company');
|
||||
$this->locale = $this->argument('locale');
|
||||
}
|
||||
|
||||
protected function changeRuntime()
|
||||
{
|
||||
$this->old_company_id = company_id();
|
||||
|
||||
company($this->company_id)->makeCurrent();
|
||||
|
||||
app()->setLocale($this->locale);
|
||||
|
||||
// Disable model cache
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
}
|
||||
|
||||
protected function revertRuntime()
|
||||
{
|
||||
if (empty($this->old_company_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
company($this->old_company_id)->makeCurrent();
|
||||
}
|
||||
|
||||
protected function getModel()
|
||||
{
|
||||
$this->model = Model::companyId($this->company_id)->alias($this->alias)->first();
|
||||
|
||||
return $this->model;
|
||||
}
|
||||
|
||||
protected function createHistory($action)
|
||||
{
|
||||
if (empty($this->model)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ModelHistory::create([
|
||||
'company_id' => $this->company_id,
|
||||
'module_id' => $this->model->id,
|
||||
'version' => module($this->alias)->get('version'),
|
||||
'description' => trans('modules.' . $action, ['module' => $this->alias]),
|
||||
'created_from' => source_name(),
|
||||
'created_by' => user_id(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command arguments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArguments()
|
||||
{
|
||||
return [
|
||||
['alias', InputArgument::REQUIRED, 'Module alias.'],
|
||||
['company', InputArgument::REQUIRED, 'Company ID.'],
|
||||
];
|
||||
}
|
||||
}
|
||||
12
app/Abstracts/Event.php
Normal file
12
app/Abstracts/Event.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class Event
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
}
|
||||
301
app/Abstracts/Export.php
Normal file
301
app/Abstracts/Export.php
Normal file
@ -0,0 +1,301 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use App\Events\Export\HeadingsPreparing;
|
||||
use App\Events\Export\RowsPreparing;
|
||||
use App\Notifications\Common\ExportFailed;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
use Illuminate\Support\Str;
|
||||
use Maatwebsite\Excel\Concerns\Exportable;
|
||||
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||
use Maatwebsite\Excel\Concerns\ShouldAutoSize;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\WithEvents;
|
||||
use Maatwebsite\Excel\Concerns\WithTitle;
|
||||
use Maatwebsite\Excel\Concerns\WithStrictNullComparison;
|
||||
use Maatwebsite\Excel\Events\AfterSheet;
|
||||
use Maatwebsite\Excel\Events\BeforeSheet;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
|
||||
|
||||
abstract class Export implements FromCollection, HasLocalePreference, ShouldAutoSize, ShouldQueue, WithHeadings, WithMapping, WithTitle, WithStrictNullComparison, WithEvents
|
||||
{
|
||||
use Exportable;
|
||||
|
||||
public $ids;
|
||||
|
||||
public $fields;
|
||||
|
||||
public $user;
|
||||
|
||||
public $request_class = null;
|
||||
|
||||
public $column_count; //number of columns to be auto sized
|
||||
|
||||
public $column_validations; //selects should have column_name and options
|
||||
|
||||
public $row_count; //number of rows that will have the dropdown
|
||||
|
||||
public function __construct($ids = null)
|
||||
{
|
||||
$this->ids = $ids;
|
||||
$this->fields = $this->fields();
|
||||
$this->column_validations = $this->columnValidations();
|
||||
$this->column_count = config('excel.exports.column_count');
|
||||
$this->row_count = config('excel.exports.row_count');
|
||||
$this->user = user();
|
||||
}
|
||||
|
||||
public function title(): string
|
||||
{
|
||||
return Str::snake((new \ReflectionClass($this))->getShortName());
|
||||
}
|
||||
|
||||
public function fields(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function map($model): array
|
||||
{
|
||||
$map = [];
|
||||
|
||||
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'transferred_at'];
|
||||
|
||||
$evil_chars = ['=', '+', '-', '@'];
|
||||
|
||||
foreach ($this->fields as $field) {
|
||||
$value = $model->$field;
|
||||
|
||||
// created_by is equal to the owner id. Therefore, the value in export is owner email.
|
||||
if ($field == 'created_by') {
|
||||
$value = $model->owner->email ?? null;
|
||||
}
|
||||
|
||||
if (in_array($field, $date_fields)) {
|
||||
$value = ExcelDate::PHPToExcel(Date::parse($value)->format('Y-m-d'));
|
||||
}
|
||||
|
||||
// Prevent CSV injection (OWASP CSV Injection)
|
||||
if (is_string($value)) {
|
||||
// Neutralize embedded newlines and tabs that could inject additional rows
|
||||
$value = str_replace(["\t", "\r", "\n"], ' ', $value);
|
||||
|
||||
// Prefix with apostrophe to neutralize formula characters,
|
||||
// including whitespace-prefix bypass (e.g., " =SUM(...)")
|
||||
if (Str::startsWith($value, $evil_chars) || Str::startsWith(ltrim($value), $evil_chars)) {
|
||||
$value = "'" . $value;
|
||||
}
|
||||
}
|
||||
|
||||
$map[] = $value;
|
||||
}
|
||||
|
||||
return $map;
|
||||
}
|
||||
|
||||
public function headings(): array
|
||||
{
|
||||
event(new HeadingsPreparing($this));
|
||||
|
||||
return $this->fields;
|
||||
}
|
||||
|
||||
public function prepareRows($rows)
|
||||
{
|
||||
event(new RowsPreparing($this, $rows));
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
public function preferredLocale()
|
||||
{
|
||||
if (! $this->user) {
|
||||
return setting('default.locale');
|
||||
}
|
||||
|
||||
return $this->user->locale;
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception): void
|
||||
{
|
||||
if (! $this->user) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->user->notify(new ExportFailed($exception->getMessage()));
|
||||
}
|
||||
|
||||
public function columnValidations(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function afterSheet($event)
|
||||
{
|
||||
$condition = class_exists($this->request_class)
|
||||
? ! ($request = new $this->request_class) instanceof FormRequest
|
||||
: true;
|
||||
|
||||
|
||||
if (empty($this->column_validations) && $condition) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$alphas = range('A', 'Z');
|
||||
|
||||
foreach ($this->fields as $key => $value) {
|
||||
$drop_column = $alphas[$key];
|
||||
|
||||
if ($this->setColumnValidations($drop_column, $event, $value)) {
|
||||
continue;
|
||||
};
|
||||
|
||||
$this->validationWarning($drop_column, $event, $value, $request);
|
||||
}
|
||||
}
|
||||
|
||||
public function setColumnValidations($drop_column, $event, $value)
|
||||
{
|
||||
if (! isset($this->column_validations[$value])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$column_validation = $this->column_validations[$value];
|
||||
|
||||
// set dropdown list for first data row
|
||||
$validation = $event->sheet->getCell("{$drop_column}2")->getDataValidation();
|
||||
|
||||
$validation->setType($column_validation['type'] ?? DataValidation::TYPE_LIST);
|
||||
|
||||
if (empty($column_validation['hide_prompt'])) {
|
||||
$validation->setAllowBlank($column_validation['allow_blank'] ?? false);
|
||||
$validation->setShowInputMessage($column_validation['show_input_message'] ?? true);
|
||||
$validation->setPromptTitle($column_validation['prompt_title'] ?? null);
|
||||
$validation->setPrompt($column_validation['prompt'] ?? null);
|
||||
}
|
||||
|
||||
if (empty($column_validation['hide_error'])) {
|
||||
$validation->setErrorStyle($column_validation['error_style'] ?? DataValidation::STYLE_INFORMATION);
|
||||
$validation->setShowErrorMessage($column_validation['show_error_message'] ?? true);
|
||||
$validation->setErrorTitle($column_validation['error_title'] ?? null);
|
||||
$validation->setError($column_validation['error'] ?? null);
|
||||
}
|
||||
|
||||
if (! empty($column_validation['options'])) {
|
||||
$validation->setFormula1(sprintf('"%s"', implode(',', $column_validation['options'])));
|
||||
$validation->setShowDropDown($column_validation['show_dropdown'] ?? true);
|
||||
}
|
||||
|
||||
// clone validation to remaining rows
|
||||
for ($i = 3; $i <= $this->row_count; $i++) {
|
||||
$event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation);
|
||||
}
|
||||
|
||||
// set columns to autosize
|
||||
for ($i = 1; $i <= $this->column_count; $i++) {
|
||||
$column = Coordinate::stringFromColumnIndex($i);
|
||||
$event->sheet->getColumnDimension($column)->setAutoSize(true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function validationWarning($drop_column, $event, $value, $request)
|
||||
{
|
||||
$rules = $this->prepareRules($request->rules());
|
||||
|
||||
if (! isset($rules[$value])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$rule = explode('|', $rules[$value]);
|
||||
|
||||
$prompt = '';
|
||||
|
||||
foreach ($rule as $r) {
|
||||
if (strpos($r, 'unique') !== false) {
|
||||
$r = 'unique';
|
||||
}
|
||||
|
||||
if (strpos($r, 'amount') !== false) {
|
||||
$r = 'double';
|
||||
}
|
||||
|
||||
if (strpos($r, 'date_format') !== false) {
|
||||
$prompt = $prompt . trans('validation.date_format', [
|
||||
'attribute' => $value,
|
||||
'format' => str_replace('date_format:', '', $r)
|
||||
]) . ' ';
|
||||
}
|
||||
|
||||
if (strpos($r, 'required_without') !== false) {
|
||||
$prompt = $prompt . trans('validation.required_without', [
|
||||
'attribute' => $value,
|
||||
'values' => str_replace('required_without:', '', $r)
|
||||
]) . ' ';
|
||||
}
|
||||
|
||||
if (in_array($r, ['required', 'email', 'integer', 'unique', 'date_format', 'double'])) {
|
||||
$prompt = $prompt . trans('validation.' . $r, ['attribute' => $value]) . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
$validation = $event->sheet->getCell("{$drop_column}2")->getDataValidation();
|
||||
|
||||
$validation->setAllowBlank(false);
|
||||
$validation->setShowInputMessage(true);
|
||||
$validation->setPromptTitle(trans('general.validation_warning'));
|
||||
$validation->setPrompt($prompt ?? null);
|
||||
|
||||
for ($i = 3; $i <= $this->row_count; $i++) {
|
||||
$event->sheet->getCell("{$drop_column}{$i}")->setDataValidation(clone $validation);
|
||||
}
|
||||
|
||||
for ($i = 1; $i <= $this->column_count; $i++) {
|
||||
$column = Coordinate::stringFromColumnIndex($i);
|
||||
$event->sheet->getColumnDimension($column)->setAutoSize(true);
|
||||
}
|
||||
}
|
||||
|
||||
public function getDropdownOptions($model, $select): array
|
||||
{
|
||||
$limit = 253;
|
||||
$selects = [];
|
||||
$totalLength = 0;
|
||||
|
||||
$model::select($select)->each(function ($row) use (&$selects, &$totalLength, $limit, $select) {
|
||||
$nameLength = mb_strlen($row->$select);
|
||||
|
||||
if ($totalLength + $nameLength <= $limit && $nameLength !== 0) {
|
||||
$selects[] = $row->$select;
|
||||
$totalLength += $nameLength;
|
||||
}
|
||||
});
|
||||
|
||||
return $selects;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can override this method to add custom rules for each row.
|
||||
*/
|
||||
public function prepareRules(array $rules): array
|
||||
{
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function registerEvents(): array
|
||||
{
|
||||
return [
|
||||
AfterSheet::class => function(AfterSheet $event) {
|
||||
$this->afterSheet($event);
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
76
app/Abstracts/Factory.php
Normal file
76
app/Abstracts/Factory.php
Normal file
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Models\Common\Company;
|
||||
use App\Traits\Jobs;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory as BaseFactory;
|
||||
use Illuminate\Database\Eloquent\Model as EloquentModel;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
abstract class Factory extends BaseFactory
|
||||
{
|
||||
use Jobs;
|
||||
|
||||
/**
|
||||
* @var Company
|
||||
*/
|
||||
protected $company;
|
||||
|
||||
/**
|
||||
* @var User|EloquentModel|object|null
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
public function __construct(...$arguments)
|
||||
{
|
||||
parent::__construct(...$arguments);
|
||||
|
||||
config(['mail.default' => 'array']);
|
||||
|
||||
// TODO: this location was put to make US | for "gmail.co.uk" issue
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
$this->setUser();
|
||||
|
||||
$this->setCompany();
|
||||
}
|
||||
|
||||
public function getCompanyUsers(): array
|
||||
{
|
||||
return $this->company->users()->enabled()->get()->pluck('id')->toArray();
|
||||
}
|
||||
|
||||
public function company(int $id): static
|
||||
{
|
||||
Cache::put('state_company_id', $id, Date::now()->addHour(6));
|
||||
|
||||
return $this->state([
|
||||
'company_id' => $id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function setUser(): void
|
||||
{
|
||||
$this->user = user_model_class()::first();
|
||||
}
|
||||
|
||||
public function setCompany(): void
|
||||
{
|
||||
$state_id = Cache::get('state_company_id');
|
||||
|
||||
$this->company = ! is_null($state_id) ? company($state_id) : $this->user->companies()->first();
|
||||
|
||||
$this->company->makeCurrent();
|
||||
|
||||
//app('url')->defaults(['company_id' => $this->company->id]);
|
||||
}
|
||||
|
||||
public function getRawAttribute($key)
|
||||
{
|
||||
$raw = $this->state([])->getExpandedAttributes(null);
|
||||
|
||||
return $raw[$key] ?? null;
|
||||
}
|
||||
}
|
||||
186
app/Abstracts/Http/ApiController.php
Normal file
186
app/Abstracts/Http/ApiController.php
Normal file
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use App\Traits\Jobs;
|
||||
use App\Traits\Permissions;
|
||||
use App\Traits\Relationships;
|
||||
use App\Exceptions\Http\Resource as ResourceException;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
|
||||
abstract class ApiController extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, Jobs, Permissions, Relationships, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->assignPermissionsToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the response for when a request fails validation.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param array $errors
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
protected function buildFailedValidationResponse(Request $request, array $errors)
|
||||
{
|
||||
if ($request->expectsJson()) {
|
||||
throw new ResourceException('Validation Error', $errors);
|
||||
}
|
||||
|
||||
return redirect()->to($this->getRedirectUrl())->withInput($request->input())->withErrors($errors, $this->errorBag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond with a location and a created resource.
|
||||
*
|
||||
* @param string $location
|
||||
* @param object $resource
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function created($location, $resource): JsonResponse
|
||||
{
|
||||
return $resource
|
||||
->response()
|
||||
->setStatusCode(201)
|
||||
->header('Location', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond with a location and an accepted resource.
|
||||
*
|
||||
* @param string $location
|
||||
* @param object $resource
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function accepted($location, $resource): JsonResponse
|
||||
{
|
||||
return $resource
|
||||
->response()
|
||||
->setStatusCode(202)
|
||||
->header('Location', $location);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond with empty content.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function noContent(): Response
|
||||
{
|
||||
return (new Response)
|
||||
->setStatusCode(204);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an error response.
|
||||
*
|
||||
* @param string $message
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function error($message, $statusCode)
|
||||
{
|
||||
throw new HttpException($statusCode, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 404 not found error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorNotFound($message = 'Not Found')
|
||||
{
|
||||
$this->error($message, 404);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 400 bad request error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorBadRequest($message = 'Bad Request')
|
||||
{
|
||||
$this->error($message, 400);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 403 forbidden error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorForbidden($message = 'Forbidden')
|
||||
{
|
||||
$this->error($message, 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 500 internal server error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorInternal($message = 'Internal Error')
|
||||
{
|
||||
$this->error($message, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 401 unauthorized error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorUnauthorized($message = 'Unauthorized')
|
||||
{
|
||||
$this->error($message, 401);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a 405 method not allowed error.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @throws \Symfony\Component\HttpKernel\Exception\HttpException
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function errorMethodNotAllowed($message = 'Method Not Allowed')
|
||||
{
|
||||
$this->error($message, 405);
|
||||
}
|
||||
}
|
||||
272
app/Abstracts/Http/Controller.php
Normal file
272
app/Abstracts/Http/Controller.php
Normal file
@ -0,0 +1,272 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use App\Abstracts\Http\Response;
|
||||
use App\Traits\Jobs;
|
||||
use App\Traits\Permissions;
|
||||
use App\Traits\Relationships;
|
||||
use App\Traits\SearchString;
|
||||
use App\Utilities\Export;
|
||||
use App\Utilities\Import;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Foundation\Validation\ValidatesRequests;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
|
||||
/**
|
||||
* @property string $type
|
||||
*/
|
||||
abstract class Controller extends BaseController
|
||||
{
|
||||
use AuthorizesRequests, Jobs, Permissions, Relationships, SearchString, ValidatesRequests;
|
||||
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->assignPermissionsToController();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a pagination collection.
|
||||
*
|
||||
* @param array|Collection $items
|
||||
* @param int $perPage
|
||||
* @param int $page
|
||||
* @param array $options
|
||||
*
|
||||
* @return LengthAwarePaginator
|
||||
*/
|
||||
public function paginate($items, $perPage = null, $page = null, $options = [])
|
||||
{
|
||||
$perPage = $perPage ?: (int) request('limit', setting('default.list_limit', '25'));
|
||||
|
||||
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
|
||||
|
||||
$items = $items instanceof Collection ? $items : Collection::make($items);
|
||||
|
||||
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response based on request type like HTML, JSON, or anything else.
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function response($view, $data = [])
|
||||
{
|
||||
$class_name = str_replace('Controllers', 'Responses', get_class($this));
|
||||
|
||||
if (class_exists($class_name)) {
|
||||
$response = new $class_name($view, $data);
|
||||
} else {
|
||||
$response = new class($view, $data) extends Response {};
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import the excel file or catch errors
|
||||
*
|
||||
* @param $class
|
||||
* @param $request
|
||||
* @param $translation
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function importExcel($class, $request, $translation)
|
||||
{
|
||||
return Import::fromExcel($class, $request, $translation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Export the excel file or catch errors
|
||||
*
|
||||
* @param $class
|
||||
* @param $translation
|
||||
* @param $extension
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function exportExcel($class, $translation, $extension = 'xlsx')
|
||||
{
|
||||
return Export::toExcel($class, $translation, $extension);
|
||||
}
|
||||
|
||||
public function setActiveTabForDocuments(): void
|
||||
{
|
||||
// Added this method to set the active tab for documents
|
||||
if (! request()->has('list_records') && ! request()->has('search')) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && ! empty($tab_pins[$this->type])) {
|
||||
$data = config('type.document.' . $this->type . '.route.params.' . $tab_pins[$this->type]);
|
||||
|
||||
if (! empty($data)) {
|
||||
request()->merge($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$status = $this->getSearchStringValue('status');
|
||||
|
||||
if (empty($status)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins[$this->type] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$status = $this->getSearchStringValue('status');
|
||||
|
||||
if (empty($status)) {
|
||||
$search = config('type.document.' . $this->type . '.route.params.unpaid.search');
|
||||
|
||||
request()->offsetSet('search', $search);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
} else {
|
||||
$unpaid = str_replace('status:', '', config('type.document.' . $this->type . '.route.params.unpaid.search'));
|
||||
$draft = str_replace('status:', '', config('type.document.' . $this->type . '.route.params.draft.search'));
|
||||
|
||||
if (($status == $unpaid) || ($status == $draft)) {
|
||||
return;
|
||||
}
|
||||
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
|
||||
public function setActiveTabForTransactions(): void
|
||||
{
|
||||
// Added this method to set the active tab for transactions
|
||||
if (! request()->has('list_records') && ! request()->has('search')) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && ! empty($tab_pins['transactions'])) {
|
||||
$data = config('type.transaction.transactions.route.params.' . $tab_pins['transactions']);
|
||||
|
||||
if (! empty($data)) {
|
||||
request()->merge($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins['transactions'] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$search = config('type.transaction.transactions.route.params.income.search');
|
||||
|
||||
request()->offsetSet('search', $search);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
} else {
|
||||
$income = str_replace('type:', 'income', config('type.transaction.transactions.route.params.income.search'));
|
||||
$expense = str_replace('type:', 'expense', config('type.transaction.transactions.route.params.expense.search'));
|
||||
|
||||
if (($type == $income) || ($type == $expense)) {
|
||||
return;
|
||||
}
|
||||
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
|
||||
public function setActiveTabForCategories(): void
|
||||
{
|
||||
if (! request()->has('list_records') && ! request()->has('search')) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && ! empty($tab_pins['categories'])) {
|
||||
$tab = $tab_pins['categories'];
|
||||
|
||||
if (! empty($tab)) {
|
||||
|
||||
request()->offsetSet('list_records', $tab);
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (! request()->has('list_records') && request()->has('search')) {
|
||||
$type = $this->getSearchStringValue('type');
|
||||
|
||||
if (empty($type)) {
|
||||
$tab_pins = setting('favorites.tab.' . user()->id, []);
|
||||
$tab_pins = ! empty($tab_pins) ? json_decode($tab_pins, true) : [];
|
||||
|
||||
if (! empty($tab_pins) && (($tab_pins['categories'] ?? null) === 'all')) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return;
|
||||
}
|
||||
|
||||
$types = $this->getSearchStringValue('type');
|
||||
|
||||
if (!empty($types)) {
|
||||
$types = is_string($types) ? explode(',', $types) : $types;
|
||||
|
||||
$tab = config('type.category.' . $types[0] . '.group') ? config('type.category.' . $types[0] . '.group') : 'all';
|
||||
|
||||
if (!empty($types) && count($types) > 0) {
|
||||
request()->offsetSet('list_records', $tab);
|
||||
|
||||
$currentSearch = request('search', '');
|
||||
|
||||
$searchParts = array_filter(explode(' ', $currentSearch), function($part) {
|
||||
return !empty(trim($part)) && !str_starts_with(trim($part), 'type:');
|
||||
});
|
||||
|
||||
$searchParts[] = 'type:' . implode(',', $types);
|
||||
|
||||
request()->offsetSet('search', implode(' ', $searchParts));
|
||||
request()->offsetSet('programmatic', '1');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($tab)) {
|
||||
request()->offsetSet('list_records', 'all');
|
||||
request()->offsetSet('programmatic', '1');
|
||||
}
|
||||
}
|
||||
}
|
||||
20
app/Abstracts/Http/FormRequest.php
Normal file
20
app/Abstracts/Http/FormRequest.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use Illuminate\Foundation\Http\FormRequest as BaseFormRequest;
|
||||
|
||||
abstract class FormRequest extends BaseFormRequest
|
||||
{
|
||||
/**
|
||||
* Prepare the data for validation.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
$this->merge([
|
||||
'company_id' => company_id(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
209
app/Abstracts/Http/PaymentController.php
Normal file
209
app/Abstracts/Http/PaymentController.php
Normal file
@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use App\Events\Document\PaymentReceived;
|
||||
use App\Http\Requests\Portal\InvoicePayment as PaymentRequest;
|
||||
use App\Models\Document\Document;
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Monolog\Logger;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
|
||||
abstract class PaymentController extends BaseController
|
||||
{
|
||||
public $alias = '';
|
||||
|
||||
public $type = ''; // hosted, redirect
|
||||
|
||||
public $setting = [];
|
||||
|
||||
public $logger = null;
|
||||
|
||||
public $user = null;
|
||||
|
||||
public $module = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->middleware(function ($request, $next) {
|
||||
$this->setting = setting($this->alias);
|
||||
$this->setting['code'] = $this->alias;
|
||||
$this->setting['language'] = app()->getLocale();
|
||||
|
||||
$this->logger = $this->getLogger();
|
||||
|
||||
$this->user = user();
|
||||
|
||||
$this->module = module($this->alias);
|
||||
|
||||
return $next($request);
|
||||
});
|
||||
}
|
||||
|
||||
public function show(Document $invoice, PaymentRequest $request, $cards = [])
|
||||
{
|
||||
$this->setContactFirstLastName($invoice);
|
||||
|
||||
$confirm_url = $this->getConfirmUrl($invoice);
|
||||
|
||||
$html = view('components.payment_method.' . $this->type, [
|
||||
'setting' => $this->setting,
|
||||
'invoice' => $invoice,
|
||||
'confirm_url' => $confirm_url,
|
||||
'store_card' => !empty($this->setting['store_card']) ? true : false,
|
||||
'cards' => $cards,
|
||||
])->render();
|
||||
|
||||
return response()->json([
|
||||
'code' => $this->setting['code'],
|
||||
'name' => $this->setting['name'],
|
||||
'description' => trans($this->alias . '::general.description'),
|
||||
'redirect' => false,
|
||||
'html' => $html,
|
||||
]);
|
||||
}
|
||||
|
||||
public function signed(Document $invoice, PaymentRequest $request)
|
||||
{
|
||||
return $this->show($invoice, $request);
|
||||
}
|
||||
|
||||
public function cancel(Document $invoice, $force_redirect = false)
|
||||
{
|
||||
$message = trans('messages.warning.payment_cancel', ['method' => setting($this->alias . '.name')]);
|
||||
|
||||
$this->logger->info($this->module->getName() . ':: Invoice: ' . $invoice->id . ' - Cancel Message: ' . $message);
|
||||
|
||||
flash($message)->warning()->important();
|
||||
|
||||
$invoice_url = $this->getInvoiceUrl($invoice);
|
||||
|
||||
if ($force_redirect || ($this->type == 'redirect')) {
|
||||
return redirect($invoice_url);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => $message,
|
||||
'redirect' => $invoice_url,
|
||||
'success' => false,
|
||||
'data' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function finish($invoice, $request, $force_redirect = false)
|
||||
{
|
||||
$this->dispatchPaidEvent($invoice, $request);
|
||||
|
||||
$this->forgetReference($invoice);
|
||||
|
||||
$message = trans('messages.success.added', ['type' => trans_choice('general.payments', 1)]);
|
||||
|
||||
$this->logger->info($this->module->getName() . ':: Invoice: ' . $invoice->id . ' - Success Message: ' . $message);
|
||||
|
||||
flash($message)->success();
|
||||
|
||||
$finish_url = $this->getFinishUrl($invoice);
|
||||
|
||||
if ($force_redirect || ($this->type == 'redirect')) {
|
||||
return redirect($finish_url);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'error' => $message,
|
||||
'redirect' => $finish_url,
|
||||
'success' => true,
|
||||
'data' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getInvoiceUrl($invoice)
|
||||
{
|
||||
return request()->isPortal($invoice->company_id)
|
||||
? route('portal.invoices.show', $invoice->id)
|
||||
: URL::signedRoute('signed.invoices.show', [$invoice->id]);
|
||||
}
|
||||
|
||||
public function getFinishUrl($invoice)
|
||||
{
|
||||
return request()->isPortal($invoice->company_id)
|
||||
? route('portal.invoices.finish', $invoice->id)
|
||||
: URL::signedRoute('signed.invoices.finish', [$invoice->id]);
|
||||
}
|
||||
|
||||
public function getConfirmUrl($invoice)
|
||||
{
|
||||
return $this->getModuleUrl($invoice, 'confirm');
|
||||
}
|
||||
|
||||
public function getReturnUrl($invoice)
|
||||
{
|
||||
return $this->getModuleUrl($invoice, 'return');
|
||||
}
|
||||
|
||||
public function getCancelUrl($invoice)
|
||||
{
|
||||
return $this->getModuleUrl($invoice, 'cancel');
|
||||
}
|
||||
|
||||
public function getNotifyUrl($invoice)
|
||||
{
|
||||
return route('portal.' . $this->alias . '.invoices.notify', $invoice->id);
|
||||
}
|
||||
|
||||
public function getModuleUrl($invoice, $suffix)
|
||||
{
|
||||
return request()->isPortal($invoice->company_id)
|
||||
? route('portal.' . $this->alias . '.invoices.' . $suffix, $invoice->id)
|
||||
: URL::signedRoute('signed.' . $this->alias . '.invoices.' . $suffix, [$invoice->id]);
|
||||
}
|
||||
|
||||
public function getLogger()
|
||||
{
|
||||
$log = new Logger($this->alias);
|
||||
$log->pushHandler(new StreamHandler(Storage::path('logs/' . $this->alias . '.log')), Logger::INFO);
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
public function dispatchPaidEvent($invoice, $request)
|
||||
{
|
||||
$request['company_id'] = $invoice->company_id;
|
||||
$request['account_id'] = setting($this->alias . '.account_id', setting('default.account'));
|
||||
$request['amount'] = $invoice->amount;
|
||||
$request['payment_method'] = $this->alias;
|
||||
$request['reference'] = $this->getReference($invoice);
|
||||
$request['type'] = 'income';
|
||||
|
||||
event(new PaymentReceived($invoice, $request));
|
||||
}
|
||||
|
||||
public function setReference($invoice, $reference)
|
||||
{
|
||||
session([
|
||||
$this->alias . '_' . $invoice->id . '_reference' => $reference
|
||||
]);
|
||||
}
|
||||
|
||||
public function getReference($invoice)
|
||||
{
|
||||
return session($this->alias . '_' . $invoice->id . '_reference');
|
||||
}
|
||||
|
||||
public function forgetReference($invoice)
|
||||
{
|
||||
session()->forget($this->alias . '_' . $invoice->id . '_reference');
|
||||
}
|
||||
|
||||
public function setContactFirstLastName(&$invoice)
|
||||
{
|
||||
$contact = explode(" ", $invoice->contact_name);
|
||||
|
||||
$last_name = array_pop($contact);
|
||||
$first_name = implode(" ", $contact);
|
||||
|
||||
$invoice->first_name = $first_name;
|
||||
$invoice->last_name = $last_name;
|
||||
}
|
||||
}
|
||||
55
app/Abstracts/Http/Response.php
Normal file
55
app/Abstracts/Http/Response.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use Illuminate\Contracts\Support\Responsable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class Response implements Responsable
|
||||
{
|
||||
protected $accepts = ['json', 'rss'];
|
||||
|
||||
protected $view;
|
||||
|
||||
protected $data;
|
||||
|
||||
public function __construct($view, $data)
|
||||
{
|
||||
$this->view = $view;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
public function toJson()
|
||||
{
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'error' => false,
|
||||
'data' => Arr::first($this->data),
|
||||
'message' => '',
|
||||
]);
|
||||
}
|
||||
|
||||
public function toHtml()
|
||||
{
|
||||
return view($this->view, $this->data);
|
||||
}
|
||||
|
||||
public function toResponse($request)
|
||||
{
|
||||
foreach ($this->accepts as $accept) {
|
||||
$request_method = 'expects' . Str::studly($accept);
|
||||
$response_method = 'to' . Str::studly($accept);
|
||||
|
||||
if (!method_exists($request, $request_method) || !method_exists($this, $response_method)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($request->{$request_method}()) {
|
||||
return $this->{$response_method}();
|
||||
}
|
||||
}
|
||||
|
||||
return $this->toHtml();
|
||||
}
|
||||
}
|
||||
174
app/Abstracts/Http/SettingController.php
Normal file
174
app/Abstracts/Http/SettingController.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Http;
|
||||
|
||||
use App\Abstracts\Http\Controller;
|
||||
use App\Events\Setting\SettingUpdated;
|
||||
use App\Events\Setting\SettingUpdating;
|
||||
use App\Http\Requests\Setting\Setting as Request;
|
||||
use App\Models\Common\Company;
|
||||
use App\Models\Setting\Currency;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Uploads;
|
||||
use App\Utilities\Installer;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class SettingController extends Controller
|
||||
{
|
||||
use DateTime, Uploads;
|
||||
|
||||
public $redirect_route = '';
|
||||
|
||||
public $skip_keys = ['company_id', '_method', '_token', '_prefix'];
|
||||
|
||||
public $file_keys = ['company.logo', 'invoice.logo'];
|
||||
|
||||
public $uploaded_file_keys = ['company.uploaded_logo', 'invoice.uploaded_logo'];
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param Request $request
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function update(Request $request)
|
||||
{
|
||||
$fields = $request->all();
|
||||
$prefix = $request->get('_prefix', 'general');
|
||||
$company_id = $request->get('company_id');
|
||||
|
||||
event(new SettingUpdating($request));
|
||||
|
||||
if (empty($company_id)) {
|
||||
$company_id = company_id();
|
||||
}
|
||||
|
||||
$company = Company::find($company_id);
|
||||
|
||||
$total_companies = Company::count();
|
||||
|
||||
// Clear setting media
|
||||
foreach ($this->file_keys as $file_key) {
|
||||
$keys = explode('.', $file_key);
|
||||
|
||||
if ($prefix != $keys[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! setting($file_key, false)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_old_key = 'uploaded_' . $keys[1];
|
||||
if (array_key_exists($file_old_key, $fields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
setting()->forget($file_key);
|
||||
}
|
||||
|
||||
foreach ($fields as $key => $value) {
|
||||
$real_key = $prefix . '.' . $key;
|
||||
|
||||
// Don't process unwanted keys
|
||||
if (in_array($key, $this->skip_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// change dropzone middleware already uploaded file
|
||||
if (in_array($real_key, $this->uploaded_file_keys)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Process file uploads
|
||||
if (in_array($real_key, $this->file_keys)) {
|
||||
// Upload attachment
|
||||
if ($request->file($key)) {
|
||||
$media = $this->getMedia($request->file($key), 'settings');
|
||||
|
||||
$company->attachMedia($media, Str::snake($real_key));
|
||||
|
||||
$value = $media->id;
|
||||
}
|
||||
|
||||
// Prevent reset
|
||||
if (empty($value)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($real_key == 'default.locale') {
|
||||
if (! in_array($value, config('language.allowed'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
user()->setAttribute('locale', $value)->save();
|
||||
}
|
||||
|
||||
if ($real_key == 'default.currency') {
|
||||
$currencies = Currency::enabled()->pluck('code')->toArray();
|
||||
|
||||
if (! in_array($value, $currencies)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$currency = Currency::code($value)->first();
|
||||
$currency->rate = '1';
|
||||
$currency->save();
|
||||
}
|
||||
|
||||
// If only 1 company
|
||||
if ($total_companies == 1) {
|
||||
$this->oneCompany($real_key, $value);
|
||||
}
|
||||
|
||||
setting()->set($real_key, $value);
|
||||
}
|
||||
|
||||
// Save all settings
|
||||
setting()->save();
|
||||
|
||||
event(new SettingUpdated($request));
|
||||
|
||||
$message = trans('messages.success.updated', ['type' => trans_choice('general.settings', 2)]);
|
||||
|
||||
$redirect_url = !empty($this->redirect_route) ? route($this->redirect_route) : url()->previous();
|
||||
|
||||
$response = [
|
||||
'status' => null,
|
||||
'success' => true,
|
||||
'error' => false,
|
||||
'message' => $message,
|
||||
'data' => null,
|
||||
'redirect' => $redirect_url,
|
||||
];
|
||||
|
||||
flash($message)->success();
|
||||
|
||||
return response()->json($response);
|
||||
}
|
||||
|
||||
protected function oneCompany($real_key, $value): void
|
||||
{
|
||||
$key = match($real_key) {
|
||||
'default.locale' => 'APP_LOCALE',
|
||||
'schedule.time' => 'APP_SCHEDULE_TIME',
|
||||
'company.name' => 'MAIL_FROM_NAME',
|
||||
'company.email' => 'MAIL_FROM_ADDRESS',
|
||||
'email.protocol' => 'MAIL_MAILER',
|
||||
'email.smtp_host' => 'MAIL_HOST',
|
||||
'email.smtp_port' => 'MAIL_PORT',
|
||||
'email.smtp_username' => 'MAIL_USERNAME',
|
||||
'email.smtp_password' => 'MAIL_PASSWORD',
|
||||
'email.smtp_encryption' => 'MAIL_ENCRYPTION',
|
||||
default => '',
|
||||
};
|
||||
|
||||
if (empty($key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Installer::updateEnv([$key => '"' . $value . '"']);
|
||||
}
|
||||
}
|
||||
276
app/Abstracts/Import.php
Normal file
276
app/Abstracts/Import.php
Normal file
@ -0,0 +1,276 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use App\Events\Import\RowPreparing;
|
||||
use App\Traits\Import as ImportHelper;
|
||||
use App\Traits\Sources;
|
||||
use App\Utilities\Date;
|
||||
use Carbon\Exceptions\InvalidFormatException;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Maatwebsite\Excel\Concerns\Importable;
|
||||
use Maatwebsite\Excel\Concerns\SkipsEmptyRows;
|
||||
use Maatwebsite\Excel\Concerns\ToModel;
|
||||
use Maatwebsite\Excel\Concerns\WithChunkReading;
|
||||
use Maatwebsite\Excel\Concerns\WithHeadingRow;
|
||||
use Maatwebsite\Excel\Concerns\WithLimit;
|
||||
use Maatwebsite\Excel\Concerns\WithMapping;
|
||||
use Maatwebsite\Excel\Concerns\WithValidation;
|
||||
use PhpOffice\PhpSpreadsheet\Shared\Date as ExcelDate;
|
||||
|
||||
abstract class Import implements HasLocalePreference, ShouldQueue, SkipsEmptyRows, WithChunkReading, WithHeadingRow, WithLimit, WithMapping, WithValidation, ToModel
|
||||
{
|
||||
use Importable, ImportHelper, Sources;
|
||||
|
||||
public $user;
|
||||
|
||||
public $model;
|
||||
|
||||
public $request_class = null;
|
||||
|
||||
public $with_trashed = false;
|
||||
|
||||
public $columns = [];
|
||||
|
||||
public $has_row = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->user = user();
|
||||
}
|
||||
|
||||
public function map($row): array
|
||||
{
|
||||
$row['company_id'] = company_id();
|
||||
|
||||
// created_by is equal to the owner id. Therefore, the value in export is owner email.
|
||||
$row['created_by'] = $this->getCreatedById($row);
|
||||
|
||||
$row['created_from'] = $this->getSourcePrefix() . 'import';
|
||||
|
||||
// Make enabled field integer
|
||||
if (isset($row['enabled'])) {
|
||||
$row['enabled'] = (int) $row['enabled'];
|
||||
}
|
||||
|
||||
// Make reconciled field integer
|
||||
if (isset($row['reconciled'])) {
|
||||
$row['reconciled'] = (int) $row['reconciled'];
|
||||
}
|
||||
|
||||
$date_fields = ['paid_at', 'invoiced_at', 'billed_at', 'due_at', 'issued_at', 'transferred_at'];
|
||||
foreach ($date_fields as $date_field) {
|
||||
if (!isset($row[$date_field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$row[$date_field] = is_numeric($row[$date_field])
|
||||
? Date::parse(ExcelDate::excelToDateTimeObject($row[$date_field]))
|
||||
->format('Y-m-d H:i:s')
|
||||
: Date::parseWithFallbackLocales($row[$date_field], null, [$this->preferredLocale()])
|
||||
->format('Y-m-d H:i:s');
|
||||
} catch (InvalidFormatException | \Exception $e) {
|
||||
Log::info($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$event = new RowPreparing($this, $row);
|
||||
|
||||
event($event);
|
||||
|
||||
return $event->row;
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* You can override this method to add custom rules for each row.
|
||||
*/
|
||||
public function prepareRules(array $rules): array
|
||||
{
|
||||
return $rules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate each row data.
|
||||
*
|
||||
* @param \Illuminate\Validation\Validator $validator
|
||||
* @throws ValidationException
|
||||
*/
|
||||
public function withValidator($validator)
|
||||
{
|
||||
$condition = class_exists($this->request_class)
|
||||
? ! ($request = new $this->request_class) instanceof FormRequest
|
||||
: true;
|
||||
|
||||
if ($condition) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($validator->getData() as $row => $data) {
|
||||
$request->initialize(request: $data);
|
||||
|
||||
$rules = $this->prepareRules($request->rules());
|
||||
|
||||
try {
|
||||
Validator::make($data, $rules)->validate();
|
||||
} catch (ValidationException $e) {
|
||||
foreach ($e->validator->failed() as $attribute => $value) {
|
||||
foreach ($value as $rule => $params) {
|
||||
if ($rule === 'In' && !empty($params)) {
|
||||
$actual_value = $data[$attribute] ?? 'null';
|
||||
$expected_values = implode(', ', $params);
|
||||
$custom_message = trans('validation.in_detailed', [
|
||||
'attribute' => $attribute,
|
||||
'value' => $actual_value,
|
||||
'values' => $expected_values
|
||||
]);
|
||||
$validator->errors()->add($row . '.' . $attribute, $custom_message);
|
||||
} else {
|
||||
$validator->addFailure($row . '.' . $attribute, $rule, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new ValidationException($validator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function chunkSize(): int
|
||||
{
|
||||
return config('excel.imports.chunk_size');
|
||||
}
|
||||
|
||||
public function limit(): int
|
||||
{
|
||||
return config('excel.imports.row_limit');
|
||||
}
|
||||
|
||||
public function isNotValid($row)
|
||||
{
|
||||
return Validator::make($row, $this->rules())->fails();
|
||||
}
|
||||
|
||||
public function isEmpty($row, $fields)
|
||||
{
|
||||
$fields = Arr::wrap($fields);
|
||||
|
||||
foreach ($fields as $field) {
|
||||
if (!empty($row[$field])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function preferredLocale()
|
||||
{
|
||||
return $this->user->locale;
|
||||
}
|
||||
|
||||
protected function replaceForBatchRules(array $rules): array
|
||||
{
|
||||
$dependent_rules = [
|
||||
'after:',
|
||||
'after_or_equal:',
|
||||
'before:',
|
||||
'before_or_equal:',
|
||||
'different:',
|
||||
'exclude_if:',
|
||||
'exclude_unless:',
|
||||
'gt:',
|
||||
'gte:',
|
||||
'in_array:',
|
||||
'lt:',
|
||||
'lte:',
|
||||
'prohibited_if:',
|
||||
'prohibited_unless:',
|
||||
'required_if:',
|
||||
'required_unless:',
|
||||
'required_with:',
|
||||
'required_with_all:',
|
||||
'required_without:',
|
||||
'required_without_all:',
|
||||
'same:',
|
||||
];
|
||||
|
||||
$batch_rules = [
|
||||
'after:*.',
|
||||
'after_or_equal:*.',
|
||||
'before:*.',
|
||||
'before_or_equal:*.',
|
||||
'different:*.',
|
||||
'exclude_if:*.',
|
||||
'exclude_unless:*.',
|
||||
'gt:*.',
|
||||
'gte:*.',
|
||||
'in_array:*.',
|
||||
'lt:*.',
|
||||
'lte:*.',
|
||||
'prohibited_if:*.',
|
||||
'prohibited_unless:*.',
|
||||
'required_if:*.',
|
||||
'required_unless:*.',
|
||||
'required_with:*.',
|
||||
'required_with_all:*.',
|
||||
'required_without:*.',
|
||||
'required_without_all:*.',
|
||||
'same:*.',
|
||||
];
|
||||
|
||||
return str_replace($dependent_rules, $batch_rules, $rules);
|
||||
}
|
||||
|
||||
//This function is used in import classes. If the data in the row exists in the database, it is returned.
|
||||
public function hasRow($row)
|
||||
{
|
||||
// must be models and columns
|
||||
if (empty($this->model) || empty($this->columns)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* This function is called for each row.
|
||||
This check is done in order not to query again for each row.
|
||||
When the model to which the query is thrown changes, the new query should be discarded.
|
||||
*/
|
||||
if (! $this->has_row || ! $this->has_row instanceof $this->model) {
|
||||
$this->has_row = $this->model::withoutEvents(function () {
|
||||
if ($this->with_trashed) {
|
||||
// This query should be used if there is no deleted_at field in the table or if the deleted data is to be retrieved.
|
||||
return $this->model::withTrashed()->get($this->columns)->each(function ($data) {
|
||||
$data->setAppends([]);
|
||||
$data->unsetRelations();
|
||||
});
|
||||
} else {
|
||||
return $this->model::get($this->columns)->each(function ($data) {
|
||||
$data->setAppends([]);
|
||||
$data->unsetRelations();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$search_value = [];
|
||||
|
||||
//In the model, the fields to be searched for the row are determined.
|
||||
foreach ($this->columns as $key) {
|
||||
$search_value[$key] = isset($row[$key]) ? $row[$key] : null;
|
||||
}
|
||||
|
||||
return in_array($search_value, $this->has_row->toArray());
|
||||
}
|
||||
}
|
||||
31
app/Abstracts/ImportMultipleSheets.php
Normal file
31
app/Abstracts/ImportMultipleSheets.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Maatwebsite\Excel\Concerns\Importable;
|
||||
use Maatwebsite\Excel\Concerns\WithChunkReading;
|
||||
use Maatwebsite\Excel\Concerns\SkipsUnknownSheets;
|
||||
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
|
||||
|
||||
abstract class ImportMultipleSheets implements ShouldQueue, WithChunkReading, WithMultipleSheets, SkipsUnknownSheets
|
||||
{
|
||||
use Importable;
|
||||
|
||||
public $user;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->user = user();
|
||||
}
|
||||
|
||||
public function chunkSize(): int
|
||||
{
|
||||
return config('excel.imports.chunk_size');
|
||||
}
|
||||
|
||||
public function onUnknownSheet($sheetName)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
136
app/Abstracts/Job.php
Normal file
136
app/Abstracts/Job.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use App\Interfaces\Job\HasOwner;
|
||||
use App\Interfaces\Job\HasSource;
|
||||
use App\Interfaces\Job\ShouldCreate;
|
||||
use App\Interfaces\Job\ShouldDelete;
|
||||
use App\Interfaces\Job\ShouldUpdate;
|
||||
use App\Traits\Jobs;
|
||||
use App\Traits\Relationships;
|
||||
use App\Traits\Sources;
|
||||
use App\Traits\Uploads;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
abstract class Job
|
||||
{
|
||||
use Jobs, Relationships, Sources, Uploads;
|
||||
|
||||
protected $model;
|
||||
|
||||
protected $request;
|
||||
|
||||
public function __construct(...$arguments)
|
||||
{
|
||||
$this->booting(...$arguments);
|
||||
$this->bootCreate(...$arguments);
|
||||
$this->bootUpdate(...$arguments);
|
||||
$this->bootDelete(...$arguments);
|
||||
$this->booted(...$arguments);
|
||||
}
|
||||
|
||||
public function booting(...$arguments): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function bootCreate(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldCreate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($arguments[0])) {
|
||||
$arguments[0] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[0]);
|
||||
if ($request instanceof Request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
if ($this instanceof HasOwner) {
|
||||
$this->setOwner();
|
||||
}
|
||||
|
||||
if ($this instanceof HasSource) {
|
||||
$this->setSource();
|
||||
}
|
||||
}
|
||||
|
||||
public function bootUpdate(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[0] instanceof Model) {
|
||||
$this->model = $arguments[0];
|
||||
}
|
||||
|
||||
if (empty($arguments[1])) {
|
||||
$arguments[1] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[1]);
|
||||
if ($request instanceof Request) {
|
||||
$this->request = $request;
|
||||
}
|
||||
}
|
||||
|
||||
public function bootDelete(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[0] instanceof Model) {
|
||||
$this->model = $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function booted(...$arguments): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function getRequestInstance($request)
|
||||
{
|
||||
if (! is_array($request)) {
|
||||
return $request;
|
||||
}
|
||||
|
||||
$class = new class() extends FormRequest {};
|
||||
|
||||
return $class->merge($request);
|
||||
}
|
||||
|
||||
public function setOwner(): void
|
||||
{
|
||||
if (! $this->request instanceof Request) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->request->has('created_by')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request->merge(['created_by' => user_id()]);
|
||||
}
|
||||
|
||||
public function setSource(): void
|
||||
{
|
||||
if (! $this->request instanceof Request) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->request->has('created_from')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request->merge(['created_from' => $this->getSourceName($this->request)]);
|
||||
}
|
||||
}
|
||||
161
app/Abstracts/JobShouldQueue.php
Normal file
161
app/Abstracts/JobShouldQueue.php
Normal file
@ -0,0 +1,161 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use App\Interfaces\Job\HasOwner;
|
||||
use App\Interfaces\Job\HasSource;
|
||||
use App\Interfaces\Job\ShouldCreate;
|
||||
use App\Interfaces\Job\ShouldDelete;
|
||||
use App\Interfaces\Job\ShouldUpdate;
|
||||
use App\Traits\Jobs;
|
||||
use App\Traits\Relationships;
|
||||
use App\Traits\Sources;
|
||||
use App\Traits\Uploads;
|
||||
use App\Utilities\QueueCollection;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class JobShouldQueue implements ShouldQueue
|
||||
{
|
||||
use InteractsWithQueue, Jobs, Queueable, Relationships, SerializesModels, Sources, Uploads;
|
||||
|
||||
protected $model;
|
||||
|
||||
protected $request;
|
||||
|
||||
public function __construct(...$arguments)
|
||||
{
|
||||
$this->booting(...$arguments);
|
||||
$this->bootCreate(...$arguments);
|
||||
$this->bootUpdate(...$arguments);
|
||||
$this->bootDelete(...$arguments);
|
||||
$this->booted(...$arguments);
|
||||
}
|
||||
|
||||
public function booting(...$arguments): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
public function bootCreate(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldCreate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($arguments[0])) {
|
||||
$arguments[0] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[0]);
|
||||
if ($request instanceof QueueCollection) {
|
||||
$this->request = $request;
|
||||
}
|
||||
|
||||
if ($this instanceof HasOwner) {
|
||||
$this->setOwner();
|
||||
}
|
||||
|
||||
if ($this instanceof HasSource) {
|
||||
$this->setSource();
|
||||
}
|
||||
}
|
||||
|
||||
public function bootUpdate(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[0] instanceof Model) {
|
||||
$this->model = $arguments[0];
|
||||
}
|
||||
|
||||
if (empty($arguments[1])) {
|
||||
$arguments[1] = [];
|
||||
}
|
||||
|
||||
$request = $this->getRequestInstance($arguments[1]);
|
||||
if ($request instanceof QueueCollection) {
|
||||
$this->request = $request;
|
||||
}
|
||||
}
|
||||
|
||||
public function bootDelete(...$arguments): void
|
||||
{
|
||||
if (! $this instanceof ShouldDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($arguments[0] instanceof Model) {
|
||||
$this->model = $arguments[0];
|
||||
}
|
||||
}
|
||||
|
||||
public function booted(...$arguments): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if request is array and if so, convert to a request class.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return \Illuminate\Foundation\Http\FormRequest
|
||||
*
|
||||
* @deprecated Request is not serializable so can't use it with queues.
|
||||
*/
|
||||
public function getRequestInstance($request)
|
||||
{
|
||||
return $this->getRequestAsCollection($request);
|
||||
}
|
||||
|
||||
/**
|
||||
* Covert the request to collection.
|
||||
*
|
||||
* @param mixed $request
|
||||
* @return \App\Utilities\QueueCollection
|
||||
*/
|
||||
public function getRequestAsCollection($request)
|
||||
{
|
||||
if (is_array($request)) {
|
||||
$data = $request;
|
||||
|
||||
$request = new class() extends FormRequest {};
|
||||
|
||||
$request->merge($data);
|
||||
}
|
||||
|
||||
return new QueueCollection($request->all());
|
||||
}
|
||||
|
||||
public function setOwner(): void
|
||||
{
|
||||
if (! $this->request instanceof QueueCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->request->has('created_by')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request->merge(['created_by' => user_id()]);
|
||||
}
|
||||
|
||||
public function setSource(): void
|
||||
{
|
||||
if (! $this->request instanceof QueueCollection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->request->has('created_from')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->request->merge(['created_from' => $this->getSourceName($this->request)]);
|
||||
}
|
||||
}
|
||||
363
app/Abstracts/Listeners/Report.php
Normal file
363
app/Abstracts/Listeners/Report.php
Normal file
@ -0,0 +1,363 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Listeners;
|
||||
|
||||
use App\Models\Banking\Account;
|
||||
use App\Models\Common\Contact;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories;
|
||||
use App\Traits\Contacts;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\SearchString;
|
||||
|
||||
abstract class Report
|
||||
{
|
||||
use Categories, Contacts, DateTime, SearchString;
|
||||
|
||||
protected $classes = [];
|
||||
|
||||
protected $events = [
|
||||
\App\Events\Report\FilterShowing::class,
|
||||
\App\Events\Report\FilterApplying::class,
|
||||
\App\Events\Report\GroupShowing::class,
|
||||
\App\Events\Report\GroupApplying::class,
|
||||
\App\Events\Report\RowsShowing::class,
|
||||
];
|
||||
|
||||
// Prevent deprecated dynamic property warnings in PHP 8.2+ by explicitly declaring these properties
|
||||
protected $class;
|
||||
protected $group;
|
||||
|
||||
public function skipThisClass($event)
|
||||
{
|
||||
$fire_event = $event;
|
||||
|
||||
$this->fireEvent(\App\Events\Report\SkipClass::class, $fire_event);
|
||||
|
||||
return (empty($event->class) || !in_array(get_class($event->class), $this->classes));
|
||||
}
|
||||
|
||||
public function skipRowsShowing($event, $group)
|
||||
{
|
||||
$fire_event = $event;
|
||||
$fire_group = $group;
|
||||
|
||||
$this->fireEvent(\App\Events\Report\SkipRowsShowing::class, $fire_event, $fire_group);
|
||||
|
||||
return $this->skipThisClass($event)
|
||||
|| empty($event->class->getGroup())
|
||||
|| ($event->class->getGroup() != $group);
|
||||
}
|
||||
|
||||
public function setDateFilter($event)
|
||||
{
|
||||
$financial_year = $this->getFinancialYear();
|
||||
|
||||
$start_date = request()->get('start_date', $financial_year->copy()->getStartDate()->toDateString());
|
||||
$end_date = request()->get('end_date', $financial_year->copy()->getEndDate()->toDateString());
|
||||
$default_value = $start_date . '-to-' . $end_date;
|
||||
|
||||
$event->class->filters['date_range'] = $this->getDateRange();
|
||||
$event->class->filters['keys']['date_range'] = 'date_range';
|
||||
$event->class->filters['defaults']['date_range'] = $default_value;
|
||||
$event->class->filters['operators']['date_range'] = [
|
||||
'equal' => true,
|
||||
'not_equal' => false,
|
||||
'range' => false,
|
||||
];
|
||||
}
|
||||
|
||||
public function getDateRange()
|
||||
{
|
||||
$date_range = [];
|
||||
|
||||
$shortcuts = $this->getDatePickerShortcuts();
|
||||
|
||||
foreach ($shortcuts as $text => $shortcut) {
|
||||
$date_range[$shortcut['start'] . '-to-' . $shortcut['end']] = $text;
|
||||
}
|
||||
|
||||
$date_range['custom'] = trans('general.date_range.custom');
|
||||
|
||||
return $date_range;
|
||||
}
|
||||
|
||||
public function getAccounts($limit = false)
|
||||
{
|
||||
$model = Account::enabled()->orderBy('name');
|
||||
|
||||
if ($limit !== false) {
|
||||
$model->take(setting('default.select_limit'));
|
||||
}
|
||||
|
||||
return $model->pluck('name', 'id')->toArray();
|
||||
}
|
||||
|
||||
public function getItemCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories($this->getItemCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories($this->getIncomeCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getExpenseCategories($limit = false)
|
||||
{
|
||||
return $this->getCategories($this->getExpenseCategoryTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getIncomeExpenseCategories($limit = false)
|
||||
{
|
||||
$types = array_merge($this->getIncomeCategoryTypes(), $this->getExpenseAndDirectCostCategoryTypes());
|
||||
|
||||
return $this->getCategories($types, $limit);
|
||||
}
|
||||
|
||||
public function getCategories($types, $limit = false)
|
||||
{
|
||||
$model = Category::withSubCategory()->type($types)->orderBy('name');
|
||||
|
||||
if ($limit !== false) {
|
||||
$model->take(setting('default.select_limit'));
|
||||
}
|
||||
|
||||
return $model->pluck('name', 'id')->toArray();
|
||||
}
|
||||
|
||||
public function getCustomers($limit = false)
|
||||
{
|
||||
return $this->getContacts($this->getCustomerTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getVendors($limit = false)
|
||||
{
|
||||
return $this->getContacts($this->getVendorTypes(), $limit);
|
||||
}
|
||||
|
||||
public function getContacts($types = null, $limit = false)
|
||||
{
|
||||
if ($types) {
|
||||
$model = Contact::type($types)->orderBy('name');
|
||||
} else {
|
||||
$model = Contact::orderBy('name');
|
||||
}
|
||||
|
||||
if ($limit !== false) {
|
||||
$model->take(setting('default.select_limit'));
|
||||
}
|
||||
|
||||
return $model->pluck('name', 'id')->toArray();
|
||||
}
|
||||
|
||||
public function getBasis()
|
||||
{
|
||||
return [
|
||||
'cash' => trans('general.cash'),
|
||||
'accrual' => trans('general.accrual'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getPeriod()
|
||||
{
|
||||
return [
|
||||
'weekly' => trans('general.weekly'),
|
||||
'monthly' => trans('general.monthly'),
|
||||
'quarterly' => trans('general.quarterly'),
|
||||
'yearly' => trans('general.yearly'),
|
||||
];
|
||||
}
|
||||
|
||||
public function getDiscount()
|
||||
{
|
||||
return [
|
||||
'item' => trans('settings.localisation.discount_location.item'),
|
||||
'total' => trans('settings.localisation.discount_location.total'),
|
||||
'both' => trans('settings.localisation.discount_location.both'),
|
||||
];
|
||||
}
|
||||
|
||||
public function applyDateFilter($event)
|
||||
{
|
||||
$event->model->dateFilter($event->args['date_field']);
|
||||
}
|
||||
|
||||
public function applyDiscountFilter($event)
|
||||
{
|
||||
$input = request('search', '');
|
||||
$discount = $this->getSearchStringValue('discount', 'both', $input);
|
||||
|
||||
switch ($discount) {
|
||||
case 'item':
|
||||
$discount_types = ['item_discount'];
|
||||
break;
|
||||
case 'total':
|
||||
$discount_types = ['discount'];
|
||||
break;
|
||||
default:
|
||||
$discount_types = ['item_discount', 'discount'];
|
||||
break;
|
||||
}
|
||||
|
||||
$event->model->whereHas('totals', function ($query) use ($discount_types) {
|
||||
$query->whereIn('code', $discount_types);
|
||||
});
|
||||
}
|
||||
|
||||
public function applySearchStringFilter($event)
|
||||
{
|
||||
$event->model->usingSearchString();
|
||||
}
|
||||
|
||||
public function applyAccountGroup($event)
|
||||
{
|
||||
if ($event->model->getTable() != 'documents') {
|
||||
return;
|
||||
}
|
||||
|
||||
$filter = explode(',', $this->getSearchStringValue('account_id'));
|
||||
|
||||
$event->model->account_id = 0;
|
||||
|
||||
foreach ($event->model->transactions as $transaction) {
|
||||
if (!empty($filter) && !in_array($transaction->account_id, $filter)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$event->model->account_id = $transaction->account_id;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyCustomerGroup($event)
|
||||
{
|
||||
foreach ($this->getCustomerTypes() as $type) {
|
||||
$id_field = $type . '_id';
|
||||
|
||||
$event->model->$id_field = $event->model->contact_id;
|
||||
}
|
||||
}
|
||||
|
||||
public function applyVendorGroup($event)
|
||||
{
|
||||
foreach ($this->getVendorTypes() as $type) {
|
||||
$id_field = $type . '_id';
|
||||
|
||||
$event->model->$id_field = $event->model->contact_id;
|
||||
}
|
||||
}
|
||||
|
||||
public function setRowNamesAndValues($event, $rows)
|
||||
{
|
||||
$nodes = [];
|
||||
|
||||
foreach ($event->class->dates as $date) {
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
foreach ($rows as $id => $name) {
|
||||
$event->class->row_names[$table_key][$id] = $name;
|
||||
$event->class->row_values[$table_key][$id][$date] = 0;
|
||||
|
||||
$nodes[$id] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setTreeNodes($event, $nodes);
|
||||
}
|
||||
|
||||
public function setTreeNodes($event, $nodes)
|
||||
{
|
||||
foreach ($event->class->tables as $table_key => $table_name) {
|
||||
foreach ($nodes as $id => $node) {
|
||||
$event->class->row_tree_nodes[$table_key][$id] = $node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getCategoriesNodes($categories)
|
||||
{
|
||||
$nodes = [];
|
||||
|
||||
foreach ($categories as $id => $name) {
|
||||
$category = Category::withSubCategory()->find($id);
|
||||
|
||||
if (!is_null($category->parent_id)) {
|
||||
unset($categories[$id]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$nodes[$id] = $this->getSubCategories($category);
|
||||
}
|
||||
|
||||
return $nodes;
|
||||
}
|
||||
|
||||
public function getSubCategories($category)
|
||||
{
|
||||
if ($category->sub_categories->count() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$sub_categories = [];
|
||||
|
||||
foreach ($category->sub_categories as $sub_category) {
|
||||
$sub_category->load('sub_categories');
|
||||
|
||||
$sub_categories[$sub_category->id] = $this->getSubCategories($sub_category);
|
||||
}
|
||||
|
||||
if (!empty($sub_categories)) {
|
||||
$sub_categories[$category->id] = null;
|
||||
}
|
||||
|
||||
return $sub_categories;
|
||||
}
|
||||
|
||||
public function getFormattedDate($event, $date)
|
||||
{
|
||||
$period = $this->getSearchStringValue('period');
|
||||
|
||||
if (empty($period)) {
|
||||
$period = $event->class->getPeriod();
|
||||
}
|
||||
|
||||
return $this->getPeriodicDate($date, $period, $event->class->year);
|
||||
}
|
||||
|
||||
protected function fireEvent($event_class, $event, $group = null)
|
||||
{
|
||||
$this->class = $event->class;
|
||||
|
||||
if ($group) {
|
||||
$this->group = $group;
|
||||
}
|
||||
|
||||
event(new $event_class($this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the listeners for the subscriber.
|
||||
*
|
||||
* @param \Illuminate\Events\Dispatcher $events
|
||||
*/
|
||||
public function subscribe($events)
|
||||
{
|
||||
$class = get_class($this);
|
||||
|
||||
foreach ($this->events as $event) {
|
||||
$method = 'handle' . (new \ReflectionClass($event))->getShortName();
|
||||
|
||||
if (!method_exists($class, $method)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$events->listen(
|
||||
$event,
|
||||
$class . '@' . $method
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
36
app/Abstracts/Listeners/Update.php
Normal file
36
app/Abstracts/Listeners/Update.php
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\Listeners;
|
||||
|
||||
use App\Utilities\Versions;
|
||||
|
||||
abstract class Update
|
||||
{
|
||||
const ALIAS = '';
|
||||
|
||||
const VERSION = '';
|
||||
|
||||
/**
|
||||
* Check the fired update based on alias and version.
|
||||
*
|
||||
* @param $event
|
||||
* @return boolean
|
||||
*/
|
||||
public function skipThisUpdate($event)
|
||||
{
|
||||
// Apply only to the specified alias
|
||||
if ($event->alias != static::ALIAS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ! Versions::shouldUpdate(static::VERSION, $event->old, $event->new);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2.0
|
||||
*/
|
||||
public function check($event)
|
||||
{
|
||||
return !$this->skipThisUpdate($event);
|
||||
}
|
||||
}
|
||||
269
app/Abstracts/Model.php
Normal file
269
app/Abstracts/Model.php
Normal file
@ -0,0 +1,269 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use Akaunting\Sortable\Traits\Sortable;
|
||||
use App\Events\Common\SearchStringApplied;
|
||||
use App\Events\Common\SearchStringApplying;
|
||||
use App\Interfaces\Export\WithParentSheet;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Owners;
|
||||
use App\Traits\SearchString;
|
||||
use App\Traits\Sources;
|
||||
use App\Traits\Tenants;
|
||||
use GeneaLabs\LaravelModelCaching\Traits\Cachable;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Laratrust\Contracts\Ownable;
|
||||
use Lorisleiva\LaravelSearchString\Concerns\SearchString as LaravelSearchString;
|
||||
|
||||
abstract class Model extends Eloquent implements Ownable
|
||||
{
|
||||
use Cachable, DateTime, LaravelSearchString, Owners, SearchString, SoftDeletes, Sortable, Sources, Tenants;
|
||||
|
||||
protected $tenantable = true;
|
||||
|
||||
protected $casts = [
|
||||
'amount' => 'double',
|
||||
'enabled' => 'boolean',
|
||||
'deleted_at' => 'datetime',
|
||||
];
|
||||
|
||||
public $allAttributes = [];
|
||||
|
||||
/**
|
||||
* Fill the model with an array of attributes.
|
||||
*
|
||||
* @param array $attributes
|
||||
* @return $this
|
||||
*
|
||||
* @throws \Illuminate\Database\Eloquent\MassAssignmentException
|
||||
*/
|
||||
public function fill(array $attributes)
|
||||
{
|
||||
$this->allAttributes = $attributes;
|
||||
|
||||
return parent::fill($attributes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Global company relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo('App\Models\Common\Company');
|
||||
}
|
||||
|
||||
/**
|
||||
* Owner relation.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function owner()
|
||||
{
|
||||
return $this->belongsTo(user_model_class(), 'created_by', 'id')->withDefault(['name' => trans('general.na')]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include company data.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeAllCompanies($query)
|
||||
{
|
||||
return $query->withoutGlobalScope('App\Scopes\Company');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include company data.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $company_id
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeCompanyId($query, $company_id)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('company_id'), '=', $company_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to get all rows filtered, sorted and paginated.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $sort
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeCollect($query, $sort = 'name')
|
||||
{
|
||||
$request = request();
|
||||
|
||||
/**
|
||||
* Modules that use the sort parameter in CRUD operations cause an error,
|
||||
* so this sort parameter set back to old value after the query is executed.
|
||||
*
|
||||
* for Custom Fields module
|
||||
*/
|
||||
$request_sort = $request->get('sort');
|
||||
|
||||
$query->usingSearchString()->sortable($sort);
|
||||
|
||||
if ($request->expectsJson() && $request->isNotApi()) {
|
||||
return $query->get();
|
||||
}
|
||||
|
||||
$request->merge(['sort' => $request_sort]);
|
||||
// This line disabled because broken sortable issue.
|
||||
//$request->offsetUnset('direction');
|
||||
$limit = (int) $request->get('limit', setting('default.list_limit', '25'));
|
||||
|
||||
return $query->paginate($limit);
|
||||
}
|
||||
|
||||
public function scopeUsingSearchString(Builder $query, string|null $string = null)
|
||||
{
|
||||
event(new SearchStringApplying($query));
|
||||
|
||||
$string = $string ?: request('search', '');
|
||||
|
||||
$string = $this->stripUnknownSearchStringTokens($string, get_class($this));
|
||||
|
||||
$this->getSearchStringManager()->updateBuilder($query, $string);
|
||||
|
||||
event(new SearchStringApplied($query));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to export the rows of the current page filtered and sorted.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $ids
|
||||
* @param $sort
|
||||
* @param $id_field
|
||||
*
|
||||
* @return \Illuminate\Support\LazyCollection
|
||||
*/
|
||||
public function scopeCollectForExport($query, $ids = [], $sort = 'name', $id_field = 'id')
|
||||
{
|
||||
$request = request();
|
||||
|
||||
if (!empty($ids)) {
|
||||
$query->whereIn($id_field, (array) $ids);
|
||||
}
|
||||
|
||||
$search = $request->get('search');
|
||||
|
||||
$query->usingSearchString($search)->sortable($sort);
|
||||
|
||||
$page = (int) $request->get('page');
|
||||
$limit = (int) $request->get('limit', setting('default.list_limit', '25'));
|
||||
$offset = $page ? ($page - 1) * $limit : 0;
|
||||
|
||||
if (! $this instanceof WithParentSheet && (empty($ids) || count((array) $ids) > $limit)) {
|
||||
$query->offset($offset)->limit($limit);
|
||||
}
|
||||
|
||||
return $query->cursor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include active models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeEnabled($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('enabled'), 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include passive models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeDisabled($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('enabled'), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scope to only include reconciled models.
|
||||
*
|
||||
* @param \Illuminate\Database\Eloquent\Builder $query
|
||||
* @param $value
|
||||
* @return \Illuminate\Database\Eloquent\Builder
|
||||
*/
|
||||
public function scopeReconciled($query, $value = 1)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('reconciled'), $value);
|
||||
}
|
||||
|
||||
public function scopeAccount($query, $accounts)
|
||||
{
|
||||
if (empty($accounts)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->whereIn($this->qualifyColumn('account_id'), (array) $accounts);
|
||||
}
|
||||
|
||||
public function scopeContact($query, $contacts)
|
||||
{
|
||||
if (empty($contacts)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $query->whereIn($this->qualifyColumn('contact_id'), (array) $contacts);
|
||||
}
|
||||
|
||||
public function scopeSource($query, $source)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('created_from'), $source);
|
||||
}
|
||||
|
||||
public function scopeIsOwner($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('created_by'), user_id());
|
||||
}
|
||||
|
||||
public function scopeIsNotOwner($query)
|
||||
{
|
||||
return $query->where($this->qualifyColumn('created_by'), '<>', user_id());
|
||||
}
|
||||
|
||||
public function scopeIsRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'like', '%-recurring');
|
||||
}
|
||||
|
||||
public function scopeIsNotRecurring(Builder $query): Builder
|
||||
{
|
||||
return $query->where($this->qualifyColumn('type'), 'not like', '%-recurring');
|
||||
}
|
||||
|
||||
public function scopeModuleEnabled(Builder $query, string $module): Builder
|
||||
{
|
||||
return $query->allCompanies()->whereHas('company', fn (Builder $q1) =>
|
||||
$q1->enabled()->whereHas('modules', fn (Builder $q2) =>
|
||||
$q2->allCompanies()->alias($module)->enabled(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public function ownerKey($owner)
|
||||
{
|
||||
if ($this->isNotOwnable()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $this->created_by;
|
||||
}
|
||||
}
|
||||
179
app/Abstracts/Notification.php
Normal file
179
app/Abstracts/Notification.php
Normal file
@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Models\Setting\EmailTemplate;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification as BaseNotification;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class Notification extends BaseNotification implements ShouldQueue
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Custom mail subject, body, etc.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $custom_mail;
|
||||
|
||||
/**
|
||||
* Create a notification instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->onQueue('notifications');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array|string
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['mail', 'database'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the mail representation of the notification.
|
||||
*
|
||||
*/
|
||||
public function initMailMessage(): MailMessage
|
||||
{
|
||||
app('url')->defaults(['company_id' => company_id()]);
|
||||
|
||||
$message = (new MailMessage)
|
||||
->from(config('mail.from.address'), config('mail.from.name'))
|
||||
->subject($this->getSubject())
|
||||
->view('components.email.body', ['body' => $this->getBody()]);
|
||||
|
||||
if (!empty($this->custom_mail['cc'])) {
|
||||
$message->cc($this->custom_mail['cc']);
|
||||
}
|
||||
|
||||
if (!empty($this->custom_mail['bcc'])) {
|
||||
$message->bcc($this->custom_mail['bcc']);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the array representation of the notification.
|
||||
*
|
||||
*/
|
||||
public function initArrayMessage(): void
|
||||
{
|
||||
app('url')->defaults(['company_id' => company_id()]);
|
||||
}
|
||||
|
||||
public function getSubject(): string
|
||||
{
|
||||
return !empty($this->custom_mail['subject'])
|
||||
? $this->custom_mail['subject']
|
||||
: $this->replaceTags($this->template->subject);
|
||||
}
|
||||
|
||||
public function getBody()
|
||||
{
|
||||
$body = !empty($this->custom_mail['body'])
|
||||
? EmailTemplate::sanitizeBody($this->custom_mail['body'])
|
||||
: $this->replaceTags($this->template->body);
|
||||
|
||||
return $body . $this->getFooter();
|
||||
}
|
||||
|
||||
public function replaceTags(string $content): string
|
||||
{
|
||||
$pattern = $this->getTagsPattern();
|
||||
$replacement = $this->applyQuote($this->getTagsReplacement());
|
||||
|
||||
return $this->revertQuote(preg_replace($pattern, $replacement, $content));
|
||||
}
|
||||
|
||||
public function getFooter()
|
||||
{
|
||||
$url = null;
|
||||
$get_started = '<span style="color: #676ba2;">' . trans('footer.get_started') . '</span>';
|
||||
|
||||
return view('components.email.footer', compact('url', 'get_started'));
|
||||
}
|
||||
|
||||
public function getTagsPattern(): array
|
||||
{
|
||||
$pattern = [];
|
||||
|
||||
foreach($this->getTags() as $tag) {
|
||||
$pattern[] = "/" . $tag . "/";
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
public function getTags(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTagsReplacement(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTagsBinding(): array
|
||||
{
|
||||
$bindings = [];
|
||||
|
||||
$tags = $this->getTags();
|
||||
$replacements = $this->getTagsReplacement();
|
||||
|
||||
$wrappers = ['{', '}'];
|
||||
|
||||
foreach ($tags as $index => $tag) {
|
||||
$key = Str::replace($wrappers, '', $tag);
|
||||
|
||||
$value = $replacements[$index] ?? '';
|
||||
|
||||
// HTML-encode string replacement values to prevent stored XSS (CWE-79).
|
||||
// Notification description templates contain intentional HTML markup
|
||||
// (<strong>, <a>) which must be preserved. This encodes only the
|
||||
// user-supplied values (e.g. customer_name, vendor_name, email)
|
||||
// while leaving the template's own markup untouched. Non-string
|
||||
// values (Money, Carbon dates, numeric amounts) pass through
|
||||
// unchanged since they do not carry attacker-controlled HTML.
|
||||
$bindings[$key] = is_string($value) ? e($value) : $value;
|
||||
}
|
||||
|
||||
return $bindings;
|
||||
}
|
||||
|
||||
public function applyQuote(array $vars): array
|
||||
{
|
||||
$new_vars = [];
|
||||
|
||||
foreach ($vars as $var) {
|
||||
// Ensure $var is a string, default to an empty string if it is null
|
||||
$new_vars[] = preg_quote($var ?? '');
|
||||
}
|
||||
|
||||
return $new_vars;
|
||||
}
|
||||
|
||||
public function revertQuote(string $content): string
|
||||
{
|
||||
return str_replace('\\', '', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 3.0
|
||||
*/
|
||||
public function initMessage()
|
||||
{
|
||||
return $this->initMailMessage();
|
||||
}
|
||||
}
|
||||
10
app/Abstracts/Observer.php
Normal file
10
app/Abstracts/Observer.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use Akaunting\MutableObserver\Traits\Mutable;
|
||||
|
||||
abstract class Observer
|
||||
{
|
||||
use Mutable;
|
||||
}
|
||||
741
app/Abstracts/Report.php
Normal file
741
app/Abstracts/Report.php
Normal file
@ -0,0 +1,741 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use Akaunting\Apexcharts\Chart;
|
||||
use App\Events\Report\DataLoaded;
|
||||
use App\Events\Report\DataLoading;
|
||||
use App\Events\Report\FilterApplying;
|
||||
use App\Events\Report\FilterShowing;
|
||||
use App\Events\Report\GroupApplying;
|
||||
use App\Events\Report\GroupShowing;
|
||||
use App\Events\Report\RowsShowing;
|
||||
use App\Events\Report\TotalCalculating;
|
||||
use App\Events\Report\TotalCalculated;
|
||||
use App\Exports\Common\Reports as Export;
|
||||
use App\Models\Common\Report as Model;
|
||||
use App\Models\Document\Document;
|
||||
use App\Models\Document\DocumentItem;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Charts;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\SearchString;
|
||||
use App\Traits\Translations;
|
||||
use App\Utilities\Date;
|
||||
use App\Utilities\Export as ExportHelper;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class Report
|
||||
{
|
||||
use Charts, DateTime, SearchString, Translations;
|
||||
|
||||
public $model;
|
||||
|
||||
public $default_name = '';
|
||||
|
||||
public $category = 'reports.income_expense';
|
||||
|
||||
public $icon = 'donut_small';
|
||||
|
||||
public $type = 'detail';
|
||||
|
||||
public $has_money = true;
|
||||
|
||||
public $group;
|
||||
|
||||
public $groups = [];
|
||||
|
||||
public $year;
|
||||
|
||||
public $views = [];
|
||||
|
||||
public $tables = [];
|
||||
|
||||
public $dates = [];
|
||||
|
||||
public $row_names = [];
|
||||
|
||||
public $row_values = [];
|
||||
|
||||
public $footer_totals = [];
|
||||
|
||||
public $filters = [];
|
||||
|
||||
public $loaded = false;
|
||||
|
||||
public $bar_formatter_type = 'money';
|
||||
|
||||
public $donut_formatter_type = 'percent';
|
||||
|
||||
public $chart = [
|
||||
'bar' => [
|
||||
'colors' => [
|
||||
'#6da252',
|
||||
],
|
||||
|
||||
'yaxis' => [
|
||||
'labels' => [
|
||||
'formatter' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
'donut' => [
|
||||
'yaxis' => [
|
||||
'labels' => [
|
||||
'formatter' => '',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
public $column_name_width = 'report-column-name';
|
||||
public $column_value_width = 'report-column-value';
|
||||
|
||||
public $row_tree_nodes = [];
|
||||
|
||||
public function __construct(Model $model = null, $load_data = true)
|
||||
{
|
||||
$this->setGroups();
|
||||
|
||||
if (!$model) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->model = $model;
|
||||
|
||||
if (!$load_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->load();
|
||||
}
|
||||
|
||||
abstract public function setData();
|
||||
|
||||
public function load()
|
||||
{
|
||||
$this->setYear();
|
||||
$this->setViews();
|
||||
$this->setTables();
|
||||
$this->setDates();
|
||||
$this->setFilters();
|
||||
$this->setRows();
|
||||
$this->loadData();
|
||||
$this->setColumnWidth();
|
||||
$this->setChartLabelFormatter();
|
||||
|
||||
$this->loaded = true;
|
||||
}
|
||||
|
||||
public function loadData()
|
||||
{
|
||||
event(new DataLoading($this));
|
||||
|
||||
$this->setData();
|
||||
|
||||
event(new DataLoaded($this));
|
||||
}
|
||||
|
||||
public function getDefaultName()
|
||||
{
|
||||
if (!empty($this->default_name)) {
|
||||
return trans($this->default_name);
|
||||
}
|
||||
|
||||
return Str::title(str_replace('_', ' ', Str::snake((new \ReflectionClass($this))->getShortName())));
|
||||
}
|
||||
|
||||
public function getCategory()
|
||||
{
|
||||
return trans($this->category);
|
||||
}
|
||||
|
||||
public function getCategoryDescription()
|
||||
{
|
||||
if (! empty($this->category_description)) {
|
||||
return trans($this->category_description);
|
||||
}
|
||||
|
||||
return $this->findTranslation([
|
||||
$this->category . '_desc',
|
||||
$this->category . '_description',
|
||||
str_replace('general.', 'reports.', $this->category) . '_desc',
|
||||
str_replace('general.', 'reports.', $this->category) . '_description',
|
||||
]);
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return $this->icon;
|
||||
}
|
||||
|
||||
public function getCharts($table_key)
|
||||
{
|
||||
return [
|
||||
'bar' => $this->getBarChart($table_key),
|
||||
'donut' => $this->getDonutChart($table_key),
|
||||
];
|
||||
}
|
||||
|
||||
public function getBarChart($table_key)
|
||||
{
|
||||
$chart = new Chart();
|
||||
|
||||
if (empty($this->chart)) {
|
||||
return $chart;
|
||||
}
|
||||
|
||||
$options = !empty($this->chart[$table_key]) ? $this->chart[$table_key]['bar'] : $this->chart['bar'];
|
||||
|
||||
$chart->setType('bar')
|
||||
->setOptions($options)
|
||||
->setDefaultLocale($this->getDefaultLocaleOfChart())
|
||||
->setLocales($this->getLocaleTranslationOfChart())
|
||||
->setLabels(array_values($this->dates))
|
||||
->setDataset($this->tables[$table_key], 'column', array_values($this->footer_totals[$table_key]));
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
public function getDonutChart($table_key)
|
||||
{
|
||||
$chart = new Chart();
|
||||
|
||||
if (empty($this->chart)) {
|
||||
return $chart;
|
||||
}
|
||||
|
||||
$tmp_values = [];
|
||||
|
||||
if (! empty($this->row_values[$table_key])) {
|
||||
foreach ($this->row_values[$table_key] as $id => $dates) {
|
||||
$tmp_values[$id] = 0;
|
||||
|
||||
foreach ($dates as $date) {
|
||||
$tmp_values[$id] += $date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tmp_values = collect($tmp_values)->sort()->reverse()->take(10)->all();
|
||||
|
||||
$total = array_sum($tmp_values);
|
||||
$total = !empty($total) ? $total : 1;
|
||||
|
||||
$group = $this->getGroup();
|
||||
|
||||
$labels = $colors = $values = [];
|
||||
|
||||
foreach ($tmp_values as $id => $value) {
|
||||
$labels[$id] = $this->row_names[$table_key][$id];
|
||||
|
||||
$colors[$id] = ($group == 'category')
|
||||
? Category::withSubCategory()->find($id)?->colorHexCode
|
||||
: $this->randHexColor();
|
||||
|
||||
$values[$id] = round(($value * 100 / $total), 0);
|
||||
}
|
||||
|
||||
$options = !empty($this->chart[$table_key]) ? $this->chart[$table_key]['donut'] : $this->chart['donut'];
|
||||
|
||||
$chart->setType('donut')
|
||||
->setOptions($options)
|
||||
->setDefaultLocale($this->getDefaultLocaleOfChart())
|
||||
->setLocales($this->getLocaleTranslationOfChart())
|
||||
->setLabels(array_values($labels))
|
||||
->setColors(array_values($colors))
|
||||
->setDataset($this->tables[$table_key], 'donut', array_values($values));
|
||||
|
||||
$chart->options['legend']['width'] = 105;
|
||||
$chart->options['legend']['position'] = 'right';
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
public function show()
|
||||
{
|
||||
return view($this->views['show'], ['print' => false])->with('class', $this);
|
||||
}
|
||||
|
||||
public function print()
|
||||
{
|
||||
return view($this->views['print'], ['print' => true])->with('class', $this);
|
||||
}
|
||||
|
||||
public function array(): array
|
||||
{
|
||||
$data = [];
|
||||
|
||||
$group = Str::plural($this->group ?? $this->getGroup());
|
||||
|
||||
foreach ($this->tables as $table_key => $table_name) {
|
||||
if (! isset($this->row_values[$table_key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($this->row_values[$table_key] as $key => $values) {
|
||||
if (empty($this->row_names[$table_key][$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($this->has_money) {
|
||||
$values = array_map(fn($value) => money($value)->format(), $values);
|
||||
}
|
||||
|
||||
$data[$table_key][$group][$this->row_names[$table_key][$key]] = $values;
|
||||
}
|
||||
|
||||
$footer_totals = $this->footer_totals[$table_key];
|
||||
|
||||
if ($this->has_money) {
|
||||
$footer_totals = array_map(fn($value) => money($value)->format(), $footer_totals);
|
||||
}
|
||||
|
||||
$data[$table_key]['totals'] = $footer_totals;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function pdf()
|
||||
{
|
||||
$view = view($this->views['print'], ['print' => true])->with('class', $this)->render();
|
||||
|
||||
$html = mb_convert_encoding($view, 'HTML-ENTITIES', 'UTF-8');
|
||||
|
||||
$pdf = app('dompdf.wrapper');
|
||||
$pdf->loadHTML($html);
|
||||
|
||||
$pdf->setPaper('A4', 'landscape');
|
||||
|
||||
$file_name = $this->model->name . ' - ' . company()->name . '.pdf';
|
||||
|
||||
return $pdf->download($file_name);
|
||||
}
|
||||
|
||||
public function export()
|
||||
{
|
||||
return ExportHelper::toExcel(new Export($this->views[$this->type], $this), $this->model->name);
|
||||
}
|
||||
|
||||
public function setColumnWidth()
|
||||
{
|
||||
if (! $period = $this->getPeriod()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$width = '';
|
||||
|
||||
switch ($period) {
|
||||
case 'quarterly':
|
||||
$width = 'w-2/12 col-2';
|
||||
break;
|
||||
case 'yearly':
|
||||
$width = 'w-4/12 col-4';
|
||||
break;
|
||||
case 'monthly':
|
||||
$width = 'col-1 w-20';
|
||||
break;
|
||||
case 'weekly':
|
||||
$width = 'col-1 w-20';
|
||||
break;
|
||||
}
|
||||
|
||||
if (empty($width)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->column_name_width = $this->column_value_width = $width;
|
||||
}
|
||||
|
||||
public function setChartLabelFormatter()
|
||||
{
|
||||
if (count($this->tables) > 1) {
|
||||
foreach ($this->tables as $table_key => $table) {
|
||||
if (empty($this->chart[$table_key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->chart[$table_key]['bar']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->bar_formatter_type);
|
||||
$this->chart[$table_key]['donut']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->donut_formatter_type);
|
||||
}
|
||||
} else {
|
||||
$this->chart['bar']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->bar_formatter_type);
|
||||
$this->chart['donut']['yaxis']['labels']['formatter'] = $this->getChartLabelFormatter($this->donut_formatter_type);
|
||||
}
|
||||
}
|
||||
|
||||
public function setYear()
|
||||
{
|
||||
$this->year = request()->filled('start_date') ? Date::parse(request('start_date'))->year : Date::now()->year;
|
||||
}
|
||||
|
||||
public function setViews()
|
||||
{
|
||||
$this->views = [
|
||||
'show' => 'components.reports.show',
|
||||
'print' => 'components.reports.print',
|
||||
'filter' => 'components.reports.filter',
|
||||
|
||||
'detail' => 'components.reports.detail',
|
||||
'detail.content.header' => 'components.reports.detail.content.header',
|
||||
'detail.content.footer' => 'components.reports.detail.content.footer',
|
||||
'detail.table' => 'components.reports.detail.table',
|
||||
'detail.table.header' => 'components.reports.detail.table.header',
|
||||
'detail.table.body' => 'components.reports.detail.table.body',
|
||||
'detail.table.row' => 'components.reports.detail.table.row',
|
||||
'detail.table.footer' => 'components.reports.detail.table.footer',
|
||||
|
||||
'summary' => 'components.reports.summary',
|
||||
'summary.content.header' => 'components.reports.summary.content.header',
|
||||
'summary.content.footer' => 'components.reports.summary.content.footer',
|
||||
'summary.table' => 'components.reports.summary.table',
|
||||
'summary.table.header' => 'components.reports.summary.table.header',
|
||||
'summary.table.body' => 'components.reports.summary.table.body',
|
||||
'summary.table.row' => 'components.reports.summary.table.row',
|
||||
'summary.table.footer' => 'components.reports.summary.table.footer',
|
||||
'summary.chart' => 'components.reports.summary.chart',
|
||||
];
|
||||
}
|
||||
|
||||
public function setTables()
|
||||
{
|
||||
$this->tables = [
|
||||
'default' => trans_choice('general.totals', 1),
|
||||
];
|
||||
}
|
||||
|
||||
public function setDates()
|
||||
{
|
||||
if (! $period = $this->getPeriod()) {
|
||||
return;
|
||||
}
|
||||
|
||||
[$start, $end] = $this->getStartAndEndDates($this->year);
|
||||
|
||||
$counter = match ($period) {
|
||||
'weekly' => $end->diffInWeeks($start),
|
||||
'quarterly' => $end->diffInQuarters($start),
|
||||
'yearly' => $end->diffInYears($start),
|
||||
default => $end->diffInMonths($start),
|
||||
};
|
||||
|
||||
for ($j = 0; $j <= $counter; $j++) {
|
||||
$date = $this->getPeriodicDate($start, $this->getPeriod(), $this->year);
|
||||
|
||||
$this->dates[] = $date;
|
||||
|
||||
foreach ($this->tables as $table_key => $table_name) {
|
||||
$this->footer_totals[$table_key][$date] = 0;
|
||||
}
|
||||
|
||||
match ($period) {
|
||||
'weekly' => $start->addWeek(),
|
||||
'quarterly' => $start->addQuarter(),
|
||||
'yearly' => $start->addYear(),
|
||||
default => $start->addMonth(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public function setFilters()
|
||||
{
|
||||
event(new FilterShowing($this));
|
||||
}
|
||||
|
||||
public function setGroups()
|
||||
{
|
||||
event(new GroupShowing($this));
|
||||
}
|
||||
|
||||
public function setRows()
|
||||
{
|
||||
event(new RowsShowing($this));
|
||||
}
|
||||
|
||||
public function setTotals($items, $date_field, $check_type = false, $table = 'default', $with_tax = true)
|
||||
{
|
||||
event(new TotalCalculating($this, $items, $date_field, $check_type, $table, $with_tax));
|
||||
|
||||
$group_field = $this->getGroup() . '_id';
|
||||
|
||||
foreach ($items as $item) {
|
||||
// Make groups extensible
|
||||
$item = $this->applyGroups($item);
|
||||
|
||||
$date = $this->getPeriodicDate(Date::parse($item->$date_field), $this->getPeriod(), $this->year);
|
||||
|
||||
if (!isset($item->$group_field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$group = $item->$group_field;
|
||||
|
||||
if (
|
||||
!isset($this->row_values[$table][$group])
|
||||
|| !isset($this->row_values[$table][$group][$date])
|
||||
|| !isset($this->footer_totals[$table][$date])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount = $item->getAmountConvertedToDefault(false, $with_tax);
|
||||
|
||||
$type = ($item->type === Document::INVOICE_TYPE || $item->type === 'income') ? 'income' : 'expense';
|
||||
|
||||
if (($check_type == false) || ($type == 'income')) {
|
||||
$this->row_values[$table][$group][$date] += $amount;
|
||||
|
||||
$this->footer_totals[$table][$date] += $amount;
|
||||
} else {
|
||||
$this->row_values[$table][$group][$date] -= $amount;
|
||||
|
||||
$this->footer_totals[$table][$date] -= $amount;
|
||||
}
|
||||
}
|
||||
|
||||
event(new TotalCalculated($this, $items, $date_field, $check_type, $table, $with_tax));
|
||||
}
|
||||
|
||||
public function setArithmeticTotals($items, $date_field, $operator = 'add', $table = 'default', $amount_field = 'amount')
|
||||
{
|
||||
$group_field = $this->getGroup() . '_id';
|
||||
|
||||
$function = $operator . 'ArithmeticAmount';
|
||||
|
||||
foreach ($items as $item) {
|
||||
// Make groups extensible
|
||||
$item = $this->applyGroups($item);
|
||||
|
||||
$date = $this->getPeriodicDate(Date::parse($item->$date_field), $this->getPeriod(), $this->year);
|
||||
|
||||
if (!isset($item->$group_field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$group = $item->$group_field;
|
||||
|
||||
if (
|
||||
!isset($this->row_values[$table][$group])
|
||||
|| !isset($this->row_values[$table][$group][$date])
|
||||
|| !isset($this->footer_totals[$table][$date])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$amount = isset($item->$amount_field) ? $item->$amount_field : 1;
|
||||
|
||||
$this->$function($this->row_values[$table][$group][$date], $amount);
|
||||
$this->$function($this->footer_totals[$table][$date], $amount);
|
||||
}
|
||||
}
|
||||
|
||||
public function addArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
$current = $current + $amount;
|
||||
}
|
||||
|
||||
public function subArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
$current = $current - $amount;
|
||||
}
|
||||
|
||||
public function mulArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
$current = $current * $amount;
|
||||
}
|
||||
|
||||
public function divArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
if ($amount == 0) {
|
||||
throw new \InvalidArgumentException('Division by zero is not allowed');
|
||||
}
|
||||
|
||||
$current = $current / $amount;
|
||||
}
|
||||
|
||||
public function modArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
if ($amount == 0) {
|
||||
throw new \InvalidArgumentException('Modulo by zero is not allowed');
|
||||
}
|
||||
|
||||
$current = $current % $amount;
|
||||
}
|
||||
|
||||
public function expArithmeticAmount(&$current, $amount)
|
||||
{
|
||||
$current = $current ** $amount;
|
||||
}
|
||||
|
||||
public function applyFilters($model, $args = [])
|
||||
{
|
||||
event(new FilterApplying($this, $model, $args));
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function applyGroups($model, $args = [])
|
||||
{
|
||||
event(new GroupApplying($this, $model, $args));
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
public function flattenDocumentItems(Collection $documents): Collection
|
||||
{
|
||||
return $documents->flatMap(fn (Document $d) =>
|
||||
$d->items->map(function (DocumentItem $di) use ($d) {
|
||||
$proxy = clone $di;
|
||||
$proxy->type = $d->type;
|
||||
$proxy->document_number = $d->document_number;
|
||||
$proxy->status = $d->status;
|
||||
$proxy->issued_at = $d->issued_at;
|
||||
$proxy->due_at = $d->due_at;
|
||||
$proxy->currency_code = $d->currency_code;
|
||||
$proxy->currency_rate = $d->currency_rate;
|
||||
$proxy->amount = $di->total;
|
||||
$proxy->contact_id = $d->contact_id;
|
||||
|
||||
return $proxy;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public function getUrl($action = 'print')
|
||||
{
|
||||
$url = company_id() . '/common/reports/' . $this->model->id . '/' . $action;
|
||||
|
||||
$parameters = array_filter(
|
||||
request()->all(),
|
||||
static function ($value, $key) {
|
||||
// Skip Laravel internal params
|
||||
if (in_array($key, ['_token', '_method'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_null($value) || is_scalar($value) || is_array($value);
|
||||
},
|
||||
ARRAY_FILTER_USE_BOTH
|
||||
);
|
||||
|
||||
$query = http_build_query($parameters, '', '&', PHP_QUERY_RFC3986);
|
||||
|
||||
if (!empty($query)) {
|
||||
$url .= '?' . $query;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getSetting($name, $default = '')
|
||||
{
|
||||
return $this->model->settings->$name ?? $default;
|
||||
}
|
||||
|
||||
public function getBasis()
|
||||
{
|
||||
return $this->getSearchStringValue('basis', $this->getSetting('basis', $this->getDefaultFieldSelection($this->getBasisField())));
|
||||
}
|
||||
|
||||
public function getPeriod()
|
||||
{
|
||||
return $this->getSearchStringValue('period', $this->getSetting('period', $this->getDefaultFieldSelection($this->getPeriodField())));
|
||||
}
|
||||
|
||||
public function getGroup()
|
||||
{
|
||||
return $this->getSearchStringValue('group', $this->getSetting('group', $this->getDefaultFieldSelection($this->getGroupField())));
|
||||
}
|
||||
|
||||
public function getDiscount()
|
||||
{
|
||||
return $this->getSearchStringValue('discount');
|
||||
}
|
||||
|
||||
public function getFields()
|
||||
{
|
||||
return [
|
||||
$this->getGroupField(),
|
||||
$this->getPeriodField(),
|
||||
$this->getBasisField(),
|
||||
];
|
||||
}
|
||||
|
||||
public function getGroupField()
|
||||
{
|
||||
$this->setGroups();
|
||||
|
||||
return [
|
||||
'type' => 'select',
|
||||
'name' => 'group',
|
||||
'title' => trans('general.group_by'),
|
||||
'icon' => 'folder',
|
||||
'values' => $this->groups,
|
||||
'selected' => 'category',
|
||||
'attributes' => [
|
||||
'required' => 'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
protected function getDefaultFieldSelection(array $field, string $fallback = ''): string
|
||||
{
|
||||
return $field['selected'] ?? $fallback;
|
||||
}
|
||||
|
||||
public function getPeriodField()
|
||||
{
|
||||
return [
|
||||
'type' => 'select',
|
||||
'name' => 'period',
|
||||
'title' => trans('general.period'),
|
||||
'icon' => 'calendar',
|
||||
'values' => [
|
||||
'weekly' => trans('general.weekly'),
|
||||
'monthly' => trans('general.monthly'),
|
||||
'quarterly' => trans('general.quarterly'),
|
||||
'yearly' => trans('general.yearly'),
|
||||
],
|
||||
'selected' => 'quarterly',
|
||||
'attributes' => [
|
||||
'required' => 'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function getBasisField()
|
||||
{
|
||||
return [
|
||||
'type' => 'select',
|
||||
'name' => 'basis',
|
||||
'title' => trans('general.basis'),
|
||||
'icon' => 'file',
|
||||
'values' => [
|
||||
'accrual' => trans('general.accrual'),
|
||||
'cash' => trans('general.cash'),
|
||||
],
|
||||
'selected' => 'accrual',
|
||||
'attributes' => [
|
||||
'required' => 'required',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function randHexColorPart(): string
|
||||
{
|
||||
return str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
public function randHexColor(): string
|
||||
{
|
||||
return '#' . $this->randHexColorPart() . $this->randHexColorPart() . $this->randHexColorPart();
|
||||
}
|
||||
|
||||
// @deprecated 3.1
|
||||
public function getFormattedDate($date)
|
||||
{
|
||||
return $this->getPeriodicDate($date, $this->getPeriod(), $this->year);
|
||||
}
|
||||
}
|
||||
13
app/Abstracts/View/Component.php
Normal file
13
app/Abstracts/View/Component.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View;
|
||||
|
||||
use Illuminate\View\Component as BaseComponent;
|
||||
|
||||
abstract class Component extends BaseComponent
|
||||
{
|
||||
public function getParentData($key, $default = null)
|
||||
{
|
||||
return app('view')->getConsumableComponentData($key, $default);
|
||||
}
|
||||
}
|
||||
568
app/Abstracts/View/Components/Contacts/Form.php
Normal file
568
app/Abstracts/View/Components/Contacts/Form.php
Normal file
@ -0,0 +1,568 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Contacts;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\ViewComponents;
|
||||
|
||||
abstract class Form extends Component
|
||||
{
|
||||
use ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'contact';
|
||||
public const DEFAULT_TYPE = 'customer';
|
||||
public const DEFAULT_PLURAL_TYPE = 'customers';
|
||||
|
||||
/* -- Main Start -- */
|
||||
public $type;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $model;
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
public $formId;
|
||||
|
||||
public $formRoute;
|
||||
|
||||
public $formMethod;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSectionGeneral;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSectionBilling;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSectionAddress;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSectionPersons;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionGeneralTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionGeneralDescription;
|
||||
|
||||
/** @var bool */
|
||||
public $hideName;
|
||||
|
||||
/** @var string */
|
||||
public $textName;
|
||||
|
||||
/** @var string */
|
||||
public $classNameFromGroupClass;
|
||||
|
||||
/** @var bool */
|
||||
public $hideEmail;
|
||||
|
||||
/** @var string */
|
||||
public $textEmail;
|
||||
|
||||
/** @var bool */
|
||||
public $hidePhone;
|
||||
|
||||
/** @var string */
|
||||
public $textPhone;
|
||||
|
||||
/** @var bool */
|
||||
public $hideWebsite;
|
||||
|
||||
/** @var string */
|
||||
public $textWebsite;
|
||||
|
||||
/** @var bool */
|
||||
public $hideReference;
|
||||
|
||||
/** @var string */
|
||||
public $textReference;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCanLogin;
|
||||
|
||||
/** @var bool */
|
||||
public $hideLogo;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionBillingTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionBillingDescription;
|
||||
|
||||
/** @var bool */
|
||||
public $hideTaxNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textTaxNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCurrency;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCategory;
|
||||
|
||||
/** @var string */
|
||||
public $categoryType;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionAddressTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionAddressDescription;
|
||||
|
||||
/** @var bool */
|
||||
public $hideAddress;
|
||||
|
||||
/** @var string */
|
||||
public $textAddress;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCity;
|
||||
|
||||
/** @var string */
|
||||
public $textCity;
|
||||
|
||||
/** @var bool */
|
||||
public $hideZipCode;
|
||||
|
||||
/** @var string */
|
||||
public $textZipCode;
|
||||
|
||||
/** @var bool */
|
||||
public $hideState;
|
||||
|
||||
/** @var string */
|
||||
public $textState;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCountry;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionPersonsTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textSectionPersonsDescription;
|
||||
|
||||
/** @var string */
|
||||
public $cancelRoute;
|
||||
/* -- Content End -- */
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $type, $model = false, $contact = false,
|
||||
string $formId = 'contact', $formRoute = '', $formMethod = '',
|
||||
bool $hideSectionGeneral = false, bool $hideSectionBilling = false, bool $hideSectionAddress = false, bool $hideSectionPersons = false,
|
||||
string $textSectionGeneralTitle = '', string $textSectionGeneralDescription = '',
|
||||
bool $hideName = false, string $textName = '', string $classNameFromGroupClass = '',
|
||||
bool $hideEmail = false, string $textEmail = '',
|
||||
bool $hidePhone = false, string $textPhone = '',
|
||||
bool $hideWebsite = false, string $textWebsite = '',
|
||||
bool $hideReference = false, string $textReference = '',
|
||||
bool $hideCanLogin = false,
|
||||
bool $hideLogo = false,
|
||||
string $textSectionBillingTitle = '', string $textSectionBillingDescription = '',
|
||||
bool $hideTaxNumber = false, string $textTaxNumber = '',
|
||||
bool $hideCurrency = false, bool $hideCategory = false, string $categoryType = '',
|
||||
string $textSectionAddressTitle = '', string $textSectionAddressDescription = '',
|
||||
bool $hideAddress = false, string $textAddress = '',
|
||||
bool $hideCity = false, string $textCity = '',
|
||||
bool $hideZipCode = false, string $textZipCode = '',
|
||||
bool $hideState = false, string $textState = '',
|
||||
bool $hideCountry = false,
|
||||
string $textSectionPersonsTitle = '', string $textSectionPersonsDescription = '',
|
||||
string $cancelRoute = ''
|
||||
) {
|
||||
$this->type = $type;
|
||||
|
||||
$this->model = ! empty($model) ? $model : $contact;
|
||||
$this->contact = $this->model;
|
||||
|
||||
/* -- Content Start -- */
|
||||
$this->formId = $formId;
|
||||
$this->formRoute = $this->getFormRoute($type, $formRoute, $this->model);
|
||||
$this->formMethod = $this->getFormMethod($type, $formMethod, $this->model);
|
||||
|
||||
$this->hideSectionGeneral = $hideSectionGeneral;
|
||||
$this->hideSectionBilling = $hideSectionBilling;
|
||||
$this->hideSectionAddress = $hideSectionAddress;
|
||||
$this->hideSectionPersons = $hideSectionPersons;
|
||||
|
||||
/* -- General Start -- */
|
||||
$this->textSectionGeneralTitle = $this->getTextSectionGeneralTitle($type, $textSectionGeneralTitle);
|
||||
$this->textSectionGeneralDescription = $this->getTextSectionGeneralDescription($type, $textSectionGeneralDescription);
|
||||
|
||||
$this->hideName = $hideName;
|
||||
$this->textName = $this->getTextName($type, $textName);
|
||||
$this->classNameFromGroupClass = $this->getClassNameFormGroupClass($type, $classNameFromGroupClass);
|
||||
|
||||
$this->hideEmail = $hideEmail;
|
||||
$this->textEmail = $this->getTextEmail($type, $textEmail);
|
||||
|
||||
$this->hidePhone = $hidePhone;
|
||||
$this->textPhone = $this->getTextPhone($type, $textPhone);
|
||||
|
||||
$this->hideWebsite = $hideWebsite;
|
||||
$this->textWebsite = $this->getTextWebsite($type, $textWebsite);
|
||||
|
||||
$this->hideReference = $hideReference;
|
||||
$this->textReference = $this->getTextReference($type, $textReference);
|
||||
|
||||
$this->hideCanLogin = $hideCanLogin;
|
||||
$this->hideLogo = $hideLogo;
|
||||
/* -- General End -- */
|
||||
|
||||
/* -- Billing Start -- */
|
||||
$this->textSectionBillingTitle = $this->getTextSectionBillingTitle($type, $textSectionBillingTitle);
|
||||
$this->textSectionBillingDescription = $this->getTextSectionBillingDescription($type, $textSectionBillingDescription);
|
||||
|
||||
$this->hideTaxNumber = $hideTaxNumber;
|
||||
$this->textTaxNumber = $this->getTextTaxNumber($type, $textTaxNumber);
|
||||
|
||||
$this->hideCurrency = $hideCurrency;
|
||||
|
||||
$this->hideCategory = $hideCategory;
|
||||
$this->categoryType = $this->getCategoryType($type, $categoryType);
|
||||
/* -- Billing End -- */
|
||||
|
||||
/* -- Address Start -- */
|
||||
$this->textSectionAddressTitle = $this->getTextSectionAddressTitle($type, $textSectionAddressTitle);
|
||||
$this->textSectionAddressDescription = $this->getTextSectionAddressDescription($type, $textSectionAddressDescription);
|
||||
|
||||
$this->hideAddress = $hideAddress;
|
||||
$this->textAddress = $this->getTextAddress($type, $textAddress);
|
||||
|
||||
$this->hideCity = $hideCity;
|
||||
$this->textCity = $this->getTextCity($type, $textCity);
|
||||
|
||||
$this->hideZipCode = $hideZipCode;
|
||||
$this->textZipCode = $this->getTextZipCode($type, $textZipCode);
|
||||
|
||||
$this->hideState = $hideState;
|
||||
$this->textState = $this->getTextState($type, $textState);
|
||||
|
||||
$this->hideState = $hideTaxNumber;
|
||||
/* -- Address End -- */
|
||||
|
||||
/* -- Persons Start -- */
|
||||
$this->textSectionPersonsTitle = $this->getTextSectionPersonsTitle($type, $textSectionPersonsTitle);
|
||||
$this->textSectionPersonsDescription = $this->getTextSectionPersonsDescription($type, $textSectionPersonsDescription);
|
||||
/* -- Persons End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
$this->cancelRoute = $this->getCancelRoute($type, $cancelRoute);
|
||||
/* -- Buttons End -- */
|
||||
/* -- Content End -- */
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
/* -- Content Start -- */
|
||||
/* -- General Start -- */
|
||||
protected function getTextSectionGeneralTitle($type, $textSectionGeneralTitle)
|
||||
{
|
||||
if (! empty($textSectionGeneralTitle)) {
|
||||
return $textSectionGeneralTitle;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_general_title', 'general');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.general';
|
||||
}
|
||||
|
||||
protected function getTextSectionGeneralDescription($type, $textSectionGeneralDescription)
|
||||
{
|
||||
if (! empty($textSectionGeneralDescription)) {
|
||||
return $textSectionGeneralDescription;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_general_description', 'form_description.general');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'customers.form_description.general';
|
||||
}
|
||||
|
||||
protected function getTextName($type, $textName)
|
||||
{
|
||||
if (! empty($textName)) {
|
||||
return $textName;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'name', 'name');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.name';
|
||||
}
|
||||
|
||||
protected function getClassNameFormGroupClass($type, $classNameFromGroupClass)
|
||||
{
|
||||
if (! empty($classNameFromGroupClass)) {
|
||||
return $classNameFromGroupClass;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'name');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'sm:col-span-6';
|
||||
}
|
||||
|
||||
protected function getTextEmail($type, $textEmail)
|
||||
{
|
||||
if (! empty($textEmail)) {
|
||||
return $textEmail;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'email', 'email');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.email';
|
||||
}
|
||||
|
||||
protected function getTextPhone($type, $textPhone)
|
||||
{
|
||||
if (! empty($textPhone)) {
|
||||
return $textPhone;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'phone', 'phone');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.phone';
|
||||
}
|
||||
|
||||
protected function getTextWebsite($type, $textWebsite)
|
||||
{
|
||||
if (! empty($textWebsite)) {
|
||||
return $textWebsite;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'website', 'website');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.website';
|
||||
}
|
||||
|
||||
protected function getTextReference($type, $textReference)
|
||||
{
|
||||
if (! empty($textReference)) {
|
||||
return $textReference;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'reference', 'reference');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.reference';
|
||||
}
|
||||
/* -- General End -- */
|
||||
|
||||
/* -- Billing Start -- */
|
||||
protected function getTextSectionBillingTitle($type, $textSectionBillingTitle)
|
||||
{
|
||||
if (! empty($textSectionBillingTitle)) {
|
||||
return $textSectionBillingTitle;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_billing_title');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'items.billing';
|
||||
}
|
||||
|
||||
protected function getTextSectionBillingDescription($type, $textSectionBillingDescription)
|
||||
{
|
||||
if (! empty($textSectionBillingDescription)) {
|
||||
return $textSectionBillingDescription;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_billing_description');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'customers.form_description.billing';
|
||||
}
|
||||
|
||||
protected function getTextTaxNumber($type, $textTaxNumber)
|
||||
{
|
||||
if (! empty($textTaxNumber)) {
|
||||
return $textTaxNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'tax_number', 'tax_number');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.tax_number';
|
||||
}
|
||||
|
||||
protected function getCategoryType($type, $categoryType)
|
||||
{
|
||||
if (! empty($categoryType)) {
|
||||
return $categoryType;
|
||||
}
|
||||
|
||||
return config('type.contact.' . $type . '.transaction_type', 'income');
|
||||
}
|
||||
/* -- Billing End -- */
|
||||
|
||||
/* -- Address Start -- */
|
||||
protected function getTextSectionAddressTitle($type, $textSectionAddressTitle)
|
||||
{
|
||||
if (! empty($textSectionAddressTitle)) {
|
||||
return $textSectionAddressTitle;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_address_title', 'address');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.address';
|
||||
}
|
||||
|
||||
protected function getTextSectionAddressDescription($type, $textSectionAddressDescription)
|
||||
{
|
||||
if (! empty($textSectionAddressDescription)) {
|
||||
return $textSectionAddressDescription;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_address_description');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'customers.form_description.address';
|
||||
}
|
||||
|
||||
/* -- Persons Start -- */
|
||||
protected function getTextSectionPersonsTitle($type, $textSectionPersonsTitle)
|
||||
{
|
||||
if (! empty($textSectionPersonsTitle)) {
|
||||
return $textSectionPersonsTitle;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_contact_persons_title', 'contact_persons');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.contact_persons';
|
||||
}
|
||||
|
||||
protected function getTextSectionPersonsDescription($type, $textSectionPersonsDescription)
|
||||
{
|
||||
if (! empty($textSectionPersonsDescription)) {
|
||||
return $textSectionPersonsDescription;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'section_contact_persons_description');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'customers.form_description.contact_persons';
|
||||
}
|
||||
|
||||
protected function getTextAddress($type, $textAddress)
|
||||
{
|
||||
if (! empty($textAddress)) {
|
||||
return $textAddress;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'address', 'address');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.address';
|
||||
}
|
||||
|
||||
protected function getTextCity($type, $textCity)
|
||||
{
|
||||
if (! empty($textCity)) {
|
||||
return $textCity;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'city', 'cities');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.cities';
|
||||
}
|
||||
|
||||
protected function getTextZipCode($type, $textZipCode)
|
||||
{
|
||||
if (! empty($textZipCode)) {
|
||||
return $textZipCode;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'zip_code', 'zip_code');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.zip_code';
|
||||
}
|
||||
|
||||
protected function getTextState($type, $textState)
|
||||
{
|
||||
if (! empty($textState)) {
|
||||
return $textState;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'state', 'state');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.state';
|
||||
}
|
||||
/* -- Address End -- */
|
||||
/* -- Content End -- */
|
||||
}
|
||||
497
app/Abstracts/View/Components/Contacts/Index.php
Normal file
497
app/Abstracts/View/Components/Contacts/Index.php
Normal file
@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Contacts;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\Documents;
|
||||
use App\Traits\ViewComponents;
|
||||
|
||||
abstract class Index extends Component
|
||||
{
|
||||
use Documents, ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'contact';
|
||||
public const DEFAULT_TYPE = 'customer';
|
||||
public const DEFAULT_PLURAL_TYPE = 'customers';
|
||||
|
||||
/* -- Main Start -- */
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
public $contacts;
|
||||
|
||||
/** @var string */
|
||||
public $textPage;
|
||||
|
||||
/** @var string */
|
||||
public $group;
|
||||
|
||||
/** @var string */
|
||||
public $page;
|
||||
|
||||
public $permissionCreate;
|
||||
|
||||
public $permissionUpdate;
|
||||
|
||||
public $permissionDelete;
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
public $checkPermissionCreate;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCreate;
|
||||
|
||||
/** @var bool */
|
||||
public $hideImport;
|
||||
|
||||
/** @var bool */
|
||||
public $hideExport;
|
||||
|
||||
/** @var string */
|
||||
public $createRoute;
|
||||
|
||||
/** @var string */
|
||||
public $importRoute;
|
||||
|
||||
/** @var array */
|
||||
public $importRouteParameters;
|
||||
|
||||
/** @var string */
|
||||
public $exportRoute;
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
/** @var bool */
|
||||
public $hideEmptyPage;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSummary;
|
||||
|
||||
public $summaryItems;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSearchString;
|
||||
|
||||
/** @var bool */
|
||||
public $hideBulkAction;
|
||||
|
||||
/** @var string */
|
||||
public $searchStringModel;
|
||||
|
||||
/** @var string */
|
||||
public $bulkActionClass;
|
||||
|
||||
/** @var array */
|
||||
public $bulkActionRouteParameters;
|
||||
|
||||
/** @var string */
|
||||
public $searchRoute;
|
||||
|
||||
/** @var string */
|
||||
public $classBulkAction;
|
||||
|
||||
/** @var bool */
|
||||
public $showLogo;
|
||||
|
||||
/** @var bool */
|
||||
public $hideName;
|
||||
|
||||
/** @var bool */
|
||||
public $hideTaxNumber;
|
||||
|
||||
/** @var string */
|
||||
public $classNameAndTaxNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textName;
|
||||
|
||||
/** @var string */
|
||||
public $textTaxNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideEmail;
|
||||
|
||||
/** @var bool */
|
||||
public $hidePhone;
|
||||
|
||||
/** @var string */
|
||||
public $classEmailAndPhone;
|
||||
|
||||
/** @var string */
|
||||
public $textEmail;
|
||||
|
||||
/** @var string */
|
||||
public $textPhone;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCountry;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCurrencyCode;
|
||||
|
||||
/** @var string */
|
||||
public $classCountryAndCurrencyCode;
|
||||
|
||||
/** @var string */
|
||||
public $textCountry;
|
||||
|
||||
/** @var string */
|
||||
public $textCurrencyCode;
|
||||
|
||||
/** @var bool */
|
||||
public $hideOpen;
|
||||
|
||||
/** @var bool */
|
||||
public $hideOverdue;
|
||||
|
||||
/** @var string */
|
||||
public $classOpenAndOverdue;
|
||||
|
||||
/** @var string */
|
||||
public $textOpen;
|
||||
|
||||
/** @var string */
|
||||
public $textOverdue;
|
||||
/* -- Content End -- */
|
||||
|
||||
/* -- Empty Start -- */
|
||||
/** @var string */
|
||||
public $imageEmptyPage;
|
||||
|
||||
/** @var string */
|
||||
public $textEmptyPage;
|
||||
|
||||
/** @var string */
|
||||
public $urlDocsPath;
|
||||
|
||||
/** @var string */
|
||||
public $routeButtonShow;
|
||||
/* -- Empty End -- */
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $type, $contacts = [], string $textPage = '', string $group = '', string $page = '',
|
||||
string $permissionCreate = '', string $permissionUpdate = '', string $permissionDelete = '',
|
||||
bool $checkPermissionCreate = true,
|
||||
bool $hideCreate = false, bool $hideImport = false, bool $hideExport = false,
|
||||
string $createRoute = '', string $importRoute = '', array $importRouteParameters = [], string $exportRoute = '',
|
||||
bool $hideEmptyPage = false, bool $hideSummary = false, $summaryItems = [], bool $hideSearchString = false, bool $hideBulkAction = false,
|
||||
string $searchStringModel = '', string $bulkActionClass = '', array $bulkActions = [], array $bulkActionRouteParameters = [], string $searchRoute = '',
|
||||
string $classBulkAction = '',
|
||||
bool $showLogo = false, bool $hideName = false, bool $hideTaxNumber = false, string $classNameAndTaxNumber = '', string $textName = '', string $textTaxNumber = '',
|
||||
bool $hideEmail = false, bool $hidePhone = false, string $classEmailAndPhone = '', string $textEmail = '', string $textPhone = '',
|
||||
bool $hideCountry = false, bool $hideCurrencyCode = false, string $classCountryAndCurrencyCode = '', string $textCountry = '', string $textCurrencyCode = '',
|
||||
bool $hideOpen = false, bool $hideOverdue = false, string $classOpenAndOverdue = '', string $textOpen = '', string $textOverdue = '',
|
||||
string $imageEmptyPage = '', string $textEmptyPage = '', string $urlDocsPath = '', string $routeButtonShow = '',
|
||||
) {
|
||||
/* -- Main Start -- */
|
||||
$this->type = $type;
|
||||
$this->group = $this->getGroup($type, $group);
|
||||
$this->page = $this->getPage($type, $page);
|
||||
$this->contacts = ($contacts) ? $contacts : collect();
|
||||
$this->textPage = $this->getTextPage($type, $textPage);
|
||||
|
||||
$this->permissionCreate = $this->getPermissionCreate($type, $permissionCreate);
|
||||
$this->permissionUpdate = $this->getPermissionUpdate($type, $permissionUpdate);
|
||||
$this->permissionDelete = $this->getPermissionDelete($type, $permissionDelete);
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
$this->checkPermissionCreate = $checkPermissionCreate;
|
||||
|
||||
$this->hideCreate = $hideCreate;
|
||||
$this->hideImport = $hideImport;
|
||||
$this->hideExport = $hideExport;
|
||||
|
||||
$this->routeButtonShow = $this->getRouteButtonShow($type, $routeButtonShow);
|
||||
$this->createRoute = $this->getCreateRoute($type, $createRoute);
|
||||
$this->importRoute = $this->getImportRoute($importRoute);
|
||||
$this->importRouteParameters = $this->getImportRouteParameters($type, $importRouteParameters);
|
||||
$this->exportRoute = $this->getExportRoute($type, $exportRoute);
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
$this->hideEmptyPage = $hideEmptyPage;
|
||||
|
||||
$this->hideSummary = $hideSummary;
|
||||
$this->summaryItems = $this->getSummaryItems($type, $summaryItems);
|
||||
|
||||
$this->hideSearchString = $hideSearchString;
|
||||
$this->hideBulkAction = $hideBulkAction;
|
||||
|
||||
$this->searchStringModel = $this->getSearchStringModel($type, $searchStringModel);
|
||||
|
||||
$this->bulkActionClass = $this->getBulkActionClass($type, $bulkActionClass);
|
||||
$this->bulkActionRouteParameters = $this->getBulkActionRouteParameters($type, $bulkActionRouteParameters);
|
||||
|
||||
$this->searchRoute = $this->getIndexRoute($type, $searchRoute);
|
||||
|
||||
$this->classBulkAction = $this->getClassBulkAction($type, $classBulkAction);
|
||||
|
||||
$this->showLogo = $showLogo;
|
||||
$this->hideName = $hideName;
|
||||
$this->hideTaxNumber = $hideTaxNumber;
|
||||
$this->classNameAndTaxNumber = $this->getClassNameAndTaxNumber($type, $classNameAndTaxNumber);
|
||||
$this->textName = $this->getTextName($type, $textName);
|
||||
$this->textTaxNumber = $this->getTextTaxNumber($type, $textTaxNumber);
|
||||
|
||||
$this->hideEmail = $hideEmail;
|
||||
$this->hidePhone = $hidePhone;
|
||||
$this->classEmailAndPhone = $this->getClassEmailAndPhone($type, $classEmailAndPhone);
|
||||
$this->textEmail = $this->getTextEmail($type, $textEmail);
|
||||
$this->textPhone = $this->getTextPhone($type, $textPhone);
|
||||
|
||||
$this->hideCountry = $hideCountry;
|
||||
$this->hideCurrencyCode = $hideCurrencyCode;
|
||||
$this->classCountryAndCurrencyCode = $this->getClassCountryAndCurrencyCode($type, $classCountryAndCurrencyCode);
|
||||
$this->textCountry = $this->getTextCountry($type, $textCountry);
|
||||
$this->textCurrencyCode = $this->getTextCurrencyCode($type, $textCurrencyCode);
|
||||
|
||||
$this->hideOpen = $hideOpen;
|
||||
$this->hideOverdue = $hideOverdue;
|
||||
$this->classOpenAndOverdue = $this->getClassOpenAndOverdue($type, $classOpenAndOverdue);
|
||||
$this->textOpen = $this->getTextOpen($type, $textOpen);
|
||||
$this->textOverdue = $this->getTextOverdue($type, $textOverdue);
|
||||
/* -- Content End -- */
|
||||
|
||||
/* -- Empty Start -- */
|
||||
$this->imageEmptyPage = $this->getImageEmptyPage($type, $imageEmptyPage);
|
||||
$this->textEmptyPage = $this->getTextEmptyPage($type, $textEmptyPage);
|
||||
$this->urlDocsPath = $this->getUrlDocsPath($type, $urlDocsPath);
|
||||
/* -- Empty End -- */
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
public function getSummaryItems($type, $summaryItems)
|
||||
{
|
||||
if (! empty($summaryItems)) {
|
||||
return $summaryItems;
|
||||
}
|
||||
|
||||
$route = $this->getIndexRoute($type, null);
|
||||
|
||||
$document_type = config('type.contact.' . $type . '.document_type', 'invoice');
|
||||
|
||||
$totals = $this->getTotalsForFutureDocuments($document_type);
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($totals as $key => $total) {
|
||||
$items[] = [
|
||||
'title' => ($key == 'overdue') ? trans('general.overdue') : trans('documents.statuses.' . $key),
|
||||
//'href' => route($route, ['search' => 'status:' . $key]),
|
||||
'amount' => money($total)->formatForHumans(),
|
||||
'tooltip' => money($total)->format(),
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/* -- Content Start -- */
|
||||
protected function getClassNameAndTaxNumber($type, $classNameAndTaxNumber)
|
||||
{
|
||||
if (! empty($classNameAndTaxNumber)) {
|
||||
return $classNameAndTaxNumber;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'name_and_tax_number');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-6/12 sm:w-3/12 truncate';
|
||||
}
|
||||
|
||||
protected function getTextName($type, $textName)
|
||||
{
|
||||
if (! empty($textName)) {
|
||||
return $textName;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'name', 'name');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.name';
|
||||
}
|
||||
|
||||
protected function getTextTaxNumber($type, $textTaxNumber)
|
||||
{
|
||||
if (! empty($textTaxNumber)) {
|
||||
return $textTaxNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'tax_number', 'tax_number');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.tax_number';
|
||||
}
|
||||
|
||||
protected function getClassEmailAndPhone($type, $classEmailAndPhone)
|
||||
{
|
||||
if (! empty($classEmailAndPhone)) {
|
||||
return $classEmailAndPhone;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'email_and_phone');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-3/12 hidden sm:table-cell';
|
||||
}
|
||||
|
||||
protected function getTextEmail($type, $textEmail)
|
||||
{
|
||||
if (! empty($textEmail)) {
|
||||
return $textEmail;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'email', 'email');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.email';
|
||||
}
|
||||
|
||||
protected function getTextPhone($type, $textPhone)
|
||||
{
|
||||
if (! empty($textPhone)) {
|
||||
return $textPhone;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'phone', 'phone');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.phone';
|
||||
}
|
||||
|
||||
protected function getClassCountryAndCurrencyCode($type, $classCountryAndCurrencyCode)
|
||||
{
|
||||
if (! empty($classCountryAndCurrencyCode)) {
|
||||
return $classCountryAndCurrencyCode;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'country_and_currency_code');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-3/12 hidden sm:table-cell';
|
||||
}
|
||||
|
||||
protected function getTextCountry($type, $textCountry)
|
||||
{
|
||||
if (! empty($textCountry)) {
|
||||
return $textCountry;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'country', 'countries');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.countries';
|
||||
}
|
||||
|
||||
protected function getTextCurrencyCode($type, $textCurrencyCode)
|
||||
{
|
||||
if (! empty($textCurrencyCode)) {
|
||||
return $textCurrencyCode;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'currency_code', 'currencies');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.currencies';
|
||||
}
|
||||
|
||||
protected function getClassOpenAndOverdue($type, $classOpenAndOverdue)
|
||||
{
|
||||
if (! empty($classOpenAndOverdue)) {
|
||||
return $classOpenAndOverdue;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'open_and_overdue');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-6/12 sm:w-3/12';
|
||||
}
|
||||
|
||||
protected function getTextOpen($type, $textOpen)
|
||||
{
|
||||
if (! empty($textOpen)) {
|
||||
return $textOpen;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'open', 'open');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.open';
|
||||
}
|
||||
|
||||
protected function getTextOverdue($type, $textOverdue)
|
||||
{
|
||||
if (! empty($textOverdue)) {
|
||||
return $textOverdue;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'overdue', 'overdue');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.overdue';
|
||||
}
|
||||
|
||||
protected function getRouteButtonShow($type, $routeButtonShow)
|
||||
{
|
||||
if (!empty($routeButtonShow)) {
|
||||
return $routeButtonShow;
|
||||
}
|
||||
|
||||
//example route parameter.
|
||||
$parameter = 1;
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'show', $parameter);
|
||||
|
||||
if (!empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
return 'customer.show';
|
||||
}
|
||||
/* -- Content End -- */
|
||||
}
|
||||
319
app/Abstracts/View/Components/Contacts/Show.php
Normal file
319
app/Abstracts/View/Components/Contacts/Show.php
Normal file
@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Contacts;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\ViewComponents;
|
||||
|
||||
abstract class Show extends Component
|
||||
{
|
||||
use ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'contact';
|
||||
public const DEFAULT_TYPE = 'customer';
|
||||
public const DEFAULT_PLURAL_TYPE = 'customers';
|
||||
|
||||
/* -- Main Start -- */
|
||||
public $type;
|
||||
|
||||
public $contact;
|
||||
|
||||
public $model;
|
||||
|
||||
public $permissionCreate;
|
||||
|
||||
public $permissionUpdate;
|
||||
|
||||
public $permissionDelete;
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
public $hideNewDropdown;
|
||||
|
||||
public $hideButtonDocument;
|
||||
|
||||
public $hideButtonTransaction;
|
||||
|
||||
public $permissionCreateDocument;
|
||||
|
||||
public $permissionCreateTransaction;
|
||||
|
||||
public $routeButtonDocument;
|
||||
|
||||
public $routeButtonTransaction;
|
||||
|
||||
public $textDocument;
|
||||
|
||||
public $textTransaction;
|
||||
|
||||
public $hideButtonEdit;
|
||||
|
||||
public $routeButtonEdit;
|
||||
|
||||
public $hideActionsDropdown;
|
||||
|
||||
public $hideButtonDuplicate;
|
||||
|
||||
public $routeButtonDuplicate;
|
||||
|
||||
public $hideButtonDelete;
|
||||
|
||||
public $routeButtonDelete;
|
||||
|
||||
public $textDeleteModal;
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Profile Start -- */
|
||||
public $hideTopLeft;
|
||||
|
||||
public $hideAvatar;
|
||||
|
||||
public $hideEmail;
|
||||
|
||||
public $hidePhone;
|
||||
|
||||
public $hideTopRight;
|
||||
|
||||
public $hideOverdue;
|
||||
|
||||
public $hideOpen;
|
||||
|
||||
public $hidePaid;
|
||||
|
||||
public $hideBottomLeft;
|
||||
|
||||
public $hideAddress;
|
||||
|
||||
public $hideTaxNumber;
|
||||
|
||||
public $hideWebsite;
|
||||
|
||||
public $hideReference;
|
||||
|
||||
public $hideUser;
|
||||
|
||||
public $hidePersons;
|
||||
|
||||
public $hideBottomRight;
|
||||
/* -- Profile End -- */
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $type, $model = false, $contact = false,
|
||||
string $permissionCreate = '', string $permissionUpdate = '', string $permissionDelete = '',
|
||||
bool $hideNewDropdown = false, bool $hideButtonDocument = false, $hideButtonTransaction = false,
|
||||
string $permissionCreateDocument = '', string $permissionCreateTransaction = '',
|
||||
$routeButtonDocument = '', $routeButtonTransaction = '',
|
||||
string $textDocument = '', string $textTransaction = '',
|
||||
bool $hideButtonEdit = false, $routeButtonEdit = '',
|
||||
bool $hideActionsDropdown = false, bool $hideButtonDuplicate = false, $routeButtonDuplicate = '',
|
||||
bool $hideButtonDelete = false, $routeButtonDelete = '', $textDeleteModal = '',
|
||||
bool $hideTopLeft = false, bool $hideAvatar = false, bool $hideEmail = false, bool $hidePhone = false,
|
||||
bool $hideTopRight = false, bool $hideOverdue = false, bool $hideOpen = false, bool $hidePaid = false,
|
||||
bool $hideBottomLeft = false, bool $hideAddress = false, bool $hideTaxNumber = false , bool $hideWebsite = false, bool $hideReference = false, bool $hideUser = false, bool $hidePersons = false,
|
||||
bool $hideBottomRight = false
|
||||
) {
|
||||
/* -- Main Start -- */
|
||||
$this->type = $type;
|
||||
|
||||
$this->model = ! empty($model) ? $model : $contact;
|
||||
$this->contact = $this->model;
|
||||
|
||||
$this->permissionCreate = $this->getPermissionCreate($type, $permissionCreate);
|
||||
$this->permissionUpdate = $this->getPermissionUpdate($type, $permissionUpdate);
|
||||
$this->permissionDelete = $this->getPermissionDelete($type, $permissionDelete);
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
$this->hideNewDropdown = $hideNewDropdown;
|
||||
$this->hideButtonDocument = $hideButtonDocument;
|
||||
$this->hideButtonTransaction = $hideButtonTransaction;
|
||||
|
||||
$this->permissionCreateDocument = $this->getPermissionCreateDocument($type, $permissionCreateDocument);
|
||||
$this->permissionCreateTransaction = $this->getPermissionCreateTransaction($type, $permissionCreateTransaction);
|
||||
|
||||
$this->routeButtonDocument = $this->getCreateDocumentRoute($type, $routeButtonDocument);
|
||||
$this->routeButtonTransaction = $this->getCreateTransactionRoute($type, $routeButtonTransaction);
|
||||
|
||||
$this->textDocument = $this->getTextDocument($type, $textDocument);
|
||||
$this->textTransaction = $this->getTextTransaction($type, $textTransaction);
|
||||
|
||||
$this->hideButtonEdit = $hideButtonEdit;
|
||||
$this->routeButtonEdit = $this->getEditRoute($type, $routeButtonEdit);
|
||||
|
||||
$this->hideActionsDropdown = $hideActionsDropdown;
|
||||
$this->hideButtonDuplicate = $hideButtonDuplicate;
|
||||
$this->routeButtonDuplicate = $this->getDuplicateRoute($type, $routeButtonDuplicate);
|
||||
|
||||
$this->hideButtonDelete = $hideButtonDelete;
|
||||
$this->routeButtonDelete = $this->getDeleteRoute($type, $routeButtonDelete);
|
||||
$this->textDeleteModal = $this->getTextDeleteModal($type, $textDeleteModal);
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Profile Start -- */
|
||||
$this->hideProfile = $hideTopLeft;
|
||||
$this->hideAvatar = $hideAvatar;
|
||||
$this->hideEmail = $hideEmail;
|
||||
$this->hidePhone = $hidePhone;
|
||||
|
||||
$this->hideDetails = $hideTopRight;
|
||||
$this->hideOverdue = $hideOverdue;
|
||||
$this->hideOpen = $hideOpen;
|
||||
$this->hidePaid = $hidePaid;
|
||||
|
||||
$this->hideSummary = $hideBottomLeft;
|
||||
$this->hideAddress = $hideAddress;
|
||||
$this->hideTaxNumber = $hideTaxNumber;
|
||||
$this->hideWebsite = $hideWebsite;
|
||||
$this->hideReference = $hideReference;
|
||||
$this->hideUser = $hideUser;
|
||||
$this->hidePersons = $hidePersons;
|
||||
|
||||
$this->hideContent = $hideBottomRight;
|
||||
/* -- Profile End -- */
|
||||
}
|
||||
|
||||
protected function getPermissionCreateDocument($type, $permissionCreateDocument)
|
||||
{
|
||||
if (! empty($permissionCreateDocument)) {
|
||||
return $permissionCreateDocument;
|
||||
}
|
||||
|
||||
$document_type = config('type.contact.' . $type . '.document_type', 'invoice');
|
||||
|
||||
$permission = '';
|
||||
$config_key = 'create';
|
||||
|
||||
// if set config translation config_key
|
||||
if ($permission = config('type.document.' . $document_type . '.permission.' . $config_key)) {
|
||||
return $permission;
|
||||
}
|
||||
|
||||
$alias = config('type.document.' . $document_type . '.alias');
|
||||
$group = config('type.document.' . $document_type . '.group');
|
||||
$prefix = config('type.document.' . $document_type . '.permission.prefix');
|
||||
|
||||
$permission = $config_key . '-';
|
||||
|
||||
// if use module set module alias
|
||||
if (! empty($alias)) {
|
||||
$permission .= $alias . '-';
|
||||
}
|
||||
|
||||
// if controller in folder it must
|
||||
if (! empty($group)) {
|
||||
$permission .= $group . '-';
|
||||
}
|
||||
|
||||
$permission .= $prefix;
|
||||
|
||||
$permissionCreateDocument = $permission;
|
||||
|
||||
return $permissionCreateDocument;
|
||||
}
|
||||
|
||||
protected function getPermissionCreateTransaction($type, $permissionCreateTransaction)
|
||||
{
|
||||
if (! empty($permissionCreateTransaction)) {
|
||||
return $permissionCreateTransaction;
|
||||
}
|
||||
|
||||
$permissionCreateTransaction = 'create-banking-transactions';
|
||||
|
||||
return $permissionCreateTransaction;
|
||||
}
|
||||
|
||||
protected function getCreateDocumentRoute($type, $routeButtonDocument)
|
||||
{
|
||||
if (! empty($routeButtonDocument)) {
|
||||
return $routeButtonDocument;
|
||||
}
|
||||
|
||||
$prefix = config('type.contact.' . $type . '.route.prefix');
|
||||
$document_type = config('type.contact.' . $type . '.document_type');
|
||||
|
||||
return $prefix . '.create-' . $document_type;
|
||||
}
|
||||
|
||||
protected function getCreateTransactionRoute($type, $routeButtonDocument)
|
||||
{
|
||||
if (! empty($routeButtonDocument)) {
|
||||
return $routeButtonDocument;
|
||||
}
|
||||
|
||||
$prefix = config('type.contact.' . $type . '.route.prefix');
|
||||
$transaction_type = config('type.contact.' . $type . '.transaction_type');
|
||||
|
||||
return $prefix . '.create-' . $transaction_type;
|
||||
}
|
||||
|
||||
protected function getTextDocument($type, $textDocument)
|
||||
{
|
||||
if (! empty($textDocument)) {
|
||||
return $textDocument;
|
||||
}
|
||||
|
||||
$document_type = config('type.contact.' . $type . '.document_type');
|
||||
|
||||
switch ($document_type) {
|
||||
case 'invoice':
|
||||
$text = 'general.invoices';
|
||||
break;
|
||||
case 'bill':
|
||||
$text = 'general.bills';
|
||||
break;
|
||||
default:
|
||||
$text = config('type.contact.' . $type . '.translation.prefix') . '.' . config('type.contact.' . $type . '.route.prefix');
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function getTextTransaction($type, $textTransaction)
|
||||
{
|
||||
if (! empty($textTransaction)) {
|
||||
return $textTransaction;
|
||||
}
|
||||
|
||||
$document_type = config('type.contact.' . $type . '.document_type');
|
||||
|
||||
switch ($document_type) {
|
||||
case 'invoice':
|
||||
$text = 'general.incomes';
|
||||
break;
|
||||
case 'bill':
|
||||
$text = 'general.expenses';
|
||||
break;
|
||||
default:
|
||||
$text = config('type.contact.' . $type . '.translation.prefix') . '.' . config('type.contact.' . $type . '.transaction_type') . 's';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function getTextDeleteModal($type, $textDeleteModal)
|
||||
{
|
||||
if (! empty($textDeleteModal)) {
|
||||
return $textDeleteModal;
|
||||
}
|
||||
|
||||
$document_type = config('type.contact.' . $type . '.document_type');
|
||||
|
||||
switch ($document_type) {
|
||||
case 'invoice':
|
||||
$text = 'general.incomes';
|
||||
break;
|
||||
case 'bill':
|
||||
$text = 'general.expenses';
|
||||
break;
|
||||
default:
|
||||
$text = config('type.contact.' . $type . '.translation.prefix') . '.' . config('type.contact.' . $type . '.transaction_type') . 's';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
1395
app/Abstracts/View/Components/Documents/Form.php
Normal file
1395
app/Abstracts/View/Components/Documents/Form.php
Normal file
File diff suppressed because it is too large
Load Diff
907
app/Abstracts/View/Components/Documents/Index.php
Normal file
907
app/Abstracts/View/Components/Documents/Index.php
Normal file
@ -0,0 +1,907 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Documents;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\Documents;
|
||||
use App\Traits\Modules;
|
||||
use App\Traits\SearchString;
|
||||
use App\Traits\ViewComponents;
|
||||
use App\Models\Document\Document;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class Index extends Component
|
||||
{
|
||||
use Documents, Modules, SearchString, ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'document';
|
||||
public const DEFAULT_TYPE = 'invoice';
|
||||
public const DEFAULT_PLURAL_TYPE = 'invoices';
|
||||
|
||||
/* -- Main Start -- */
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
/** @var string */
|
||||
public $contact_type;
|
||||
|
||||
/** @var string */
|
||||
public $category_type;
|
||||
|
||||
/** @var string */
|
||||
public $alias;
|
||||
|
||||
public $documents;
|
||||
|
||||
public $totalDocuments;
|
||||
|
||||
/** @var string */
|
||||
public $group;
|
||||
|
||||
/** @var string */
|
||||
public $page;
|
||||
|
||||
/** @var string */
|
||||
public $textTabDocument;
|
||||
|
||||
/** @var string */
|
||||
public $routeTabDocument;
|
||||
|
||||
/** @var string */
|
||||
public $routeTabRecurring;
|
||||
|
||||
/** @var string */
|
||||
public $routeParamsTabUnpaid;
|
||||
|
||||
/** @var string */
|
||||
public $routeParamsTabDraft;
|
||||
|
||||
/** @var string */
|
||||
public $textPage;
|
||||
|
||||
/** @var string */
|
||||
public $permissionCreate;
|
||||
|
||||
/** @var string */
|
||||
public $permissionUpdate;
|
||||
|
||||
/** @var string */
|
||||
public $permissionDelete;
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
/** @var bool */
|
||||
public $hideAcceptPayment;
|
||||
|
||||
/** @var bool */
|
||||
public $checkPermissionCreate;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCreate;
|
||||
|
||||
/** @var bool */
|
||||
public $hideImport;
|
||||
|
||||
/** @var bool */
|
||||
public $hideExport;
|
||||
|
||||
/** @var string */
|
||||
public $createRoute;
|
||||
|
||||
/** @var string */
|
||||
public $importRoute;
|
||||
|
||||
/** @var array */
|
||||
public $importRouteParameters;
|
||||
|
||||
/** @var string */
|
||||
public $exportRoute;
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
/** @var bool */
|
||||
public $hideEmptyPage;
|
||||
|
||||
/** @var array */
|
||||
public $emptyPageButtons;
|
||||
|
||||
/** @var string */
|
||||
public $imageEmptyPage;
|
||||
|
||||
/** @var string */
|
||||
public $textEmptyPage;
|
||||
|
||||
/** @var string */
|
||||
public $urlDocsPath;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSummary;
|
||||
|
||||
/** @var array */
|
||||
public $summaryItems;
|
||||
|
||||
/** @var bool */
|
||||
public $withoutTabs;
|
||||
|
||||
/** @var string */
|
||||
public $tabActive;
|
||||
|
||||
/** @var string */
|
||||
public $tabSuffix;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRecurringTemplates;
|
||||
|
||||
/** @var bool */
|
||||
public $hideSearchString;
|
||||
|
||||
/** @var bool */
|
||||
public $hideBulkAction;
|
||||
|
||||
/** @var string */
|
||||
public $searchStringModel;
|
||||
|
||||
/** @var string */
|
||||
public $bulkActionClass;
|
||||
|
||||
/** @var array */
|
||||
public $bulkActionRouteParameters;
|
||||
|
||||
/** @var string */
|
||||
public $searchRoute;
|
||||
|
||||
/** @var string */
|
||||
public $classBulkAction;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDueAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideIssuedAt;
|
||||
|
||||
/** @var string */
|
||||
public $classDueAtAndIssueAt;
|
||||
|
||||
/** @var string */
|
||||
public $textDueAt;
|
||||
|
||||
/** @var string */
|
||||
public $textIssuedAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideStartedAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideEndedAt;
|
||||
|
||||
/** @var string */
|
||||
public $classStartedAtAndEndedAt;
|
||||
|
||||
/** @var string */
|
||||
public $textStartedAt;
|
||||
|
||||
/** @var string */
|
||||
public $textEndedAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideStatus;
|
||||
|
||||
/** @var string */
|
||||
public $classStatus;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactName;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCategory;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCurrency;
|
||||
|
||||
/** @var string */
|
||||
public $textCategory;
|
||||
|
||||
/** @var bool */
|
||||
public $hideFrequency;
|
||||
|
||||
/** @var string */
|
||||
public $classFrequencyAndDuration;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDuration;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDocumentNumber;
|
||||
|
||||
/** @var string */
|
||||
public $classContactNameAndDocumentNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textContactName;
|
||||
|
||||
/** @var string */
|
||||
public $showContactRoute;
|
||||
|
||||
/** @var string */
|
||||
public $textDocumentNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideAmount;
|
||||
|
||||
/** @var string */
|
||||
public $classAmount;
|
||||
|
||||
/** @var bool */
|
||||
public $hideShow;
|
||||
|
||||
/** @var string */
|
||||
public $showRoute;
|
||||
|
||||
/** @var bool */
|
||||
public $hideEdit;
|
||||
|
||||
/** @var string */
|
||||
public $editRoute;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDuplicate;
|
||||
|
||||
/** @var string */
|
||||
public $duplicateRoute;
|
||||
|
||||
/** @var string */
|
||||
public $textDocumentStatus;
|
||||
|
||||
/** @var bool */
|
||||
public $checkButtonReconciled;
|
||||
|
||||
/** @var bool */
|
||||
public $checkButtonCancelled;
|
||||
/* -- Content End -- */
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $type, string $contact_type = '', string $category_type = '', string $alias = '', $documents = [], int $totalDocuments = null, string $group = '', string $page = '', string $textTabDocument = '', string $textPage = '',
|
||||
string $routeTabDocument = '', string $routeTabRecurring = '', string $routeParamsTabUnpaid = '', string $routeParamsTabDraft = '',
|
||||
string $permissionCreate = '', string $permissionUpdate = '', string $permissionDelete = '',
|
||||
bool $hideAcceptPayment = false, bool $checkPermissionCreate = true,
|
||||
bool $hideCreate = false, bool $hideImport = false, bool $hideExport = false,
|
||||
string $createRoute = '', string $importRoute = '', array $importRouteParameters = [], string $exportRoute = '',
|
||||
bool $hideEmptyPage = false, array $emptyPageButtons = [], string $imageEmptyPage = '', string $textEmptyPage = '', string $urlDocsPath = '',
|
||||
bool $hideSummary = false, array $summaryItems = [],
|
||||
bool $withoutTabs = false, string $tabActive = '', string $tabSuffix = '', bool $hideRecurringTemplates = false,
|
||||
bool $hideSearchString = false, bool $hideBulkAction = false,
|
||||
string $searchStringModel = '', string $bulkActionClass = '', array $bulkActions = [], array $bulkActionRouteParameters = [], string $searchRoute = '', string $classBulkAction = '',
|
||||
bool $hideDueAt = false, bool $hideIssuedAt = false, string $classDueAtAndIssueAt = '', string $textDueAt = '', string $textIssuedAt = '',
|
||||
bool $hideStartedAt = false, bool $hideEndedAt = false, string $classStartedAtAndEndedAt = '', string $textStartedAt = '', string $textEndedAt = '',
|
||||
bool $hideStatus = false, string $classStatus = '',
|
||||
bool $hideCategory = false, string $textCategory = '', bool $hideCurrency = false,
|
||||
bool $hideFrequency = false, bool $hideDuration = false, string $classFrequencyAndDuration = '',
|
||||
bool $hideContactName = false, bool $hideDocumentNumber = false, string $classContactNameAndDocumentNumber = '', string $textContactName = '', string $showContactRoute = '', string $textDocumentNumber = '',
|
||||
bool $hideAmount = false, string $classAmount = '',
|
||||
bool $hideShow = false, string $showRoute = '', bool $hideEdit = false, string $editRoute = '', bool $hideDuplicate = false, string $duplicateRoute = '',
|
||||
string $textDocumentStatus = '',
|
||||
bool $checkButtonReconciled = true, bool $checkButtonCancelled = true
|
||||
) {
|
||||
/* -- Main Start -- */
|
||||
$this->type = $type;
|
||||
$this->contact_type = $this->getTypeContact($type, $contact_type);
|
||||
$this->category_type = $this->getTypeCategory($type, $category_type);
|
||||
$this->alias = $this->getAlias($type, $alias);
|
||||
$this->documents = ($documents) ? $documents : collect();
|
||||
$this->totalDocuments = $this->getTotalDocuments($totalDocuments);
|
||||
$this->group = $this->getGroup($type, $group);
|
||||
$this->page = $this->getPage($type, $page);
|
||||
$this->textTabDocument = $this->getTextTabDocument($type, $textTabDocument);
|
||||
$this->textPage = $this->getTextPage($type, $textPage);
|
||||
|
||||
$this->permissionCreate = $this->getPermissionCreate($type, $permissionCreate);
|
||||
$this->permissionUpdate = $this->getPermissionUpdate($type, $permissionUpdate);
|
||||
$this->permissionDelete = $this->getPermissionDelete($type, $permissionDelete);
|
||||
|
||||
$this->routeTabDocument = $this->getRouteTabDocument($type, $routeTabDocument);
|
||||
$this->routeParamsTabUnpaid = $this->getRouteParamsTabUnpaid($type, $routeParamsTabUnpaid);
|
||||
$this->routeParamsTabDraft = $this->getRouteParamsTabDraft($type, $routeParamsTabDraft);
|
||||
$this->routeTabRecurring = $this->getRouteTabRecurring($type, $routeTabRecurring);
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
$this->hideAcceptPayment = $hideAcceptPayment;
|
||||
|
||||
$this->checkPermissionCreate = $checkPermissionCreate;
|
||||
|
||||
$this->hideCreate = $hideCreate;
|
||||
$this->hideImport = $hideImport;
|
||||
$this->hideExport = $hideExport;
|
||||
|
||||
$this->createRoute = $this->getCreateRoute($type, $createRoute);
|
||||
$this->importRoute = $this->getImportRoute($importRoute);
|
||||
$this->importRouteParameters = $this->getImportRouteParameters($type, $importRouteParameters);
|
||||
$this->exportRoute = $this->getExportRoute($type, $exportRoute);
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
/* -- Empty Page Start -- */
|
||||
$this->hideEmptyPage = $this->getHideEmptyPage($hideEmptyPage);
|
||||
|
||||
$this->emptyPageButtons = $this->getEmptyPageButtons($type, $emptyPageButtons);
|
||||
$this->imageEmptyPage = $this->getImageEmptyPage($type, $imageEmptyPage);
|
||||
$this->textEmptyPage = $this->getTextEmptyPage($type, $textEmptyPage);
|
||||
$this->urlDocsPath = $this->getUrlDocsPath($type, $urlDocsPath);
|
||||
/* -- Empty Page End -- */
|
||||
|
||||
/* -- Summary Start -- */
|
||||
$this->hideSummary = $hideSummary;
|
||||
$this->summaryItems = $this->getSummaryItems($type, $summaryItems);
|
||||
/* -- Sumamry End -- */
|
||||
|
||||
/* Container Start */
|
||||
$this->withoutTabs = $withoutTabs;
|
||||
$this->tabSuffix = $this->getTabSuffix($type, $tabSuffix);
|
||||
$this->tabActive = $this->getTabActive($type, $tabActive);
|
||||
|
||||
$this->hideRecurringTemplates = $hideRecurringTemplates;
|
||||
|
||||
$this->hideSearchString = $hideSearchString;
|
||||
$this->hideBulkAction = $hideBulkAction;
|
||||
|
||||
$this->searchStringModel = $this->getSearchStringModel($type, $searchStringModel);
|
||||
|
||||
$this->bulkActionClass = $this->getBulkActionClass($type, $bulkActionClass);
|
||||
$this->bulkActionRouteParameters = $this->getBulkActionRouteParameters($type, $bulkActionRouteParameters);
|
||||
|
||||
$this->searchRoute = $this->getIndexRoute($type, $searchRoute);
|
||||
|
||||
$this->classBulkAction = $this->getClassBulkAction($type, $classBulkAction);
|
||||
|
||||
/* Document Start */
|
||||
$this->hideDueAt = $hideDueAt;
|
||||
$this->hideIssuedAt = $hideIssuedAt;
|
||||
$this->classDueAtAndIssueAt = $this->getClassDueAtAndIssueAt($type, $classDueAtAndIssueAt);
|
||||
$this->textDueAt = $this->getTextDueAt($type, $textDueAt);
|
||||
$this->textIssuedAt = $this->getTextIssuedAt($type, $textIssuedAt);
|
||||
|
||||
$this->hideStartedAt = $hideStartedAt;
|
||||
$this->hideEndedAt = $hideEndedAt;
|
||||
$this->classStartedAtAndEndedAt = $this->getClassStartedAndEndedAt($type, $classStartedAtAndEndedAt);
|
||||
$this->textStartedAt = $this->getTextStartedAt($type, $textStartedAt);
|
||||
$this->textEndedAt = $this->getTextEndedAt($type, $textEndedAt);
|
||||
|
||||
$this->hideStatus = $hideStatus;
|
||||
$this->classStatus = $this->getClassStatus($type, $classStatus);
|
||||
|
||||
$this->hideCategory = $hideCategory;
|
||||
$this->textCategory = $this->getTextCategory($type, $textCategory);
|
||||
$this->hideCurrency = $hideCurrency;
|
||||
|
||||
$this->hideFrequency = $hideFrequency;
|
||||
$this->hideDuration = $hideDuration;
|
||||
$this->classFrequencyAndDuration = $this->getClassFrequencyAndDuration($type, $classFrequencyAndDuration);
|
||||
|
||||
$this->hideContactName = $hideContactName;
|
||||
$this->hideDocumentNumber = $hideDocumentNumber;
|
||||
$this->classContactNameAndDocumentNumber = $this->getClassContactNameAndDocumentNumber($type, $classContactNameAndDocumentNumber);
|
||||
$this->textContactName = $this->getTextContactName($type, $textContactName);
|
||||
$this->showContactRoute = $this->getShowContactRoute($type, $showContactRoute);
|
||||
$this->textDocumentNumber = $this->getTextDocumentNumber($type, $textDocumentNumber);
|
||||
|
||||
$this->hideAmount = $hideAmount;
|
||||
$this->classAmount = $this->getClassAmount($type, $classAmount);
|
||||
|
||||
$this->hideShow = $hideShow;
|
||||
$this->showRoute = $this->getShowRoute($type, $showRoute);
|
||||
|
||||
$this->hideEdit = $hideEdit;
|
||||
$this->editRoute = $this->getEditRoute($type, $editRoute);
|
||||
|
||||
$this->hideDuplicate = $hideDuplicate;
|
||||
$this->duplicateRoute = $this->getDuplicateRoute($type, $duplicateRoute);
|
||||
|
||||
$this->textDocumentStatus = $this->getTextDocumentStatus($type, $textDocumentStatus);
|
||||
|
||||
$this->checkButtonReconciled = $checkButtonReconciled;
|
||||
$this->checkButtonCancelled = $checkButtonCancelled;
|
||||
/* Document End */
|
||||
/* Container End */
|
||||
/* -- Content End -- */
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
protected function getTypeContact($type, $typeContact)
|
||||
{
|
||||
if (! empty($typeContact)) {
|
||||
return $typeContact;
|
||||
}
|
||||
|
||||
return config('type.' . static::OBJECT_TYPE . '.' . $type . '.contact_type', 'customer');
|
||||
}
|
||||
|
||||
protected function getTypeCategory($type, $typeCategory)
|
||||
{
|
||||
if (!empty($typeCategory)) {
|
||||
return $typeCategory;
|
||||
}
|
||||
|
||||
if ($category_type = config('type.' . static::OBJECT_TYPE . '.' . $type . '.category_type')) {
|
||||
return $category_type;
|
||||
}
|
||||
|
||||
// set default type
|
||||
$type = Document::INVOICE_TYPE;
|
||||
|
||||
return config('type.' . static::OBJECT_TYPE .'.' . $type . '.category_type');
|
||||
}
|
||||
|
||||
protected function getTotalDocuments($totalDocuments)
|
||||
{
|
||||
if (! is_null($totalDocuments)) {
|
||||
return $totalDocuments;
|
||||
}
|
||||
|
||||
return $this->documents->count();
|
||||
}
|
||||
|
||||
protected function getHideEmptyPage($hideEmptyPage): bool
|
||||
{
|
||||
if ($hideEmptyPage) {
|
||||
return $hideEmptyPage;
|
||||
}
|
||||
|
||||
if ($this->totalDocuments > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request()->has('search') && ($this->totalDocuments > 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getEmptyPageButtons($type, $emptyPageButtons)
|
||||
{
|
||||
if (! empty($emptyPageButtons)) {
|
||||
return $emptyPageButtons;
|
||||
}
|
||||
|
||||
$prefix = config('type.' . static::OBJECT_TYPE . '.' . $type . '.route.prefix', 'invoices');
|
||||
|
||||
$buttons = [];
|
||||
|
||||
if (! $this->hideCreate) {
|
||||
$route = $this->getRouteFromConfig($type, 'create');
|
||||
|
||||
$buttons[] = [
|
||||
'permission' => $this->permissionCreate,
|
||||
'url' => route($this->createRoute),
|
||||
'text' => trans('general.title.new', ['type' => trans_choice($this->textPage ?? 'general.' . $prefix, 1)]),
|
||||
'description' => trans('general.empty.actions.new', ['type' => strtolower(trans_choice($this->textPage ?? 'general.' . $prefix, 1))]),
|
||||
'active_badge' => true,
|
||||
'stack' => 'create_button',
|
||||
];
|
||||
}
|
||||
|
||||
if (! $this->hideImport) {
|
||||
$route = $this->getRouteFromConfig($type, 'import');
|
||||
|
||||
$buttons[] = [
|
||||
'permission' => $this->permissionCreate,
|
||||
'url' => route($this->importRoute, $this->importRouteParameters),
|
||||
'text' => trans('import.title', ['type' => trans_choice($this->textPage ?? 'general.' . $prefix, 2)]),
|
||||
'description' => trans('general.empty.actions.import', ['type' => strtolower(trans_choice($this->textPage ?? 'general.' . $prefix, 2))]),
|
||||
'stack' => 'import_button',
|
||||
];
|
||||
}
|
||||
|
||||
return $buttons;
|
||||
}
|
||||
|
||||
public function getSummaryItems($type, $summaryItems)
|
||||
{
|
||||
if (! empty($summaryItems)) {
|
||||
return $summaryItems;
|
||||
}
|
||||
|
||||
$route = $this->getIndexRoute($type, null);
|
||||
|
||||
$totals = $this->getTotalsForFutureDocuments($type);
|
||||
|
||||
$items = [];
|
||||
|
||||
foreach ($totals as $key => $total) {
|
||||
$title = ($key == 'overdue') ? trans('general.overdue') : trans('documents.statuses.' . $key);
|
||||
$href = route($route, ['search' => 'status:' . $key]);
|
||||
$amount = money($total)->formatForHumans();
|
||||
$tooltip = money($total)->format();
|
||||
|
||||
$items[] = [
|
||||
'title' => $title,
|
||||
//'href' => $href,
|
||||
'amount' => $amount,
|
||||
'tooltip' => $tooltip,
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
protected function getTextTabDocument($type, $textTabDocument)
|
||||
{
|
||||
if (! empty($textTabDocument)) {
|
||||
return $textTabDocument;
|
||||
}
|
||||
|
||||
$default_key = config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.prefix');
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'tab_document', $default_key);
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.invoices';
|
||||
}
|
||||
|
||||
public function getTabActive($type, $tabActive)
|
||||
{
|
||||
if (! empty($tabActive)) {
|
||||
return $tabActive;
|
||||
}
|
||||
|
||||
return $type . '-' . $this->tabSuffix;
|
||||
}
|
||||
|
||||
public function getTabSuffix($type, $tabSuffix)
|
||||
{
|
||||
if (! empty($tabSuffix)) {
|
||||
return $tabSuffix;
|
||||
}
|
||||
|
||||
if (request()->get('list_records') == 'all') {
|
||||
return 'all';
|
||||
}
|
||||
|
||||
$status = $this->getSearchStringValue('status');
|
||||
|
||||
$unpaid = str_replace('status:', '', config('type.document.' . $type . '.route.params.unpaid.search'));
|
||||
|
||||
if ($status == $unpaid) {
|
||||
return 'unpaid';
|
||||
}
|
||||
|
||||
$draft = str_replace('status:', '', config('type.document.' . $type . '.route.params.draft.search'));
|
||||
|
||||
if ($status == $draft) {
|
||||
return 'draft';
|
||||
}
|
||||
|
||||
$suffix = $this->getTabActiveFromSetting($type);
|
||||
|
||||
if (! empty($suffix)) {
|
||||
return $suffix;
|
||||
}
|
||||
|
||||
return 'unpaid';
|
||||
}
|
||||
|
||||
protected function getRouteTabDocument($type, $routeTabDocument)
|
||||
{
|
||||
if (! empty($routeTabDocument)) {
|
||||
return $routeTabDocument;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'document', 'invoices');
|
||||
|
||||
if (! empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
return 'invoices.index';
|
||||
}
|
||||
|
||||
protected function getRouteParamsTabUnpaid($type, $routeParamsTabUnpaid)
|
||||
{
|
||||
if (! empty($routeParamsTabUnpaid)) {
|
||||
return $routeParamsTabUnpaid;
|
||||
}
|
||||
|
||||
$params = $this->getRouteParamsFromConfig($type, 'unpaid');
|
||||
|
||||
if (! empty($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
return ['search' => 'status:sent,viewed,partial'];
|
||||
}
|
||||
|
||||
protected function getRouteParamsTabDraft($type, $routeParamsTabDraft)
|
||||
{
|
||||
if (! empty($routeParamsTabDraft)) {
|
||||
return $routeParamsTabDraft;
|
||||
}
|
||||
|
||||
$params = $this->getRouteParamsFromConfig($type, 'draft');
|
||||
|
||||
if (! empty($params)) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
return ['search' => 'status:draft'];
|
||||
}
|
||||
|
||||
protected function getRouteTabRecurring($type, $routeTabDocument)
|
||||
{
|
||||
if (! empty($routeTabDocument)) {
|
||||
return $routeTabDocument;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'recurring', 'recurring-invoices');
|
||||
|
||||
if (! empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
return 'recurring-invoices.index';
|
||||
}
|
||||
|
||||
protected function getClassDueAtAndIssueAt($type, $classDueAtAndIssueAt)
|
||||
{
|
||||
if (! empty($classDueAtAndIssueAt)) {
|
||||
return $classDueAtAndIssueAt;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'due_at_and_issue_at');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 table-title hidden sm:table-cell';
|
||||
}
|
||||
|
||||
protected function getTextDueAt($type, $textDueAt)
|
||||
{
|
||||
if (! empty($textDueAt)) {
|
||||
return $textDueAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'due_at', 'due_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.due_date';
|
||||
}
|
||||
|
||||
protected function getTextIssuedAt($type, $textIssuedAt)
|
||||
{
|
||||
if (! empty($textIssuedAt)) {
|
||||
return $textIssuedAt;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_date';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'invoice_date';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'issued_at', $default_key);
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.invoice_date';
|
||||
}
|
||||
|
||||
protected function getClassStartedAndEndedAt($type, $classStartedAtAndEndedAt)
|
||||
{
|
||||
if (! empty($classStartedAtAndEndedAt)) {
|
||||
return $classStartedAtAndEndedAt;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'started_at_and_end_at');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 table-title hidden sm:table-cell';
|
||||
}
|
||||
|
||||
protected function getClassFrequencyAndDuration($type, $classFrequencyAndDuration)
|
||||
{
|
||||
if (! empty($classFrequencyAndDuration)) {
|
||||
return $classFrequencyAndDuration;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'frequency_and_duration');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-2/12';
|
||||
}
|
||||
|
||||
protected function getTextStartedAt($type, $textStartedAt)
|
||||
{
|
||||
if (! empty($textStartedAt)) {
|
||||
return $textStartedAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'started_at', 'started_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.start_date';
|
||||
}
|
||||
|
||||
protected function getTextEndedAt($type, $textEndedAt)
|
||||
{
|
||||
if (! empty($textEndedAt)) {
|
||||
return $textEndedAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'ended_at', 'ended_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'recurring.last_issued';
|
||||
}
|
||||
|
||||
protected function getClassStatus($type, $classStatus)
|
||||
{
|
||||
if (! empty($classStatus)) {
|
||||
return $classStatus;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'status');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-3/12 table-title hidden sm:table-cell';
|
||||
}
|
||||
|
||||
protected function getTextCategory($type, $textCategory)
|
||||
{
|
||||
if (!empty($textCategory)) {
|
||||
return $textCategory;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'categories', 'categories', 'trans_choice');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.categories';
|
||||
}
|
||||
|
||||
protected function getClassContactNameAndDocumentNumber($type, $classContactNameAndDocumentNumber)
|
||||
{
|
||||
if (! empty($classContactNameAndDocumentNumber)) {
|
||||
return $classContactNameAndDocumentNumber;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'contact_name');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-6/12 sm:w-3/12 table-title';
|
||||
}
|
||||
|
||||
protected function getTextContactName($type, $textContactName)
|
||||
{
|
||||
if (! empty($textContactName)) {
|
||||
return $textContactName;
|
||||
}
|
||||
|
||||
$default_key = Str::plural(config('type.' . static::OBJECT_TYPE . '.' . $type . '.contact_type'), 2);
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'contact_name', $default_key, 'trans_choice');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.customers';
|
||||
}
|
||||
|
||||
protected function getShowContactRoute($type, $showContactRoute)
|
||||
{
|
||||
if (! empty($showContactRoute)) {
|
||||
return $showContactRoute;
|
||||
}
|
||||
|
||||
|
||||
if (! empty($showRoute)) {
|
||||
return $showRoute;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'contact.show', 1);
|
||||
|
||||
if (!empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
$default_key = Str::plural(config('type.' . static::OBJECT_TYPE . '.' . $type . '.contact_type'), 2);
|
||||
|
||||
return $default_key . '.show';
|
||||
}
|
||||
|
||||
protected function getTextDocumentNumber($type, $textDocumentNumber)
|
||||
{
|
||||
if (! empty($textDocumentNumber)) {
|
||||
return $textDocumentNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document_number', 'numbers');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.numbers';
|
||||
}
|
||||
|
||||
protected function getClassAmount($type, $classAmount)
|
||||
{
|
||||
if (! empty($classAmount)) {
|
||||
return $classAmount;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'amount');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-6/12 sm:w-2/12';
|
||||
}
|
||||
|
||||
protected function getTextDocumentStatus($type, $textDocumentStatus)
|
||||
{
|
||||
if (! empty($textDocumentStatus)) {
|
||||
return $textDocumentStatus;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document_status', 'statuses.');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
$alias = config('type.' . static::OBJECT_TYPE . '.' . $type . '.alias');
|
||||
|
||||
if (! empty($alias)) {
|
||||
$translation = $alias . '::' . config('type.' . static::OBJECT_TYPE . '.' . $type . '.translation.prefix') . '.statuses';
|
||||
|
||||
if (is_array(trans($translation))) {
|
||||
return $translation . '.';
|
||||
}
|
||||
}
|
||||
|
||||
return 'documents.statuses.';
|
||||
}
|
||||
}
|
||||
1434
app/Abstracts/View/Components/Documents/Show.php
Normal file
1434
app/Abstracts/View/Components/Documents/Show.php
Normal file
File diff suppressed because it is too large
Load Diff
767
app/Abstracts/View/Components/Documents/Template.php
Normal file
767
app/Abstracts/View/Components/Documents/Template.php
Normal file
@ -0,0 +1,767 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Documents;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Models\Common\Media;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Documents;
|
||||
use App\Traits\Tailwind;
|
||||
use App\Traits\ViewComponents;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use Image;
|
||||
use ReflectionProperty;
|
||||
|
||||
abstract class Template extends Component
|
||||
{
|
||||
use DateTime, Documents, Tailwind, ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'document';
|
||||
public const DEFAULT_TYPE = 'invoice';
|
||||
public const DEFAULT_PLURAL_TYPE = 'invoices';
|
||||
|
||||
public $type;
|
||||
|
||||
public $item;
|
||||
|
||||
public $document;
|
||||
|
||||
/** @var string */
|
||||
public $documentTemplate;
|
||||
|
||||
public $date_format;
|
||||
|
||||
public $logo;
|
||||
|
||||
public $backgroundColor;
|
||||
|
||||
public $hideFooter;
|
||||
|
||||
public $hideCompanyLogo;
|
||||
|
||||
public $hideCompanyDetails;
|
||||
|
||||
public $hideCompanyName;
|
||||
|
||||
public $hideCompanyAddress;
|
||||
|
||||
public $hideCompanyTaxNumber;
|
||||
|
||||
public $hideCompanyPhone;
|
||||
|
||||
public $hideCompanyEmail;
|
||||
|
||||
public $hideContactInfo;
|
||||
|
||||
public $hideContactName;
|
||||
|
||||
public $hideContactAddress;
|
||||
|
||||
public $hideContactTaxNumber;
|
||||
|
||||
public $hideContactPhone;
|
||||
|
||||
public $hideContactEmail;
|
||||
|
||||
public $hideOrderNumber;
|
||||
|
||||
public $hideDocumentNumber;
|
||||
|
||||
public $hideIssuedAt;
|
||||
|
||||
public $hideDueAt;
|
||||
|
||||
/** @var string */
|
||||
public $textDocumentTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textDocumentSubheading;
|
||||
|
||||
public $textContactInfo;
|
||||
|
||||
/** @var string */
|
||||
public $textIssuedAt;
|
||||
|
||||
/** @var string */
|
||||
public $textDueAt;
|
||||
|
||||
/** @var string */
|
||||
public $textDocumentNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textOrderNumber;
|
||||
|
||||
/** @var string */
|
||||
public $showContactRoute;
|
||||
|
||||
public $hideItems;
|
||||
|
||||
public $hideName;
|
||||
|
||||
public $hideDescription;
|
||||
|
||||
public $hideQuantity;
|
||||
|
||||
public $hidePrice;
|
||||
|
||||
public $hideDiscount;
|
||||
|
||||
public $hideAmount;
|
||||
|
||||
/** @var string */
|
||||
public $textItems;
|
||||
|
||||
/** @var string */
|
||||
public $textQuantity;
|
||||
|
||||
/** @var string */
|
||||
public $textPrice;
|
||||
|
||||
/** @var string */
|
||||
public $textAmount;
|
||||
|
||||
public $hideNote;
|
||||
|
||||
/** @var bool */
|
||||
public $print;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
$type, $document, $item = false, $documentTemplate = '', $logo = '', $backgroundColor = '',
|
||||
bool $hideFooter = false, bool $hideCompanyLogo = false, bool $hideCompanyDetails = false,
|
||||
bool $hideCompanyName = false, bool $hideCompanyAddress = false, bool $hideCompanyTaxNumber = false, bool $hideCompanyPhone = false, bool $hideCompanyEmail = false, bool $hideContactInfo = false,
|
||||
bool $hideContactName = false, bool $hideContactAddress = false, bool $hideContactTaxNumber = false, bool $hideContactPhone = false, bool $hideContactEmail = false,
|
||||
bool $hideOrderNumber = false, bool $hideDocumentNumber = false, bool $hideIssuedAt = false, bool $hideDueAt = false,
|
||||
string $textDocumentTitle = '', string $textDocumentSubheading = '',
|
||||
string $textContactInfo = '', string $textDocumentNumber = '', string $textOrderNumber = '', string $textIssuedAt = '', string $textDueAt = '', string $showContactRoute = '',
|
||||
bool $hideItems = false, bool $hideName = false, bool $hideDescription = false, bool $hideQuantity = false, bool $hidePrice = false, bool $hideDiscount = false, bool $hideAmount = false, bool $hideNote = false,
|
||||
string $textItems = '', string $textQuantity = '', string $textPrice = '', string $textAmount = '', bool $print = false
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->item = $item;
|
||||
$this->document = $document;
|
||||
$this->documentTemplate = $this->getDocumentTemplate($type, $documentTemplate);
|
||||
$this->logo = $this->getLogo($logo);
|
||||
$this->backgroundColor = $this->getBackgroundColor($type, $backgroundColor);
|
||||
|
||||
$this->hideFooter = $hideFooter;
|
||||
$this->hideCompanyLogo = $hideCompanyLogo;
|
||||
$this->hideCompanyDetails = $hideCompanyDetails;
|
||||
$this->hideCompanyName = $hideCompanyName;
|
||||
$this->hideCompanyAddress = $hideCompanyAddress;
|
||||
$this->hideCompanyTaxNumber = $hideCompanyTaxNumber;
|
||||
$this->hideCompanyPhone = $hideCompanyPhone;
|
||||
$this->hideCompanyEmail = $hideCompanyEmail;
|
||||
$this->hideContactInfo = $hideContactInfo;
|
||||
$this->hideContactName = $hideContactName;
|
||||
$this->hideContactAddress = $hideContactAddress;
|
||||
$this->hideContactTaxNumber = $hideContactTaxNumber;
|
||||
$this->hideContactPhone = $hideContactPhone;
|
||||
$this->hideContactEmail = $hideContactEmail;
|
||||
$this->hideOrderNumber = $hideOrderNumber;
|
||||
$this->hideDocumentNumber = $hideDocumentNumber;
|
||||
$this->hideIssuedAt = $hideIssuedAt;
|
||||
$this->hideDueAt = $hideDueAt;
|
||||
|
||||
$this->textDocumentTitle = $this->getTextDocumentTitle($type, $textDocumentTitle);
|
||||
$this->textDocumentSubheading = $this->getTextDocumentSubheading($type, $textDocumentSubheading);
|
||||
$this->textContactInfo = $this->getTextContactInfo($type, $textContactInfo);
|
||||
$this->showContactRoute = $this->getShowContactRoute($type, $showContactRoute);
|
||||
$this->textIssuedAt = $this->getTextIssuedAt($type, $textIssuedAt);
|
||||
$this->textDocumentNumber = $this->getTextDocumentNumber($type, $textDocumentNumber);
|
||||
$this->textDueAt = $this->getTextDueAt($type, $textDueAt);
|
||||
$this->textOrderNumber = $this->getTextOrderNumber($type, $textOrderNumber);
|
||||
|
||||
$this->hideItems = $this->getHideItems($type, $hideItems, $hideName, $hideDescription);
|
||||
$this->hideName = $this->getHideName($type, $hideName);
|
||||
$this->hideDescription = $this->getHideDescription($type, $hideDescription);
|
||||
$this->hideQuantity = $this->getHideQuantity($type, $hideQuantity);
|
||||
$this->hidePrice = $this->getHidePrice($type, $hidePrice);
|
||||
$this->hideDiscount = $this->getHideDiscount($type, $hideDiscount);
|
||||
$this->hideAmount = $this->getHideAmount($type, $hideAmount);
|
||||
$this->hideNote = $hideNote;
|
||||
|
||||
$this->textItems = $this->getTextItems($type, $textItems);
|
||||
$this->textQuantity = $this->getTextQuantity($type, $textQuantity);
|
||||
$this->textPrice = $this->getTextPrice($type, $textPrice);
|
||||
$this->textAmount = $this->getTextAmount($type, $textAmount);
|
||||
|
||||
$this->print = $this->getPrint($print);
|
||||
|
||||
// Set Parent data
|
||||
//$this->setParentData();
|
||||
}
|
||||
|
||||
protected function getDocumentTemplate($type, $documentTemplate)
|
||||
{
|
||||
if (! empty($documentTemplate)) {
|
||||
return $documentTemplate;
|
||||
}
|
||||
|
||||
if (! empty($this->document) && ! empty($this->document->template)) {
|
||||
$document_templates = $this->getDocumentTemplates($type);
|
||||
|
||||
$template = 'default';
|
||||
|
||||
foreach ($document_templates as $template) {
|
||||
if ($template['id'] != $this->document->template) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$template = $template['template'];
|
||||
break;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ($template = config('type.document.' . $type . '.template', false)) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$documentTemplate = setting($this->getDocumentSettingKey($type, 'template'), 'default');
|
||||
|
||||
return $documentTemplate;
|
||||
}
|
||||
|
||||
protected function getLogo($logo)
|
||||
{
|
||||
if (! empty($logo)) {
|
||||
return $logo;
|
||||
}
|
||||
|
||||
static $content;
|
||||
|
||||
if (! empty($content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$media_id = (! empty($this->document->contact->logo) && ! empty($this->document->contact->logo->id)) ? $this->document->contact->logo->id : setting('company.logo');
|
||||
|
||||
$media = Media::find($media_id);
|
||||
|
||||
if (! empty($media)) {
|
||||
$path = $media->getDiskPath();
|
||||
|
||||
if (! $media->fileExists()) {
|
||||
return $logo;
|
||||
}
|
||||
} else {
|
||||
$path = base_path('public/img/company.png');
|
||||
}
|
||||
|
||||
try {
|
||||
$image = Image::cache(function($image) use ($media, $path) {
|
||||
$width = setting('invoice.logo_size_width');
|
||||
$height = setting('invoice.logo_size_height');
|
||||
|
||||
if ($media) {
|
||||
$image->make($media->contents())->resize($width, $height)->encode();
|
||||
} else {
|
||||
$image->make($path)->resize($width, $height)->encode();
|
||||
}
|
||||
});
|
||||
} catch (NotReadableException | \Exception $e) {
|
||||
Log::info('Company ID: ' . company_id() . ' components/documentshow.php exception.');
|
||||
Log::info($e->getMessage());
|
||||
|
||||
$path = base_path('public/img/company.png');
|
||||
|
||||
$image = Image::cache(function($image) use ($path) {
|
||||
$width = setting('invoice.logo_size_width');
|
||||
$height = setting('invoice.logo_size_height');
|
||||
|
||||
$image->make($path)->resize($width, $height)->encode();
|
||||
});
|
||||
}
|
||||
|
||||
if (empty($image)) {
|
||||
return $logo;
|
||||
}
|
||||
|
||||
$extension = File::extension($path);
|
||||
|
||||
$content = 'data:image/' . $extension . ';base64,' . base64_encode($image);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function getBackgroundColor($type, $backgroundColor)
|
||||
{
|
||||
if (! empty($backgroundColor)) {
|
||||
return $backgroundColor;
|
||||
}
|
||||
|
||||
if (! empty($this->document) && $this->document->color !== '') {
|
||||
return $this->getHexCodeOfTailwindClass($this->document->color);
|
||||
}
|
||||
|
||||
// checking setting color
|
||||
$key = $this->getDocumentSettingKey($type, 'color');
|
||||
|
||||
if (! empty(setting($key))) {
|
||||
$backgroundColor = setting($key);
|
||||
}
|
||||
|
||||
// checking config color
|
||||
if (empty($backgroundColor) && $background_color = config('type.document.' . $type . '.color', false)) {
|
||||
$backgroundColor = $background_color;
|
||||
}
|
||||
|
||||
// set default color
|
||||
if (empty($backgroundColor)) {
|
||||
$backgroundColor = '#55588b';
|
||||
}
|
||||
|
||||
return $this->getHexCodeOfTailwindClass($backgroundColor);
|
||||
}
|
||||
|
||||
protected function getTextDocumentTitle($type, $textDocumentTitle)
|
||||
{
|
||||
if (! empty($textDocumentTitle)) {
|
||||
return $textDocumentTitle;
|
||||
}
|
||||
|
||||
if (! empty($this->document) && $this->document->title !== '') {
|
||||
return $this->document->title;
|
||||
}
|
||||
|
||||
$key = $this->getDocumentSettingKey($type, 'title');
|
||||
|
||||
if (! empty(setting($key))) {
|
||||
return setting($key);
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document_title', Str::plural($type));
|
||||
|
||||
if (! empty($translation)) {
|
||||
return trans_choice($translation, 1);
|
||||
}
|
||||
|
||||
return setting('invoice.title');
|
||||
}
|
||||
|
||||
protected function getTextDocumentSubheading($type, $textDocumentSubheading)
|
||||
{
|
||||
if (! empty($textDocumentSubheading)) {
|
||||
return $textDocumentSubheading;
|
||||
}
|
||||
|
||||
if (! empty($this->document) && $this->document->subheading !== '') {
|
||||
return $this->document->subheading;
|
||||
}
|
||||
|
||||
$key = $this->getDocumentSettingKey($type, 'subheading');
|
||||
|
||||
if (! empty(setting($key))) {
|
||||
return setting($key);
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document_subheading', 'subheading');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return trans($translation);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getTextDocumentNumber($type, $textDocumentNumber)
|
||||
{
|
||||
if (! empty($textDocumentNumber)) {
|
||||
return $textDocumentNumber;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_number';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'invoice_number';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document_number', $default_key);
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.numbers';
|
||||
}
|
||||
|
||||
protected function getTextOrderNumber($type, $textOrderNumber)
|
||||
{
|
||||
if (! empty($textOrderNumber)) {
|
||||
return $textOrderNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'order_number');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.order_number';
|
||||
}
|
||||
|
||||
protected function getTextContactInfo($type, $textContactInfo)
|
||||
{
|
||||
if (! empty($textContactInfo)) {
|
||||
return $textContactInfo;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_from';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'bill_to';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'contact_info', $default_key);
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.bill_to';
|
||||
}
|
||||
|
||||
protected function getTextIssuedAt($type, $textIssuedAt)
|
||||
{
|
||||
if (! empty($textIssuedAt)) {
|
||||
return $textIssuedAt;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_date';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'invoice_date';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'issued_at', $default_key);
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.invoice_date';
|
||||
}
|
||||
|
||||
protected function getTextDueAt($type, $textDueAt)
|
||||
{
|
||||
if (! empty($textDueAt)) {
|
||||
return $textDueAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'due_at', 'due_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.due_date';
|
||||
}
|
||||
|
||||
protected function getTextItems($type, $textItems)
|
||||
{
|
||||
if (! empty($textItems)) {
|
||||
return $textItems;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if (setting($this->getDocumentSettingKey($type, 'item_name'), 'items') === 'custom') {
|
||||
if (empty($textItems = setting($this->getDocumentSettingKey($type, 'item_name_input')))) {
|
||||
$textItems = 'general.items';
|
||||
}
|
||||
|
||||
return $textItems;
|
||||
}
|
||||
|
||||
if (setting($this->getDocumentSettingKey($type, 'item_name')) !== null
|
||||
&& (trans(setting($this->getDocumentSettingKey($type, 'item_name'))) != setting($this->getDocumentSettingKey($type, 'item_name')))
|
||||
) {
|
||||
return setting($this->getDocumentSettingKey($type, 'item_name'));
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'items');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.items';
|
||||
}
|
||||
|
||||
protected function getTextQuantity($type, $textQuantity)
|
||||
{
|
||||
if (! empty($textQuantity)) {
|
||||
return $textQuantity;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if (setting($this->getDocumentSettingKey($type, 'quantity_name'), 'quantity') === 'custom') {
|
||||
if (empty($textQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name_input')))) {
|
||||
$textQuantity = 'invoices.quantity';
|
||||
}
|
||||
|
||||
return $textQuantity;
|
||||
}
|
||||
|
||||
if (setting($this->getDocumentSettingKey($type, 'quantity_name')) !== null
|
||||
&& (trans(setting($this->getDocumentSettingKey($type, 'quantity_name'))) != setting($this->getDocumentSettingKey($type, 'quantity_name')))
|
||||
) {
|
||||
return setting($this->getDocumentSettingKey($type, 'quantity_name'));
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'quantity');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.quantity';
|
||||
}
|
||||
|
||||
protected function getTextPrice($type, $textPrice)
|
||||
{
|
||||
if (! empty($textPrice)) {
|
||||
return $textPrice;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if (setting($this->getDocumentSettingKey($type, 'price_name'), 'price') === 'custom') {
|
||||
if (empty($textPrice = setting($this->getDocumentSettingKey($type, 'price_name_input')))) {
|
||||
$textPrice = 'invoices.price';
|
||||
}
|
||||
|
||||
return $textPrice;
|
||||
}
|
||||
|
||||
if (setting($this->getDocumentSettingKey($type, 'price_name')) !== null
|
||||
&& (trans(setting($this->getDocumentSettingKey($type, 'price_name'))) != setting($this->getDocumentSettingKey($type, 'price_name')))
|
||||
) {
|
||||
return setting($this->getDocumentSettingKey($type, 'price_name'));
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'price');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.price';
|
||||
}
|
||||
|
||||
protected function getTextAmount($type, $textAmount)
|
||||
{
|
||||
if (! empty($textAmount)) {
|
||||
return $textAmount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'amount');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.amount';
|
||||
}
|
||||
|
||||
protected function getShowContactRoute($type, $showContactRoute)
|
||||
{
|
||||
if (! empty($showContactRoute)) {
|
||||
return $showContactRoute;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'contact.show', 1);
|
||||
|
||||
if (!empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
$default_key = Str::plural(config('type.' . static::OBJECT_TYPE . '.' . $type . '.contact_type'), 2);
|
||||
|
||||
return $default_key . '.show';
|
||||
}
|
||||
|
||||
protected function getHideItems($type, $hideItems, $hideName, $hideDescription)
|
||||
{
|
||||
if (! empty($hideItems)) {
|
||||
return $hideItems;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'items');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
$hideItems = ($this->getHideName($type, $hideName) & $this->getHideDescription($type, $hideDescription)) ? true : false;
|
||||
|
||||
return $hideItems;
|
||||
}
|
||||
|
||||
protected function getHideName($type, $hideName)
|
||||
{
|
||||
if (! empty($hideName)) {
|
||||
return $hideName;
|
||||
}
|
||||
|
||||
$hideName = setting($this->getDocumentSettingKey($type, 'item_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideName === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'name');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDescription($type, $hideDescription)
|
||||
{
|
||||
if (! empty($hideDescription)) {
|
||||
return $hideDescription;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_item_description'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'description');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideQuantity($type, $hideQuantity)
|
||||
{
|
||||
if (! empty($hideQuantity)) {
|
||||
return $hideQuantity;
|
||||
}
|
||||
|
||||
$hideQuantity = setting($this->getDocumentSettingKey($type, 'quantity_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideQuantity === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'quantity');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHidePrice($type, $hidePrice)
|
||||
{
|
||||
if (! empty($hidePrice)) {
|
||||
return $hidePrice;
|
||||
}
|
||||
|
||||
$hidePrice = setting($this->getDocumentSettingKey($type, 'price_name'), false);
|
||||
|
||||
// if you use settting translation
|
||||
if ($hidePrice === 'hide') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'price');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getHideDiscount($type, $hideDiscount)
|
||||
{
|
||||
if (! empty($hideDiscount)) {
|
||||
return $hideDiscount;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if ($hideDiscount = setting($this->getDocumentSettingKey($type, 'hide_discount'), false)) {
|
||||
return $hideDiscount;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'discount');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
// @todo what return value invoice or always false??
|
||||
return setting('invoice.hide_discount', $hideDiscount);
|
||||
}
|
||||
|
||||
protected function getHideAmount($type, $hideAmount)
|
||||
{
|
||||
if (! empty($hideAmount)) {
|
||||
return $hideAmount;
|
||||
}
|
||||
|
||||
// if you use settting translation
|
||||
if (setting($this->getDocumentSettingKey($type, 'hide_amount'), false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$hide = $this->getHideFromConfig($type, 'amount');
|
||||
|
||||
if ($hide) {
|
||||
return $hide;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getPrint($print)
|
||||
{
|
||||
if (! empty($print)) {
|
||||
return $print;
|
||||
}
|
||||
|
||||
$self = new ReflectionProperty($this::class, 'methodCache');
|
||||
$self->setAccessible(true);
|
||||
|
||||
$values = $self->getValue();
|
||||
|
||||
if (array_key_exists('App\View\Components\Layouts\Admin', $values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
362
app/Abstracts/View/Components/Form.php
Normal file
362
app/Abstracts/View/Components/Form.php
Normal file
@ -0,0 +1,362 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
abstract class Form extends Component
|
||||
{
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
/** @var string */
|
||||
public $name;
|
||||
|
||||
/** @var string */
|
||||
public $label;
|
||||
|
||||
/** @var string */
|
||||
public $id;
|
||||
|
||||
public $value;
|
||||
|
||||
/** @var string */
|
||||
public $valueKey;
|
||||
|
||||
/** @var string */
|
||||
public $placeholder;
|
||||
|
||||
/** @var string */
|
||||
public $searchText;
|
||||
|
||||
/** @var array */
|
||||
public $options;
|
||||
|
||||
/** @var array */
|
||||
public $option;
|
||||
|
||||
/** @var string */
|
||||
public $optionKey;
|
||||
|
||||
/** @var string */
|
||||
public $optionValue;
|
||||
|
||||
/** @var array */
|
||||
public $fullOptions;
|
||||
|
||||
/** @var string */
|
||||
public $checked;
|
||||
|
||||
/** @var string */
|
||||
public $checkedKey;
|
||||
|
||||
/** @var string */
|
||||
public $selected;
|
||||
|
||||
/** @var string */
|
||||
public $selectedKey;
|
||||
|
||||
/** @var string */
|
||||
public $rows;
|
||||
|
||||
/** @var bool */
|
||||
public $remote;
|
||||
|
||||
/** @var bool */
|
||||
public $multiple;
|
||||
|
||||
/** @var bool */
|
||||
public $addNew;
|
||||
|
||||
/** @var bool */
|
||||
public $group;
|
||||
|
||||
/** @var bool */
|
||||
public $searchable;
|
||||
|
||||
/** @var bool */
|
||||
public $disabled;
|
||||
|
||||
/** @var bool */
|
||||
public $readonly;
|
||||
|
||||
/** @var bool */
|
||||
public $required;
|
||||
|
||||
/** @var bool */
|
||||
public $notRequired;
|
||||
|
||||
/** @var string */
|
||||
public $formGroupClass = 'sm:col-span-3';
|
||||
|
||||
/** @var string */
|
||||
public $inputGroupClass = '';
|
||||
|
||||
/** @var string */
|
||||
public $icon = '';
|
||||
|
||||
/** @var string */
|
||||
public $trailing = '';
|
||||
|
||||
/** @var array */
|
||||
public $custom_attributes = [];
|
||||
|
||||
/** @var array */
|
||||
public $dynamicAttributes = [];
|
||||
|
||||
/** @var bool */
|
||||
public $hideCurrency;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
string $name = '', string $type = 'text', string $label = '', string $id = null, $value = '', $valueKey = null, string $placeholder = '', string $searchText = '',
|
||||
$options = [], $option = [], string $optionKey = 'id', string $optionValue = 'name', $fullOptions = [], $checked = null, $checkedKey = null, $selected = null, $selectedKey = null, $rows = '3',
|
||||
$remote = false, $multiple = false, $addNew = false, $group = false,
|
||||
bool $searchable = false, bool $disabled = false, bool $readonly = false, bool $required = true, bool $notRequired = false,
|
||||
string $formGroupClass = '', string $inputGroupClass = '', $icon = '', $trailing = '',
|
||||
$dynamicAttributes = '',
|
||||
bool $hideCurrency = false
|
||||
) {
|
||||
$this->type = $this->getType($type);
|
||||
$this->name = $this->getName($name);
|
||||
$this->label = $label;
|
||||
$this->id = $id ?? $name;
|
||||
$this->value = $this->getValue($value, $valueKey);
|
||||
$this->placeholder = $this->getPlaceholder($placeholder);
|
||||
$this->searchText = $this->getSearchText($searchText);
|
||||
$this->rows = $rows;
|
||||
|
||||
$this->remote = $remote;
|
||||
$this->multiple = $multiple;
|
||||
$this->addNew = $addNew;
|
||||
$this->group = $group;
|
||||
|
||||
$this->searchable = $searchable;
|
||||
$this->disabled = $disabled;
|
||||
$this->readonly = $readonly;
|
||||
$this->required = $this->getRequired($required, $notRequired);
|
||||
|
||||
$this->options = $this->getOptions($options);
|
||||
$this->option = $option;
|
||||
$this->optionKey = $optionKey;
|
||||
$this->optionValue = $optionValue;
|
||||
$this->fullOptions = $this->getFullOptions($fullOptions, $options, $searchable);
|
||||
$this->checked = $this->getChecked($checked, $checkedKey);
|
||||
$this->selected = $this->getSelected($selected, $selectedKey);
|
||||
|
||||
$this->formGroupClass = $this->getFromGroupClass($formGroupClass);
|
||||
$this->inputGroupClass = $this->getInputGroupClass($inputGroupClass);
|
||||
|
||||
$this->icon = $icon;
|
||||
$this->trailing = $trailing;
|
||||
|
||||
$this->custom_attributes = $this->getCustomAttributes();
|
||||
|
||||
$this->setDynamicAttributes($dynamicAttributes);
|
||||
|
||||
$this->hideCurrency = $hideCurrency;
|
||||
}
|
||||
|
||||
protected function getType($type)
|
||||
{
|
||||
if (! empty($type) && (! empty($this->type) && $type != 'text')) {
|
||||
return $type;
|
||||
}
|
||||
|
||||
if (! empty($this->type)) {
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getName($name)
|
||||
{
|
||||
if (! empty($name)) {
|
||||
return $name;
|
||||
}
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
protected function getValue($value, $valueKey)
|
||||
{
|
||||
if ($value != '') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (empty($valueKey)) {
|
||||
$valueKey = $this->name;
|
||||
}
|
||||
|
||||
if (empty($valueKey)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
// set model value.
|
||||
$model = $this->getParentData('model');
|
||||
|
||||
$value_keys = explode('.', $valueKey);
|
||||
|
||||
if (count($value_keys) > 1) {
|
||||
$valueKey = $value_keys[0];
|
||||
}
|
||||
|
||||
if (! empty($model->{$valueKey})) {
|
||||
$value = $model->{$valueKey};
|
||||
}
|
||||
|
||||
if ($model instanceof Collection) {
|
||||
$value = $model->get($valueKey);
|
||||
}
|
||||
|
||||
if (count($value_keys) > 1) {
|
||||
$value = $value[0]->{$value_keys[1]};
|
||||
}
|
||||
|
||||
if (empty($value) && request()->has($valueKey)) {
|
||||
$value = request()->get($valueKey);
|
||||
}
|
||||
|
||||
return old($valueKey, $value);
|
||||
}
|
||||
|
||||
protected function getPlaceholder($placeholder)
|
||||
{
|
||||
if (! empty($placeholder)) {
|
||||
return $placeholder;
|
||||
}
|
||||
|
||||
$label = $this->label;
|
||||
|
||||
if (! empty($this->label) && ! empty($this->label->contents)) {
|
||||
$label = $this->name;
|
||||
}
|
||||
|
||||
if ($this->type == 'select') {
|
||||
return trans('general.form.select.field', ['field' => $label]);
|
||||
}
|
||||
|
||||
return trans('general.form.enter', ['field' => $label]);
|
||||
}
|
||||
|
||||
protected function getSearchText($searchText)
|
||||
{
|
||||
if (! empty($searchText)) {
|
||||
return $searchText;
|
||||
}
|
||||
|
||||
return trans('general.search_placeholder');
|
||||
}
|
||||
|
||||
protected function getOptions($options)
|
||||
{
|
||||
if (! empty($options)) {
|
||||
if (is_array($options) && ! $this->group) {
|
||||
$o = collect();
|
||||
|
||||
foreach ($options as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$o->push((object) $value);
|
||||
} else {
|
||||
$o->push((object) [
|
||||
'id' => $key,
|
||||
'name' => $value,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$options = $o;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getFullOptions($fullOptions, $options, $searchable)
|
||||
{
|
||||
if (! empty($fullOptions)) {
|
||||
return $fullOptions;
|
||||
}
|
||||
|
||||
if ($searchable && empty($fullOptions)) {
|
||||
$this->options = $this->options->take(setting('default.select_limit'));
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getChecked($checked, $checkedKey)
|
||||
{
|
||||
return $this->getValue($checked, $checkedKey);
|
||||
}
|
||||
|
||||
protected function getSelected($selected, $selectedKey)
|
||||
{
|
||||
return $this->getValue($selected, $selectedKey);
|
||||
}
|
||||
|
||||
protected function getRequired($required, $notRequired)
|
||||
{
|
||||
if (! empty($notRequired)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $required;
|
||||
}
|
||||
|
||||
protected function getFromGroupClass($formGroupClass)
|
||||
{
|
||||
if (! empty($formGroupClass)) {
|
||||
return $formGroupClass;
|
||||
}
|
||||
|
||||
return $this->formGroupClass;
|
||||
}
|
||||
|
||||
protected function getInputGroupClass($inputGroupClass)
|
||||
{
|
||||
if (! empty($inputGroupClass)) {
|
||||
return $inputGroupClass;
|
||||
}
|
||||
|
||||
return $this->inputGroupClass;
|
||||
}
|
||||
|
||||
protected function getCustomAttributes()
|
||||
{
|
||||
$attributes = [];
|
||||
|
||||
if (! empty($this->required)) {
|
||||
$attributes['required'] = $this->required;
|
||||
}
|
||||
|
||||
if (! empty($this->disabled)) {
|
||||
$attributes['disabled'] = $this->disabled;
|
||||
}
|
||||
|
||||
if (! empty($this->readonly)) {
|
||||
$attributes['readonly'] = $this->readonly;
|
||||
}
|
||||
|
||||
foreach ($this->custom_attributes as $key => $value) {
|
||||
$attributes[$key] = $value;
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
protected function setDynamicAttributes($dynamicAttributes)
|
||||
{
|
||||
if (! empty($dynamicAttributes)) {
|
||||
$this->dynamicAttributes = $dynamicAttributes;
|
||||
}
|
||||
}
|
||||
}
|
||||
760
app/Abstracts/View/Components/Transactions/Index.php
Normal file
760
app/Abstracts/View/Components/Transactions/Index.php
Normal file
@ -0,0 +1,760 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Transactions;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\Transactions;
|
||||
use App\Traits\Modules;
|
||||
use App\Traits\SearchString;
|
||||
use App\Traits\ViewComponents;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class Index extends Component
|
||||
{
|
||||
use SearchString, Transactions, Modules, ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'transaction';
|
||||
public const DEFAULT_TYPE = 'transaction';
|
||||
public const DEFAULT_PLURAL_TYPE = 'transactions';
|
||||
|
||||
public $type;
|
||||
|
||||
public $alias;
|
||||
|
||||
public $transactions;
|
||||
|
||||
public $totalTransactions;
|
||||
|
||||
public $permissionCreate;
|
||||
|
||||
public $permissionUpdate;
|
||||
|
||||
public $permissionDelete;
|
||||
|
||||
public $routeTabDocument;
|
||||
|
||||
public $routeParamsTabUnpaid;
|
||||
|
||||
public $routeParamsTabDraft;
|
||||
|
||||
public $routeTabRecurring;
|
||||
|
||||
public $checkPermissionCreate;
|
||||
|
||||
public $hideIncomeCreate;
|
||||
|
||||
public $routeIncomeCreate;
|
||||
|
||||
public $textIncomeCreate;
|
||||
|
||||
public $hideExpenseCreate;
|
||||
|
||||
public $routeExpenseCreate;
|
||||
|
||||
public $textExpenseCreate;
|
||||
|
||||
public $hideImport;
|
||||
|
||||
public $routeImport;
|
||||
|
||||
public $hideExport;
|
||||
|
||||
public $routeExport;
|
||||
|
||||
public $hideEmptyPage;
|
||||
|
||||
public $hideSummary;
|
||||
|
||||
public $summaryItems;
|
||||
|
||||
public $hideSearchString;
|
||||
|
||||
public $hideBulkAction;
|
||||
|
||||
public $bulkActions;
|
||||
|
||||
/** @var string */
|
||||
public $searchStringModel;
|
||||
|
||||
/** @var string */
|
||||
public $bulkActionClass;
|
||||
|
||||
/** @var array */
|
||||
public $bulkActionRouteParameters;
|
||||
|
||||
/** @var string */
|
||||
public $searchRoute;
|
||||
|
||||
/** @var string */
|
||||
public $classBulkAction;
|
||||
|
||||
/** @var string */
|
||||
public $tabActive;
|
||||
|
||||
/** @var string */
|
||||
public $tabSuffix;
|
||||
|
||||
public $hidePaymentMethod;
|
||||
|
||||
public $hidePaidAt;
|
||||
|
||||
public $hideNumber;
|
||||
|
||||
public $classPaidAtAndNumber;
|
||||
|
||||
public $textPaidAt;
|
||||
|
||||
public $textNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideStartedAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideEndedAt;
|
||||
|
||||
/** @var string */
|
||||
public $classStartedAtAndEndedAt;
|
||||
|
||||
/** @var string */
|
||||
public $textStartedAt;
|
||||
|
||||
/** @var string */
|
||||
public $textEndedAt;
|
||||
|
||||
public $hideType;
|
||||
|
||||
public $hideCategory;
|
||||
|
||||
public $classTypeAndCategory;
|
||||
|
||||
public $textType;
|
||||
|
||||
public $textCategory;
|
||||
|
||||
/** @var bool */
|
||||
public $hideStatus;
|
||||
|
||||
/** @var string */
|
||||
public $classStatus;
|
||||
|
||||
/** @var bool */
|
||||
public $hideFrequency;
|
||||
|
||||
/** @var string */
|
||||
public $classFrequencyAndDuration;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDuration;
|
||||
|
||||
public $hideAccount;
|
||||
|
||||
public $classAccount;
|
||||
|
||||
public $textAccount;
|
||||
|
||||
public $hideContact;
|
||||
|
||||
public $hideDocument;
|
||||
|
||||
public $classContactAndDocument;
|
||||
|
||||
public $textContact;
|
||||
|
||||
public $textDocument;
|
||||
|
||||
public $hideAmount;
|
||||
|
||||
public $classAmount;
|
||||
|
||||
public $textAmount;
|
||||
|
||||
public function __construct(
|
||||
string $type = '', string $alias = '', $transactions = [], int $totalTransactions = null,
|
||||
string $permissionCreate = '', string $permissionUpdate = '', string $permissionDelete = '',
|
||||
bool $checkPermissionCreate = true,
|
||||
bool $hideIncomeCreate = false, $routeIncomeCreate = '', string $textIncomeCreate = '',
|
||||
bool $hideExpenseCreate = false, $routeExpenseCreate = '', string $textExpenseCreate = '',
|
||||
bool $hideImport = false, $routeImport = '',
|
||||
bool $hideExport = false, $routeExport = '',
|
||||
bool $hideEmptyPage = false,
|
||||
bool $hideSummary = false, array $summaryItems = [],
|
||||
bool $hideSearchString = false, bool $hideBulkAction = false,
|
||||
string $searchStringModel = '', string $bulkActionClass = '', array $bulkActions = [], array $bulkActionRouteParameters = [], string $searchRoute = '', string $classBulkAction = '',
|
||||
string $tabActive = '', string $tabSuffix = '',
|
||||
bool $hidePaymentMethod = false,
|
||||
bool $hidePaidAt = false, bool $hideNumber = false, string $classPaidAtAndNumber = '', string $textPaidAt = '', string $textNumber = '',
|
||||
bool $hideStartedAt = false, bool $hideEndedAt = false, string $classStartedAtAndEndedAt = '', string $textStartedAt = '', string $textEndedAt = '',
|
||||
bool $hideType = false, bool $hideCategory = false, string $classTypeAndCategory = '', string $textType = '', string $textCategory = '',
|
||||
bool $hideStatus = false, string $classStatus = '',
|
||||
bool $hideAccount = false, string $classAccount = '', string $textAccount = '',
|
||||
bool $hideFrequency = false, bool $hideDuration = false, string $classFrequencyAndDuration = '',
|
||||
bool $hideContact = false, bool $hideDocument = false, string $classContactAndDocument = '', string $textContact = '', string $textDocument = '',
|
||||
bool $hideAmount = false, string $classAmount = '', string $textAmount = ''
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->transactions = $transactions;
|
||||
/* -- Main Start -- */
|
||||
$this->type = $type;
|
||||
$this->alias = $this->getAlias($type, $alias);
|
||||
$this->transactions = ($transactions) ? $transactions : collect();
|
||||
$this->totalTransactions = $this->getTotalTransactions($totalTransactions);
|
||||
|
||||
$this->permissionCreate = $this->getPermissionCreate($type, $permissionCreate);
|
||||
$this->permissionUpdate = $this->getPermissionUpdate($type, $permissionUpdate);
|
||||
$this->permissionDelete = $this->getPermissionDelete($type, $permissionDelete);
|
||||
|
||||
/* -- Main End -- */
|
||||
|
||||
/* -- Buttons Start -- */
|
||||
$this->checkPermissionCreate = $checkPermissionCreate;
|
||||
|
||||
$this->hideIncomeCreate = $hideIncomeCreate;
|
||||
$this->routeIncomeCreate = $this->getRouteIncomeCreate($type, $routeIncomeCreate);
|
||||
$this->textIncomeCreate = $this->getTextIncomeCreate($type, $textIncomeCreate);
|
||||
|
||||
$this->hideExpenseCreate = $hideExpenseCreate;
|
||||
$this->routeExpenseCreate = $this->getRouteExpenseCreate($type, $routeExpenseCreate);
|
||||
$this->textExpenseCreate = $this->getTextExpenseCreate($type, $textExpenseCreate);
|
||||
|
||||
$this->hideImport = $hideImport;
|
||||
$this->routeImport = $this->getRouteImport($type, $routeImport);
|
||||
|
||||
$this->hideExport = $hideExport;
|
||||
$this->routeExport = $this->getRouteExport($type, $routeExport);
|
||||
/* -- Buttons End -- */
|
||||
|
||||
/* -- Content Start -- */
|
||||
|
||||
/* -- Empty Page Start -- */
|
||||
$this->hideEmptyPage = $this->getHideEmptyPage($hideEmptyPage);
|
||||
/* -- Empty Page End -- */
|
||||
|
||||
/* -- Summary Start -- */
|
||||
$this->hideSummary = $hideSummary;
|
||||
$this->summaryItems = $this->getSummaryItems($type, $summaryItems);
|
||||
/* -- Summary End -- */
|
||||
|
||||
/* Container Start */
|
||||
$this->hideSearchString = $hideSearchString;
|
||||
$this->hideBulkAction = $hideBulkAction;
|
||||
|
||||
$this->searchStringModel = $this->getSearchStringModel($type, $searchStringModel);
|
||||
|
||||
$this->bulkActionClass = $this->getBulkActionClass($type, $bulkActionClass);
|
||||
$this->bulkActionRouteParameters = $this->getBulkActionRouteParameters($type, $bulkActionRouteParameters);
|
||||
|
||||
$this->searchRoute = $this->getIndexRoute($type, $searchRoute);
|
||||
|
||||
$this->classBulkAction = $this->getClassBulkAction($type, $classBulkAction);
|
||||
|
||||
$this->tabSuffix = $this->getTabSuffix($type, $tabSuffix);
|
||||
$this->tabActive = $this->getTabActive($type, $tabActive);
|
||||
|
||||
$this->hidePaymentMethod = $hidePaymentMethod;
|
||||
|
||||
/* Document Start */
|
||||
$this->hidePaidAt = $hidePaidAt;
|
||||
$this->hideNumber = $hideNumber;
|
||||
$this->classPaidAtAndNumber = $this->getClassPaidAtAndNumber($type, $classPaidAtAndNumber);
|
||||
$this->textPaidAt = $this->getTextPaidAt($type, $textPaidAt);
|
||||
$this->textNumber = $this->getTextNumber($type, $textNumber);
|
||||
|
||||
$this->hideStartedAt = $hideStartedAt;
|
||||
$this->hideEndedAt = $hideEndedAt;
|
||||
$this->classStartedAtAndEndedAt = $this->getClassStartedAndEndedAt($type, $classStartedAtAndEndedAt);
|
||||
$this->textStartedAt = $this->getTextStartedAt($type, $textStartedAt);
|
||||
$this->textEndedAt = $this->getTextEndedAt($type, $textEndedAt);
|
||||
|
||||
$this->hideType = $hideType;
|
||||
$this->hideCategory = $hideCategory;
|
||||
$this->classTypeAndCategory = $this->getClassTypeAndCategory($type, $classTypeAndCategory);
|
||||
$this->textType = $this->getTextType($type, $textType);
|
||||
$this->textCategory = $this->getTextCategory($type, $textCategory);
|
||||
|
||||
$this->hideStatus = $hideStatus;
|
||||
$this->classStatus = $this->getClassStatus($type, $classStatus);
|
||||
|
||||
$this->hideFrequency = $hideFrequency;
|
||||
$this->hideDuration = $hideDuration;
|
||||
$this->classFrequencyAndDuration = $this->getClassFrequencyAndDuration($type, $classFrequencyAndDuration);
|
||||
|
||||
$this->hideAccount = $hideAccount;
|
||||
$this->classAccount = $this->getClassAccount($type, $classAccount);
|
||||
$this->textAccount = $this->getTextAccount($type, $textAccount);
|
||||
|
||||
$this->hideContact = $hideContact;
|
||||
$this->hideDocument = $hideDocument;
|
||||
$this->classContactAndDocument = $this->getClassContactAndDocument($type, $classContactAndDocument);
|
||||
$this->textContact = $this->getTextContact($type, $textContact);
|
||||
$this->textDocument = $this->getTextDocument($type, $textDocument);
|
||||
|
||||
$this->hideAmount = $hideAmount;
|
||||
$this->classAmount = $this->getClassAmount($type, $classAmount);
|
||||
$this->textAmount = $this->getTextAmount($type, $textAmount);
|
||||
|
||||
/* Document End */
|
||||
|
||||
/* Container End */
|
||||
|
||||
/* -- Content End -- */
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
protected function getTotalTransactions($totalTransactions)
|
||||
{
|
||||
if (! is_null($totalTransactions)) {
|
||||
return $totalTransactions;
|
||||
}
|
||||
|
||||
return $this->transactions->count();
|
||||
}
|
||||
|
||||
protected function getRouteIncomeCreate ($type, $routeIncomeCreate)
|
||||
{
|
||||
if (! empty($routeIncomeCreate)) {
|
||||
return $routeIncomeCreate;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'income_create');
|
||||
|
||||
if (! empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return ['recurring-transactions.create', ['type' => 'income-recurring']];
|
||||
}
|
||||
|
||||
return ['transactions.create', ['type' => 'income']];
|
||||
}
|
||||
|
||||
protected function getTextIncomeCreate($type, $textIncomeCreate)
|
||||
{
|
||||
if (! empty($textIncomeCreate)) {
|
||||
return $textIncomeCreate;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'income_create', 'income_create');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return 'general.recurring_incomes';
|
||||
}
|
||||
|
||||
return 'general.incomes';
|
||||
}
|
||||
|
||||
protected function getRouteExpenseCreate ($type, $routeExpenseCreate)
|
||||
{
|
||||
if (! empty($routeExpenseCreate)) {
|
||||
return $routeExpenseCreate;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'expense_create');
|
||||
|
||||
if (! empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return ['recurring-transactions.create', ['type' => 'expense-recurring']];
|
||||
}
|
||||
|
||||
return ['transactions.create', ['type' => 'expense']];
|
||||
}
|
||||
|
||||
protected function getTextExpenseCreate($type, $textExpenseCreate)
|
||||
{
|
||||
if (! empty($textExpenseCreate)) {
|
||||
return $textExpenseCreate;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'expense_create', 'expense_create');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return 'general.recurring_expenses';
|
||||
}
|
||||
|
||||
return 'general.expenses';
|
||||
}
|
||||
|
||||
protected function getRouteImport($type, $routeImport)
|
||||
{
|
||||
if (! empty($routeImport)) {
|
||||
return $routeImport;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'import');
|
||||
|
||||
if (! empty($route)) {
|
||||
//return $route;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return ['import.create', ['banking', 'recurring-transactions']];
|
||||
}
|
||||
|
||||
return ['import.create', ['banking', 'transactions']];
|
||||
}
|
||||
|
||||
protected function getRouteExport($type, $routeExport)
|
||||
{
|
||||
if (! empty($routeExport)) {
|
||||
return $routeExport;
|
||||
}
|
||||
|
||||
$route = $this->getRouteFromConfig($type, 'export');
|
||||
|
||||
if (! empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
if (Str::contains($type, 'recurring')) {
|
||||
return 'recurring-transactions.export';
|
||||
}
|
||||
|
||||
return 'transactions.export';
|
||||
}
|
||||
|
||||
protected function getHideEmptyPage($hideEmptyPage): bool
|
||||
{
|
||||
if ($hideEmptyPage) {
|
||||
return $hideEmptyPage;
|
||||
}
|
||||
|
||||
if ($this->totalTransactions > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (request()->has('search') && ($this->totalTransactions > 0)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getSummaryItems($type, $summaryItems)
|
||||
{
|
||||
if (! empty($summaryItems)) {
|
||||
return $summaryItems;
|
||||
}
|
||||
|
||||
#todo this lines
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getTabActive($type, $tabActive)
|
||||
{
|
||||
if (! empty($tabActive)) {
|
||||
return $tabActive;
|
||||
}
|
||||
|
||||
$search_type = $type == 'income-recurring' ? 'recurring-transactions' : search_string_value('type');
|
||||
|
||||
return ($this->tabSuffix) ? 'transactions-' . $this->tabSuffix : $search_type;
|
||||
}
|
||||
|
||||
public function getTabSuffix($type, $tabSuffix)
|
||||
{
|
||||
if (! empty($tabSuffix)) {
|
||||
return $tabSuffix;
|
||||
}
|
||||
|
||||
$search_type = $type == 'income-recurring' ? 'recurring-transactions' : search_string_value('type');
|
||||
|
||||
if ($search_type == 'income') {
|
||||
return 'income';
|
||||
}
|
||||
|
||||
if ($search_type == 'expense') {
|
||||
return 'expense';
|
||||
}
|
||||
|
||||
$suffix = $this->getTabActiveFromSetting($type);
|
||||
|
||||
if (! empty($suffix)) {
|
||||
return $suffix;
|
||||
}
|
||||
|
||||
return 'all';
|
||||
}
|
||||
|
||||
protected function getClassPaidAtAndNumber($type, $classPaidAtAndNumber)
|
||||
{
|
||||
if (! empty($classPaidAtAndNumber)) {
|
||||
return $classPaidAtAndNumber;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'paid_at_and_number');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 sm:w-3/12';
|
||||
}
|
||||
|
||||
protected function getTextPaidAt($type, $textPaidAt)
|
||||
{
|
||||
if (! empty($textPaidAt)) {
|
||||
return $textPaidAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'paid_at', 'paid_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.date';
|
||||
}
|
||||
|
||||
protected function getTextNumber($type, $textNumber)
|
||||
{
|
||||
if (! empty($textNumber)) {
|
||||
return $textNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'number', 'number');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.numbers';
|
||||
}
|
||||
|
||||
protected function getClassStartedAndEndedAt($type, $classStartedAtAndEndedAt)
|
||||
{
|
||||
if (! empty($classStartedAtAndEndedAt)) {
|
||||
return $classStartedAtAndEndedAt;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'started_at_and_end_at');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return '"w-4/12 sm:w-3/12';
|
||||
}
|
||||
|
||||
protected function getTextStartedAt($type, $textStartedAt)
|
||||
{
|
||||
if (! empty($textStartedAt)) {
|
||||
return $textStartedAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'started_at', 'started_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.start_date';
|
||||
}
|
||||
|
||||
protected function getTextEndedAt($type, $textEndedAt)
|
||||
{
|
||||
if (! empty($textEndedAt)) {
|
||||
return $textEndedAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'ended_at', 'ended_date');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'recurring.last_issued';
|
||||
}
|
||||
|
||||
protected function getClassTypeAndCategory($type, $classTypeAndCategory)
|
||||
{
|
||||
if (! empty($classTypeAndCategory)) {
|
||||
return $classTypeAndCategory;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'type_and_category');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-2/12';
|
||||
}
|
||||
|
||||
protected function getTextType($type, $textType)
|
||||
{
|
||||
if (! empty($textType)) {
|
||||
return $textType;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'type', 'type');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.types';
|
||||
}
|
||||
|
||||
protected function getTextCategory($type, $textCategory)
|
||||
{
|
||||
if (! empty($textCategory)) {
|
||||
return $textCategory;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'category', 'category');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.categories';
|
||||
}
|
||||
|
||||
protected function getClassStatus($type, $classStatus)
|
||||
{
|
||||
if (! empty($classStatus)) {
|
||||
return $classStatus;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'status');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 sm:w-3/12';
|
||||
}
|
||||
|
||||
protected function getClassFrequencyAndDuration($type, $classFrequencyAndDuration)
|
||||
{
|
||||
if (! empty($classFrequencyAndDuration)) {
|
||||
return $classFrequencyAndDuration;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'frequency_and_duration');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-2/12';
|
||||
}
|
||||
|
||||
protected function getClassAccount($type, $classAccount)
|
||||
{
|
||||
if (! empty($classAccount)) {
|
||||
return $classAccount;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'account');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 sm:w-3/12';
|
||||
}
|
||||
|
||||
protected function getTextAccount($type, $textAccount)
|
||||
{
|
||||
if (! empty($textAccount)) {
|
||||
return $textAccount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'account', 'account');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.accounts';
|
||||
}
|
||||
|
||||
protected function getClassContactAndDocument($type, $classContactAndDocument)
|
||||
{
|
||||
if (! empty($classContactAndDocument)) {
|
||||
return $classContactAndDocument;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'contact_and_document');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-2/12';
|
||||
}
|
||||
|
||||
protected function getTextContact($type, $textContact)
|
||||
{
|
||||
if (! empty($textContact)) {
|
||||
return $textContact;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'contact', 'contact');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.contacts';
|
||||
}
|
||||
|
||||
protected function getTextDocument($type, $textDocument)
|
||||
{
|
||||
if (! empty($textDocument)) {
|
||||
return $textDocument;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'document', 'document');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.documents';
|
||||
}
|
||||
|
||||
protected function getClassAmount($type, $classAmount)
|
||||
{
|
||||
if (! empty($classAmount)) {
|
||||
return $classAmount;
|
||||
}
|
||||
|
||||
$class = $this->getClassFromConfig($type, 'amount');
|
||||
|
||||
if (! empty($class)) {
|
||||
return $class;
|
||||
}
|
||||
|
||||
return 'w-4/12 sm:w-2/12';
|
||||
}
|
||||
|
||||
protected function getTextAmount($type, $textAmount)
|
||||
{
|
||||
if (! empty($textAmount)) {
|
||||
return $textAmount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'amount', 'amount');
|
||||
|
||||
if (! empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.amount';
|
||||
}
|
||||
}
|
||||
1142
app/Abstracts/View/Components/Transactions/Show.php
Normal file
1142
app/Abstracts/View/Components/Transactions/Show.php
Normal file
File diff suppressed because it is too large
Load Diff
660
app/Abstracts/View/Components/Transactions/Template.php
Normal file
660
app/Abstracts/View/Components/Transactions/Template.php
Normal file
@ -0,0 +1,660 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Transactions;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Models\Common\Media;
|
||||
use App\Traits\DateTime;
|
||||
use App\Traits\Transactions;
|
||||
use App\Utilities\Modules;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Str;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use Intervention\Image\Exception\NotReadableException;
|
||||
use App\Traits\ViewComponents;
|
||||
|
||||
abstract class Template extends Component
|
||||
{
|
||||
use DateTime, Transactions, ViewComponents;
|
||||
|
||||
public const OBJECT_TYPE = 'transaction';
|
||||
public const DEFAULT_TYPE = 'transaction';
|
||||
public const DEFAULT_PLURAL_TYPE = 'transactions';
|
||||
|
||||
/** @var string */
|
||||
public $type;
|
||||
|
||||
public $transaction;
|
||||
|
||||
public $logo;
|
||||
|
||||
/** @var array */
|
||||
public $payment_methods;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompany;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyLogo;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyDetails;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyName;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyAddress;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyTaxNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyPhone;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCompanyEmail;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContentTitle;
|
||||
|
||||
/** @var bool */
|
||||
public $hideNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hidePaidAt;
|
||||
|
||||
/** @var bool */
|
||||
public $hideAccount;
|
||||
|
||||
/** @var bool */
|
||||
public $hideCategory;
|
||||
|
||||
/** @var bool */
|
||||
public $hidePaymentMethods;
|
||||
|
||||
/** @var bool */
|
||||
public $hideReference;
|
||||
|
||||
/** @var bool */
|
||||
public $hideDescription;
|
||||
|
||||
/** @var bool */
|
||||
public $hideAmount;
|
||||
|
||||
/** @var string */
|
||||
public $textContentTitle;
|
||||
|
||||
/** @var string */
|
||||
public $textNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textPaidAt;
|
||||
|
||||
/** @var string */
|
||||
public $textAccount;
|
||||
|
||||
/** @var string */
|
||||
public $textCategory;
|
||||
|
||||
/** @var string */
|
||||
public $textPaymentMethods;
|
||||
|
||||
/** @var string */
|
||||
public $textReference;
|
||||
|
||||
/** @var string */
|
||||
public $textDescription;
|
||||
|
||||
/** @var string */
|
||||
public $textAmount;
|
||||
|
||||
/** @var string */
|
||||
public $textPaidBy;
|
||||
|
||||
/** @var string */
|
||||
public $textContactInfo;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContact;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactInfo;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactName;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactAddress;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactTaxNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactPhone;
|
||||
|
||||
/** @var bool */
|
||||
public $hideContactEmail;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelated;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelatedDocumentNumber;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelatedContact;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelatedDocumentDate;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelatedDocumentAmount;
|
||||
|
||||
/** @var bool */
|
||||
public $hideRelatedAmount;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedTransansaction;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedDocumentNumber;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedContact;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedDocumentDate;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedDocumentAmount;
|
||||
|
||||
/** @var string */
|
||||
public $textRelatedAmount;
|
||||
|
||||
/** @var string */
|
||||
public $routeDocumentShow;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
$type, $transaction, $logo = '', array $payment_methods = [],
|
||||
bool $hideCompany = false, bool $hideCompanyLogo = false, bool $hideCompanyDetails = false, bool $hideCompanyName = false, bool $hideCompanyAddress = false,
|
||||
bool $hideCompanyTaxNumber = false, bool $hideCompanyPhone = false, bool $hideCompanyEmail = false,
|
||||
bool $hideContentTitle = false, bool $hideNumber = false, bool $hidePaidAt = false, bool $hideAccount = false, bool $hideCategory = false, bool $hidePaymentMethods = false, bool $hideReference = false,
|
||||
bool $hideDescription = false, bool $hideAmount = false,
|
||||
string $textContentTitle = '', string $textNumber = '', string $textPaidAt = '', string $textAccount = '', string $textCategory = '', string $textPaymentMethods = '', string $textReference = '',
|
||||
string $textDescription = '', string $textAmount = '', string $textPaidBy = '', string $textContactInfo = '',
|
||||
bool $hideContact = false, bool $hideContactInfo = false, bool $hideContactName = false, bool $hideContactAddress = false, bool $hideContactTaxNumber = false,
|
||||
bool $hideContactPhone = false, bool $hideContactEmail = false,
|
||||
bool $hideRelated = false, bool $hideRelatedDocumentNumber = false, bool $hideRelatedContact = false, bool $hideRelatedDocumentDate = false, bool $hideRelatedDocumentAmount = false, bool $hideRelatedAmount = false,
|
||||
string $textRelatedTransansaction = '', string $textRelatedDocumentNumber = '', string $textRelatedContact = '', string $textRelatedDocumentDate = '', string $textRelatedDocumentAmount = '', string $textRelatedAmount = '',
|
||||
string $routeDocumentShow = ''
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->transaction = $transaction;
|
||||
|
||||
$this->logo = $this->getLogo($logo);
|
||||
$this->payment_methods = ($payment_methods) ?: Modules::getPaymentMethods('all');
|
||||
|
||||
// Company Information Hide checker
|
||||
$this->hideCompany = $hideCompany;
|
||||
$this->hideCompanyLogo = $hideCompanyLogo;
|
||||
$this->hideCompanyDetails = $hideCompanyDetails;
|
||||
$this->hideCompanyName = $hideCompanyName;
|
||||
$this->hideCompanyAddress = $hideCompanyAddress;
|
||||
$this->hideCompanyTaxNumber = $hideCompanyTaxNumber;
|
||||
$this->hideCompanyPhone = $hideCompanyPhone;
|
||||
$this->hideCompanyEmail = $hideCompanyEmail;
|
||||
|
||||
// Transaction Information Hide checker
|
||||
$this->hideContentTitle = $hideContentTitle;
|
||||
$this->hideNumber = $hideNumber;
|
||||
$this->hidePaidAt = $hidePaidAt;
|
||||
$this->hideAccount = $hideAccount;
|
||||
$this->hideCategory = $hideCategory;
|
||||
$this->hidePaymentMethods = $hidePaymentMethods;
|
||||
$this->hideReference = $hideReference;
|
||||
$this->hideDescription = $hideDescription;
|
||||
$this->hideAmount = $hideAmount;
|
||||
|
||||
// Transaction Information Text
|
||||
$this->textContentTitle = $this->getTextContentTitle($type, $textContentTitle);
|
||||
$this->textNumber = $this->getTextNumber($type, $textNumber);
|
||||
$this->textPaidAt = $this->getTextPaidAt($type, $textPaidAt);
|
||||
$this->textAccount = $this->getTextAccount($type, $textAccount);
|
||||
$this->textCategory = $this->getTextCategory($type, $textCategory);
|
||||
$this->textPaymentMethods = $this->getTextPaymentMethods($type, $textPaymentMethods);
|
||||
$this->textReference = $this->getTextReference($type, $textReference);
|
||||
$this->textDescription = $this->getTextDescription($type, $textDescription);
|
||||
$this->textAmount = $this->getTextAmount($type, $textAmount);
|
||||
$this->textPaidBy = $this->getTextPaidBy($type, $textPaidBy);
|
||||
$this->textContactInfo = $this->getTextContactInfo($type, $textContactInfo);
|
||||
|
||||
// Contact Information Hide checker
|
||||
$this->hideContact = $hideContact;
|
||||
$this->hideContactInfo = $hideContactInfo;
|
||||
$this->hideContactName = $hideContactName;
|
||||
$this->hideContactAddress = $hideContactAddress;
|
||||
$this->hideContactTaxNumber = $hideContactTaxNumber;
|
||||
$this->hideContactPhone = $hideContactPhone;
|
||||
$this->hideContactEmail = $hideContactEmail;
|
||||
|
||||
// Related Information Hide checker
|
||||
$this->hideRelated = $hideRelated;
|
||||
$this->hideRelatedDocumentNumber = $hideRelatedDocumentNumber;
|
||||
$this->hideRelatedContact = $hideRelatedContact;
|
||||
$this->hideRelatedDocumentDate = $hideRelatedDocumentDate;
|
||||
$this->hideRelatedDocumentAmount = $hideRelatedDocumentAmount;
|
||||
$this->hideRelatedAmount = $hideRelatedAmount;
|
||||
|
||||
// Related Information Text
|
||||
$this->textRelatedTransansaction = $this->getTextRelatedTransansaction($type, $textRelatedTransansaction);
|
||||
$this->textRelatedDocumentNumber = $this->getTextRelatedDocumentNumber($type, $textRelatedDocumentNumber);
|
||||
$this->textRelatedContact = $this->getTextRelatedContact($type, $textRelatedContact);
|
||||
$this->textRelatedDocumentDate = $this->getTextRelatedDocumentDate($type, $textRelatedDocumentDate);
|
||||
$this->textRelatedDocumentAmount = $this->getTextRelatedDocumentAmount($type, $textRelatedDocumentAmount);
|
||||
$this->textRelatedAmount = $this->getTextRelatedAmount($type, $textRelatedAmount);
|
||||
|
||||
$this->routeDocumentShow = $this->routeDocumentShow($type, $routeDocumentShow);
|
||||
}
|
||||
|
||||
protected function getLogo($logo)
|
||||
{
|
||||
if (!empty($logo)) {
|
||||
return $logo;
|
||||
}
|
||||
|
||||
static $content;
|
||||
|
||||
if (! empty($content)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$media_id = (!empty($this->transaction->contact->logo) && !empty($this->transaction->contact->logo->id)) ? $this->transaction->contact->logo->id : setting('company.logo');
|
||||
|
||||
$media = Media::find($media_id);
|
||||
|
||||
if (!empty($media)) {
|
||||
$path = $media->getDiskPath();
|
||||
|
||||
if (! $media->fileExists()) {
|
||||
return $logo;
|
||||
}
|
||||
} else {
|
||||
$path = base_path('public/img/company.png');
|
||||
}
|
||||
|
||||
try {
|
||||
$image = Image::cache(function($image) use ($media, $path) {
|
||||
$width = setting('invoice.logo_size_width');
|
||||
$height = setting('invoice.logo_size_height');
|
||||
|
||||
if ($media) {
|
||||
$image->make($media->contents())->resize($width, $height)->encode();
|
||||
} else {
|
||||
$image->make($path)->resize($width, $height)->encode();
|
||||
}
|
||||
});
|
||||
} catch (NotReadableException | \Exception $e) {
|
||||
Log::info('Company ID: ' . company_id() . ' components/transactionshow.php exception.');
|
||||
Log::info($e->getMessage());
|
||||
|
||||
$path = base_path('public/img/company.png');
|
||||
|
||||
$image = Image::cache(function($image) use ($path) {
|
||||
$width = setting('invoice.logo_size_width');
|
||||
$height = setting('invoice.logo_size_height');
|
||||
|
||||
$image->make($path)->resize($width, $height)->encode();
|
||||
});
|
||||
}
|
||||
|
||||
if (empty($image)) {
|
||||
return $logo;
|
||||
}
|
||||
|
||||
$extension = File::extension($path);
|
||||
|
||||
$content = 'data:image/' . $extension . ';base64,' . base64_encode($image);
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
protected function getTextContentTitle($type, $textContentTitle)
|
||||
{
|
||||
if (!empty($textContentTitle)) {
|
||||
return $textContentTitle;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'payment_made';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'receipts';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, $type . '_made', $default_key);
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.receipts';
|
||||
}
|
||||
|
||||
protected function getTextNumber($type, $textNumber)
|
||||
{
|
||||
if (!empty($textNumber)) {
|
||||
return $textNumber;
|
||||
}
|
||||
|
||||
return 'general.numbers';
|
||||
}
|
||||
|
||||
protected function getTextPaidAt($type, $textPaidAt)
|
||||
{
|
||||
if (!empty($textPaidAt)) {
|
||||
return $textPaidAt;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'paid_at', 'date');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.date';
|
||||
}
|
||||
|
||||
protected function getTextAccount($type, $textAccount)
|
||||
{
|
||||
if (!empty($textAccount)) {
|
||||
return $textAccount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'accounts', 'accounts', 'trans_choice');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.accounts';
|
||||
}
|
||||
|
||||
protected function getTextCategory($type, $textCategory)
|
||||
{
|
||||
if (!empty($textCategory)) {
|
||||
return $textCategory;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'categories', 'categories', 'trans_choice');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.categories';
|
||||
}
|
||||
|
||||
protected function getTextPaymentMethods($type, $textPaymentMethods)
|
||||
{
|
||||
if (!empty($textPaymentMethods)) {
|
||||
return $textPaymentMethods;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'payment_methods', 'payment_methods', 'trans_choice');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.payment_methods';
|
||||
}
|
||||
|
||||
protected function getTextReference($type, $textReference)
|
||||
{
|
||||
if (!empty($textReference)) {
|
||||
return $textReference;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'reference', 'reference');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.reference';
|
||||
}
|
||||
|
||||
protected function getTextDescription($type, $textDescription)
|
||||
{
|
||||
if (!empty($textDescription)) {
|
||||
return $textDescription;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'description', 'description');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.description';
|
||||
}
|
||||
|
||||
protected function getTextAmount($type, $textAmount)
|
||||
{
|
||||
if (!empty($textAmount)) {
|
||||
return $textAmount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'amount', 'amount');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.amount';
|
||||
}
|
||||
|
||||
protected function getTextPaidBy($type, $textPaidBy)
|
||||
{
|
||||
if (!empty($textPaidBy)) {
|
||||
return $textPaidBy;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'paid_to';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'paid_by';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'paid_to_by', $default_key);
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'transactions.paid_by';
|
||||
}
|
||||
|
||||
protected function getTextContactInfo($type, $textContactInfo)
|
||||
{
|
||||
if (!empty($textContactInfo)) {
|
||||
return $textContactInfo;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$textContactInfo = 'bills.bill_from';
|
||||
break;
|
||||
default:
|
||||
$textContactInfo = 'invoices.bill_to';
|
||||
break;
|
||||
}
|
||||
|
||||
return $textContactInfo;
|
||||
}
|
||||
|
||||
protected function getTextRelatedTransansaction($type, $textRelatedTransansaction)
|
||||
{
|
||||
if (!empty($textRelatedTransansaction)) {
|
||||
return $textRelatedTransansaction;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'related_bill';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'related_invoice';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_type', $default_key);
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'transactions.related_invoice';
|
||||
}
|
||||
|
||||
protected function getTextRelatedDocumentNumber($type, $textRelatedDocumentNumber)
|
||||
{
|
||||
if (!empty($textRelatedDocumentNumber)) {
|
||||
return $textRelatedDocumentNumber;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_document_number', 'numbers');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.numbers';
|
||||
}
|
||||
|
||||
protected function getTextRelatedContact($type, $textRelatedContact)
|
||||
{
|
||||
if (!empty($textRelatedContact)) {
|
||||
return $textRelatedContact;
|
||||
}
|
||||
|
||||
$default_key = Str::plural(config('type.transaction.' . $type . '.contact_type'), 2);
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_contact', $default_key, 'trans_choice');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.customers';
|
||||
}
|
||||
|
||||
protected function getTextRelatedDocumentDate($type, $textRelatedDocumentDate)
|
||||
{
|
||||
if (!empty($textRelatedDocumentDate)) {
|
||||
return $textRelatedDocumentDate;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_date';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'invoice_date';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_document_date', $default_key);
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'invoices.invoice_date';
|
||||
}
|
||||
|
||||
protected function getTextRelatedDocumentAmount($type, $textRelatedDocumentAmount)
|
||||
{
|
||||
if (!empty($textRelatedDocumentAmount)) {
|
||||
return $textRelatedDocumentAmount;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bill':
|
||||
case 'expense':
|
||||
case 'purchase':
|
||||
$default_key = 'bill_amount';
|
||||
break;
|
||||
default:
|
||||
$default_key = 'invoice_amount';
|
||||
break;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_document_amount', $default_key);
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.amount';
|
||||
}
|
||||
|
||||
protected function getTextRelatedAmount($type, $textRelatedAmount)
|
||||
{
|
||||
if (!empty($textRelatedAmount)) {
|
||||
return $textRelatedAmount;
|
||||
}
|
||||
|
||||
$translation = $this->getTextFromConfig($type, 'related_amount', 'amount');
|
||||
|
||||
if (!empty($translation)) {
|
||||
return $translation;
|
||||
}
|
||||
|
||||
return 'general.amount';
|
||||
}
|
||||
|
||||
protected function routeDocumentShow($type, $routeDocumentShow)
|
||||
{
|
||||
if (!empty($routeDocumentShow)) {
|
||||
return $routeDocumentShow;
|
||||
}
|
||||
|
||||
if (!$this->transaction->document) {
|
||||
return $routeDocumentShow;
|
||||
}
|
||||
|
||||
//example route parameter.
|
||||
$parameter = 1;
|
||||
|
||||
$route = $this->getRouteFromConfig($this->transaction->document->type, 'show', $parameter);
|
||||
|
||||
if (!empty($route)) {
|
||||
return $route;
|
||||
}
|
||||
|
||||
return 'invoices.show';
|
||||
}
|
||||
}
|
||||
50
app/Abstracts/View/Components/Transfers/Show.php
Normal file
50
app/Abstracts/View/Components/Transfers/Show.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Transfers;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\ViewComponents;
|
||||
use App\Utilities\Modules;
|
||||
|
||||
abstract class Show extends Component
|
||||
{
|
||||
use ViewComponents;
|
||||
|
||||
public $model;
|
||||
|
||||
public $transfer;
|
||||
|
||||
public array $payment_methods;
|
||||
|
||||
public string $template;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
$model = false, $transfer = false, array $payment_methods = [], string $template = ''
|
||||
) {
|
||||
$this->model = $model;
|
||||
$this->transfer = $this->getTransfer($model, $transfer);
|
||||
$this->payment_methods = ($payment_methods) ?: Modules::getPaymentMethods('all');
|
||||
$this->template = ! empty($template) ? $template : setting('transfer.template');
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
protected function getTransfer($model, $transfer)
|
||||
{
|
||||
if (! empty($model)) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
if (! empty($transfer)) {
|
||||
return $transfer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
50
app/Abstracts/View/Components/Transfers/Template.php
Normal file
50
app/Abstracts/View/Components/Transfers/Template.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts\View\Components\Transfers;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
use App\Traits\ViewComponents;
|
||||
use App\Utilities\Modules;
|
||||
|
||||
abstract class Template extends Component
|
||||
{
|
||||
use ViewComponents;
|
||||
|
||||
public $model;
|
||||
|
||||
public $transfer;
|
||||
|
||||
public array $payment_methods;
|
||||
|
||||
public string $template;
|
||||
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
$model = false, $transfer = false, array $payment_methods = [], string $template = ''
|
||||
) {
|
||||
$this->model = $model;
|
||||
$this->transfer = $this->getTransfer($model, $transfer);
|
||||
$this->payment_methods = ($payment_methods) ?: Modules::getPaymentMethods('all');
|
||||
$this->template = ! empty($template) ? $template : setting('transfer.template');
|
||||
|
||||
// Set Parent data
|
||||
$this->setParentData();
|
||||
}
|
||||
|
||||
protected function getTransfer($model, $transfer)
|
||||
{
|
||||
if (! empty($model)) {
|
||||
return $model;
|
||||
}
|
||||
|
||||
if (! empty($transfer)) {
|
||||
return $transfer;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
143
app/Abstracts/Widget.php
Normal file
143
app/Abstracts/Widget.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Abstracts;
|
||||
|
||||
use App\Models\Common\Report;
|
||||
use App\Traits\Charts;
|
||||
use App\Traits\DateTime;
|
||||
use App\Utilities\Date;
|
||||
use App\Utilities\Reports;
|
||||
|
||||
abstract class Widget
|
||||
{
|
||||
use Charts, DateTime;
|
||||
|
||||
public $model;
|
||||
|
||||
public $default_name = '';
|
||||
|
||||
public $default_settings = [
|
||||
'width' => '50',
|
||||
];
|
||||
|
||||
public $description = '';
|
||||
|
||||
public $report_class = '';
|
||||
|
||||
public $views = [
|
||||
'header' => 'components.widgets.header',
|
||||
];
|
||||
|
||||
/**
|
||||
* Methods that may be invoked via the Widgets@getData endpoint.
|
||||
* Subclasses can append additional safe, read-only methods.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public array $allowed_methods = ['show'];
|
||||
|
||||
public array $data = [];
|
||||
|
||||
public function __construct($model = null)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function getDefaultName()
|
||||
{
|
||||
return trans($this->default_name);
|
||||
}
|
||||
|
||||
public function getDefaultSettings()
|
||||
{
|
||||
return $this->default_settings;
|
||||
}
|
||||
|
||||
public function getDescription()
|
||||
{
|
||||
return trans($this->description);
|
||||
}
|
||||
|
||||
public function getReportUrl(): string
|
||||
{
|
||||
$empty_url = '';
|
||||
|
||||
if (empty($this->report_class)) {
|
||||
return $empty_url;
|
||||
}
|
||||
|
||||
if (Reports::isModule($this->report_class) && Reports::isModuleDisabled($this->report_class)) {
|
||||
$alias = Reports::getModuleAlias($this->report_class);
|
||||
|
||||
return route('apps.app.show', [
|
||||
'alias' => $alias,
|
||||
'utm_source' => 'widget',
|
||||
'utm_medium' => 'app',
|
||||
'utm_campaign' => str_replace('-', '_', $alias),
|
||||
]);
|
||||
}
|
||||
|
||||
if (! class_exists($this->report_class)) {
|
||||
return $empty_url;
|
||||
}
|
||||
|
||||
if (Reports::cannotRead($this->report_class)) {
|
||||
return $empty_url;
|
||||
}
|
||||
|
||||
$model = Report::where('class', $this->report_class)->first();
|
||||
|
||||
if (! $model instanceof Report) {
|
||||
return route('reports.create');
|
||||
}
|
||||
|
||||
return route('reports.show', $model->id);
|
||||
}
|
||||
|
||||
public function getViews()
|
||||
{
|
||||
return $this->views;
|
||||
}
|
||||
|
||||
public function view($name, $data = [])
|
||||
{
|
||||
if (request_is_api()) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return view($name, array_merge(['class' => $this], (array) $data));
|
||||
}
|
||||
|
||||
public function applyFilters($query, $args = ['date_field' => 'paid_at'])
|
||||
{
|
||||
return $this->scopeDateFilter($query, $args['date_field']);
|
||||
}
|
||||
|
||||
public function calculateDocumentTotals($model)
|
||||
{
|
||||
$open = $overdue = 0;
|
||||
|
||||
$today = Date::today()->toDateString();
|
||||
|
||||
if ($model->status == 'paid') {
|
||||
return [$open, $overdue];
|
||||
}
|
||||
|
||||
$payments = 0;
|
||||
|
||||
if ($model->status == 'partial') {
|
||||
foreach ($model->transactions as $transaction) {
|
||||
$payments += $transaction->getAmountConvertedToDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the invoice/bill is open or overdue
|
||||
if ($model->due_at > $today) {
|
||||
$open += $model->getAmountConvertedToDefault() - $payments;
|
||||
} else {
|
||||
$overdue += $model->getAmountConvertedToDefault() - $payments;
|
||||
}
|
||||
|
||||
return [$open, $overdue];
|
||||
}
|
||||
}
|
||||
74
app/Builders/Category.php
Normal file
74
app/Builders/Category.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Builders;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Pagination\Paginator;
|
||||
|
||||
class Category extends Builder
|
||||
{
|
||||
/**
|
||||
* Execute the query as a "select" statement.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function get($columns = ['*'])
|
||||
{
|
||||
$collection = parent::get($columns);
|
||||
|
||||
return $collection->withChildren('sub_categories', function ($list, $parent, $relation, $level, $addChildren) {
|
||||
$parent->load($relation);
|
||||
$parent->level = $level;
|
||||
$parent->syncOriginalAttribute('level');
|
||||
|
||||
$list->push($parent);
|
||||
|
||||
if ($parent->$relation->count() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($parent->$relation as $item) {
|
||||
$addChildren($list, $item, $relation, $level + 1, $addChildren);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the categories excluding their children.
|
||||
*
|
||||
* @param array|string $columns
|
||||
* @return \Illuminate\Support\Collection
|
||||
*/
|
||||
public function getWithoutChildren($columns = ['*'])
|
||||
{
|
||||
return parent::get($columns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Paginate the given query.
|
||||
*
|
||||
* @param int|null $perPage
|
||||
* @param array $columns
|
||||
* @param string $pageName
|
||||
* @param int|null $page
|
||||
* @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
|
||||
{
|
||||
$page = $page ?: Paginator::resolveCurrentPage($pageName);
|
||||
|
||||
$perPage = $perPage ?: $this->model->getPerPage();
|
||||
|
||||
$results = ($total = $this->toBase()->getCountForPagination())
|
||||
? $this->forPage($page, $perPage)->getWithoutChildren($columns)
|
||||
: $this->model->newCollection();
|
||||
|
||||
return $this->paginator($results, $total, $perPage, $page, [
|
||||
'path' => Paginator::resolveCurrentPath(),
|
||||
'pageName' => $pageName,
|
||||
]);
|
||||
}
|
||||
}
|
||||
123
app/BulkActions/Auth/Users.php
Normal file
123
app/BulkActions/Auth/Users.php
Normal file
@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Auth;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Events\Auth\LandingPageShowing;
|
||||
use App\Jobs\Auth\DeleteUser;
|
||||
use App\Jobs\Auth\UpdateUser;
|
||||
|
||||
class Users extends BulkAction
|
||||
{
|
||||
/**
|
||||
* Instantiate a new controller instance.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->model = user_model_class();
|
||||
}
|
||||
|
||||
public $text = 'general.users';
|
||||
|
||||
public $path = [
|
||||
'group' => 'auth',
|
||||
'type' => 'users',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-auth-users',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-auth-users',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-auth-users',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-auth-users',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$roles = role_model_class()::all()->reject(function ($r) {
|
||||
$status = $r->hasPermission('read-client-portal');
|
||||
|
||||
if ($r->name == 'employee') {
|
||||
$status = true;
|
||||
}
|
||||
|
||||
return $status;
|
||||
})->pluck('display_name', 'id');
|
||||
|
||||
$u = new \stdClass();
|
||||
$u->landing_pages = [];
|
||||
|
||||
event(new LandingPageShowing($u));
|
||||
|
||||
$landing_pages = $u->landing_pages;
|
||||
|
||||
return $this->response('bulk-actions.auth.users.edit', compact('selected', 'roles', 'landing_pages'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$users = $this->getSelectedRecords($request, 'contact');
|
||||
|
||||
foreach ($users as $user) {
|
||||
try {
|
||||
$request->merge([
|
||||
'enabled' => $user->enabled,
|
||||
'uploaded_logo' => $user->logo,
|
||||
]); //
|
||||
|
||||
$this->dispatch(new UpdateUser($user, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$users = $this->getSelectedRecords($request, 'contact');
|
||||
|
||||
foreach ($users as $user) {
|
||||
try {
|
||||
$this->dispatch(new UpdateUser($user, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$users = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($users as $user) {
|
||||
try {
|
||||
$this->dispatch(new DeleteUser($user));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
app/BulkActions/Banking/Accounts.php
Normal file
100
app/BulkActions/Banking/Accounts.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Banking;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Banking\DeleteAccount;
|
||||
use App\Jobs\Banking\UpdateAccount;
|
||||
use App\Models\Banking\Account;
|
||||
|
||||
class Accounts extends BulkAction
|
||||
{
|
||||
public $model = Account::class;
|
||||
|
||||
public $text = 'general.accounts';
|
||||
|
||||
public $path = [
|
||||
'group' => 'banking',
|
||||
'type' => 'accounts',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-banking-accounts',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-banking-accounts',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-banking-accounts',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-banking-accounts',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.banking.accounts.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$accounts = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
try {
|
||||
$request->merge([
|
||||
'enabled' => $account->enabled,
|
||||
'currency_code' => ($request->get('currency_code')) ?? $account->currency_code,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateAccount($account, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$accounts = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
try {
|
||||
$this->dispatch(new UpdateAccount($account, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$accounts = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
try {
|
||||
$this->dispatch(new DeleteAccount($account));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
app/BulkActions/Banking/Reconciliations.php
Normal file
96
app/BulkActions/Banking/Reconciliations.php
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Banking;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Models\Banking\Reconciliation;
|
||||
use App\Models\Banking\Transaction;
|
||||
|
||||
class Reconciliations extends BulkAction
|
||||
{
|
||||
public $model = Reconciliation::class;
|
||||
|
||||
public $text = 'general.reconciliations';
|
||||
|
||||
public $path = [
|
||||
'group' => 'banking',
|
||||
'type' => 'reconciliations',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'reconcile' => [
|
||||
'icon' => 'published_with_changes',
|
||||
'name' => 'reconciliations.reconcile',
|
||||
'message' => 'bulk_actions.message.reconcile',
|
||||
'permission' => 'update-banking-reconciliations',
|
||||
],
|
||||
'unreconcile' => [
|
||||
'icon' => 'layers_clear',
|
||||
'name' => 'reconciliations.unreconcile',
|
||||
'message' => 'bulk_actions.message.unreconcile',
|
||||
'permission' => 'update-banking-reconciliations',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-banking-reconciliations',
|
||||
],
|
||||
];
|
||||
|
||||
public function reconcile($request)
|
||||
{
|
||||
$reconciliations = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($reconciliations as $reconciliation) {
|
||||
\DB::transaction(function () use ($reconciliation) {
|
||||
$reconciliation->reconciled = 1;
|
||||
$reconciliation->save();
|
||||
|
||||
Transaction::where('account_id', $reconciliation->account_id)
|
||||
->isNotReconciled()
|
||||
->whereBetween('paid_at', [$reconciliation->started_at, $reconciliation->ended_at])->each(function ($item) {
|
||||
$item->reconciled = 1;
|
||||
$item->save();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function unreconcile($request)
|
||||
{
|
||||
$reconciliations = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($reconciliations as $reconciliation) {
|
||||
\DB::transaction(function () use ($reconciliation) {
|
||||
$reconciliation->reconciled = 0;
|
||||
$reconciliation->save();
|
||||
|
||||
Transaction::where('account_id', $reconciliation->account_id)
|
||||
->isReconciled()
|
||||
->whereBetween('paid_at', [$reconciliation->started_at, $reconciliation->ended_at])->each(function ($item) {
|
||||
$item->reconciled = 0;
|
||||
$item->save();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$reconciliations = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($reconciliations as $reconciliation) {
|
||||
\DB::transaction(function () use ($reconciliation) {
|
||||
$reconciliation->delete();
|
||||
|
||||
Transaction::where('account_id', $reconciliation->account_id)
|
||||
->isReconciled()
|
||||
->whereBetween('paid_at', [$reconciliation->started_at, $reconciliation->ended_at])->each(function ($item) {
|
||||
$item->reconciled = 0;
|
||||
$item->save();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
122
app/BulkActions/Banking/Transactions.php
Normal file
122
app/BulkActions/Banking/Transactions.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Banking;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Exports\Banking\Transactions as Export;
|
||||
use App\Jobs\Banking\UpdateTransaction;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Traits\Transactions as TransactionsTrait;
|
||||
|
||||
class Transactions extends BulkAction
|
||||
{
|
||||
use TransactionsTrait;
|
||||
|
||||
public $model = Transaction::class;
|
||||
|
||||
public $text = 'general.transactions';
|
||||
|
||||
public $path = [
|
||||
'group' => 'banking',
|
||||
'type' => 'transactions',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-banking-transactions',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-banking-transactions',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$real_type = 'income';
|
||||
$contact_type = 'customer';
|
||||
$account_currency_code = 'USD';
|
||||
|
||||
$types = [];
|
||||
$transactions = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
$r_type = $this->getRealTypeTransaction($transaction->type);
|
||||
|
||||
if (in_array($r_type, $types)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$types[] = $r_type;
|
||||
|
||||
$real_type = $r_type;
|
||||
|
||||
$contact_type = $transaction->contact->type;
|
||||
|
||||
if (! $contact_type) {
|
||||
if ($real_type == Transaction::INCOME_TYPE
|
||||
|| $real_type == Transaction::INCOME_TRANSFER_TYPE
|
||||
|| $real_type == Transaction::INCOME_SPLIT_TYPE
|
||||
|| $real_type == Transaction::INCOME_RECURRING_TYPE
|
||||
) {
|
||||
$contact_type = 'customer';
|
||||
} else {
|
||||
$contact_type = 'vendor';
|
||||
}
|
||||
}
|
||||
|
||||
$account_currency_code = $transaction->account->currency_code;
|
||||
}
|
||||
|
||||
$category_and_contact = count($types) > 1 ? false : true;
|
||||
|
||||
return $this->response('bulk-actions.banking.transactions.edit', compact('selected', 'category_and_contact', 'real_type', 'contact_type', 'account_currency_code'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$transactions = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($transactions as $transaction) {
|
||||
try {
|
||||
$request->merge([
|
||||
'type' => $transaction->type,
|
||||
'uploaded_attachment' => $transaction->attachment,
|
||||
])->except([
|
||||
|
||||
]);
|
||||
|
||||
$this->dispatch(new UpdateTransaction($transaction, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$this->deleteTransactions($request);
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.transactions', 2));
|
||||
}
|
||||
}
|
||||
57
app/BulkActions/Banking/Transfers.php
Normal file
57
app/BulkActions/Banking/Transfers.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Banking;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Banking\DeleteTransfer;
|
||||
use App\Models\Banking\Transfer;
|
||||
use App\Exports\Banking\Transfers as Export;
|
||||
|
||||
class Transfers extends BulkAction
|
||||
{
|
||||
public $model = Transfer::class;
|
||||
|
||||
public $text = 'general.transfers';
|
||||
|
||||
public $path = [
|
||||
'group' => 'banking',
|
||||
'type' => 'transfers',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-banking-transfers',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$transfers = $this->getSelectedRecords($request, [
|
||||
'expense_transaction', 'income_transaction'
|
||||
]);
|
||||
|
||||
foreach ($transfers as $transfer) {
|
||||
try {
|
||||
$this->dispatch(new DeleteTransfer($transfer));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.transfers', 2));
|
||||
}
|
||||
}
|
||||
122
app/BulkActions/Common/Companies.php
Normal file
122
app/BulkActions/Common/Companies.php
Normal file
@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Common;
|
||||
|
||||
use Akaunting\Money\Currency as MoneyCurrency;
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Common\DeleteCompany;
|
||||
use App\Jobs\Common\UpdateCompany;
|
||||
use App\Models\Common\Company;
|
||||
use App\Traits\Users;
|
||||
|
||||
class Companies extends BulkAction
|
||||
{
|
||||
use Users;
|
||||
|
||||
public $model = Company::class;
|
||||
|
||||
public $text = 'general.companies';
|
||||
|
||||
public $path = [
|
||||
'group' => 'common',
|
||||
'type' => 'companies',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-common-companies',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-common-companies',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-common-companies',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$money_currencies = MoneyCurrency::getCurrencies();
|
||||
|
||||
$currencies = [];
|
||||
|
||||
foreach ($money_currencies as $key => $item) {
|
||||
$currencies[$key] = $key . ' - ' . $item['name'];
|
||||
}
|
||||
|
||||
return $this->response('bulk-actions.common.companies.edit', compact('selected', 'currencies'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$companies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($companies as $company) {
|
||||
try {
|
||||
if ($this->isNotUserCompany($company->id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$request->merge([
|
||||
'enabled' => $company->enabled,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateCompany($company, $this->getUpdateRequest($request), company_id()));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function enable($request)
|
||||
{
|
||||
$companies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($companies as $company) {
|
||||
try {
|
||||
$this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 1])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$companies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($companies as $company) {
|
||||
try {
|
||||
$this->dispatch(new UpdateCompany($company, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$companies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($companies as $company) {
|
||||
try {
|
||||
$this->dispatch(new DeleteCompany($company));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
app/BulkActions/Common/Dashboards.php
Normal file
67
app/BulkActions/Common/Dashboards.php
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Common;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Common\DeleteDashboard;
|
||||
use App\Jobs\Common\UpdateDashboard;
|
||||
use App\Models\Common\Dashboard;
|
||||
|
||||
class Dashboards extends BulkAction
|
||||
{
|
||||
public $model = Dashboard::class;
|
||||
|
||||
public $text = 'general.dashboards';
|
||||
|
||||
public $path = [
|
||||
'group' => 'common',
|
||||
'type' => 'dashboards',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-common-dashboards',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-common-dashboards',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-common-dashboards',
|
||||
],
|
||||
];
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$dashboards = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($dashboards as $dashboard) {
|
||||
try {
|
||||
$this->dispatch(new UpdateDashboard($dashboard, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$dashboards = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($dashboards as $dashboard) {
|
||||
try {
|
||||
$this->dispatch(new DeleteDashboard($dashboard));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
100
app/BulkActions/Common/Items.php
Normal file
100
app/BulkActions/Common/Items.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Common;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Exports\Common\Items as Export;
|
||||
use App\Jobs\Common\DeleteItem;
|
||||
use App\Jobs\Common\UpdateItem;
|
||||
use App\Models\Common\Item;
|
||||
|
||||
class Items extends BulkAction
|
||||
{
|
||||
public $model = Item::class;
|
||||
|
||||
public $text = 'general.items';
|
||||
|
||||
public $path = [
|
||||
'group' => 'common',
|
||||
'type' => 'items',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-common-items',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'path' => ['group' => 'common', 'type' => 'items'],
|
||||
'type' => '*',
|
||||
'permission' => 'update-common-items',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'path' => ['group' => 'common', 'type' => 'items'],
|
||||
'type' => '*',
|
||||
'permission' => 'update-common-items',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-common-items',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.common.items.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request, 'taxes');
|
||||
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$this->dispatch(new UpdateItem($item, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$items = $this->getSelectedRecords($request, 'taxes');
|
||||
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$this->dispatch(new DeleteItem($item));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.items', 2));
|
||||
}
|
||||
}
|
||||
156
app/BulkActions/Purchases/Bills.php
Normal file
156
app/BulkActions/Purchases/Bills.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Purchases;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Events\Document\DocumentCancelled;
|
||||
use App\Events\Document\DocumentReceived;
|
||||
use App\Exports\Purchases\Bills\Bills as Export;
|
||||
use App\Jobs\Banking\CreateBankingDocumentTransaction;
|
||||
use App\Jobs\Document\CreateDocumentHistory;
|
||||
use App\Jobs\Document\DeleteDocument;
|
||||
use App\Jobs\Document\UpdateDocument;
|
||||
use App\Models\Document\Document;
|
||||
|
||||
class Bills extends BulkAction
|
||||
{
|
||||
public $model = Document::class;
|
||||
|
||||
public $text = 'general.bills';
|
||||
|
||||
public $path = [
|
||||
'group' => 'purchases',
|
||||
'type' => 'bills',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-purchases-bills',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'received' => [
|
||||
'icon' => 'send',
|
||||
'name' => 'bills.mark_received',
|
||||
'message' => 'bulk_actions.message.received',
|
||||
'permission' => 'update-purchases-bills',
|
||||
],
|
||||
'cancelled' => [
|
||||
'icon' => 'cancel',
|
||||
'name' => 'documents.actions.cancel',
|
||||
'message' => 'bulk_actions.message.cancelled',
|
||||
'permission' => 'update-purchases-bills',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-purchases-bills',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.purchases.bills.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$bills = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
try {
|
||||
$discount = $bill->totals->where('code', 'discount')->makeHidden('title')->pluck('amount')->first();
|
||||
|
||||
// for extra total rows..
|
||||
$totals = $bill->totals()->whereNotIn('code', ['sub_total', 'total', 'tax', 'discount'])->get()->toArray();
|
||||
|
||||
$request->merge([
|
||||
'items' => $bill->items->toArray(),
|
||||
'uploaded_attachment' => $bill->attachment,
|
||||
'category_id' => ($request->get('category_id')) ?? $bill->category_id,
|
||||
'discount' => $discount,
|
||||
'totals' => $totals,
|
||||
])->except([
|
||||
|
||||
]);
|
||||
|
||||
$this->dispatch(new UpdateDocument($bill, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function received($request)
|
||||
{
|
||||
$bills = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
if ($bill->status == 'received') {
|
||||
continue;
|
||||
}
|
||||
|
||||
event(new DocumentReceived($bill));
|
||||
}
|
||||
}
|
||||
|
||||
public function cancelled($request)
|
||||
{
|
||||
$bills = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
if (in_array($bill->status, ['cancelled', 'draft'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event(new DocumentCancelled($bill));
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($request)
|
||||
{
|
||||
$bills = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
$clone = $bill->duplicate();
|
||||
|
||||
$description = trans('messages.success.added', ['type' => $clone->document_number]);
|
||||
|
||||
$this->dispatch(new CreateDocumentHistory($clone, 0, $description));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$bills = $this->getSelectedRecords($request, [
|
||||
'items', 'item_taxes', 'histories', 'transactions', 'recurring', 'totals'
|
||||
]);
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
try {
|
||||
$this->dispatch(new DeleteDocument($bill));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.bills', 2));
|
||||
}
|
||||
}
|
||||
97
app/BulkActions/Purchases/Vendors.php
Normal file
97
app/BulkActions/Purchases/Vendors.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Purchases;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Exports\Purchases\Vendors as Export;
|
||||
use App\Jobs\Common\UpdateContact;
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
class Vendors extends BulkAction
|
||||
{
|
||||
public $model = Contact::class;
|
||||
|
||||
public $text = 'general.vendors';
|
||||
|
||||
public $path = [
|
||||
'group' => 'purchases',
|
||||
'type' => 'vendors',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-purchases-vendors',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-purchases-vendors',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-purchases-vendors',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-purchases-vendors',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.purchases.vendors.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$vendors = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($vendors as $vendor) {
|
||||
try {
|
||||
$request->merge([
|
||||
'enabled' => $vendor->enabled,
|
||||
'uploaded_logo' => $vendor->logo,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateContact($vendor, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$this->disableContacts($request);
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$this->deleteContacts($request);
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.vendors', 2));
|
||||
}
|
||||
}
|
||||
97
app/BulkActions/Sales/Customers.php
Normal file
97
app/BulkActions/Sales/Customers.php
Normal file
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Sales;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Exports\Sales\Customers as Export;
|
||||
use App\Jobs\Common\UpdateContact;
|
||||
use App\Models\Common\Contact;
|
||||
|
||||
class Customers extends BulkAction
|
||||
{
|
||||
public $model = Contact::class;
|
||||
|
||||
public $text = 'general.customers';
|
||||
|
||||
public $path = [
|
||||
'group' => 'sales',
|
||||
'type' => 'customers',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-sales-customers',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-sales-customers',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-sales-customers',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-sales-customers',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.sales.customers.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$customers = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($customers as $customer) {
|
||||
try {
|
||||
$request->merge([
|
||||
'enabled' => $customer->enabled,
|
||||
'uploaded_logo' => $customer->logo,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateContact($customer, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$this->disableContacts($request);
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$this->deleteContacts($request);
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.customers', 2));
|
||||
}
|
||||
}
|
||||
172
app/BulkActions/Sales/Invoices.php
Normal file
172
app/BulkActions/Sales/Invoices.php
Normal file
@ -0,0 +1,172 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Sales;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Models\Document\Document;
|
||||
use App\Jobs\Document\DeleteDocument;
|
||||
use App\Jobs\Common\CreateZipForDownload;
|
||||
use App\Jobs\Document\UpdateDocument;
|
||||
use App\Events\Document\DocumentCreated;
|
||||
use App\Events\Document\DocumentCancelled;
|
||||
use App\Events\Document\DocumentMarkedSent;
|
||||
use App\Exports\Sales\Invoices\Invoices as Export;
|
||||
|
||||
class Invoices extends BulkAction
|
||||
{
|
||||
public $model = Document::class;
|
||||
|
||||
public $text = 'general.invoices';
|
||||
|
||||
public $path = [
|
||||
'group' => 'sales',
|
||||
'type' => 'invoices',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-sales-invoices',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'sent' => [
|
||||
'icon' => 'send',
|
||||
'name' => 'invoices.mark_sent',
|
||||
'message' => 'bulk_actions.message.sent',
|
||||
'permission' => 'update-sales-invoices',
|
||||
],
|
||||
'cancelled' => [
|
||||
'icon' => 'cancel',
|
||||
'name' => 'documents.actions.cancel',
|
||||
'message' => 'bulk_actions.message.cancelled',
|
||||
'permission' => 'update-sales-invoices',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-sales-invoices',
|
||||
],
|
||||
'export' => [
|
||||
'icon' => 'file_download',
|
||||
'name' => 'general.export',
|
||||
'message' => 'bulk_actions.message.export',
|
||||
'type' => 'download',
|
||||
],
|
||||
'download' => [
|
||||
'icon' => 'download',
|
||||
'name' => 'general.download',
|
||||
'message' => 'bulk_actions.message.download',
|
||||
'type' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->response('bulk-actions.sales.invoices.edit', compact('selected'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$invoices = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
try {
|
||||
$discount = $invoice->totals->where('code', 'discount')->makeHidden('title')->pluck('amount')->first();
|
||||
|
||||
// for extra total rows..
|
||||
$totals = $invoice->totals()->whereNotIn('code', ['sub_total', 'total', 'tax', 'discount'])->get()->toArray();
|
||||
|
||||
$request->merge([
|
||||
'items' => $invoice->items->toArray(),
|
||||
'uploaded_attachment' => $invoice->attachment,
|
||||
'category_id' => ($request->get('category_id')) ?? $invoice->category_id,
|
||||
'discount' => $discount,
|
||||
'totals' => $totals,
|
||||
])->except([
|
||||
|
||||
]);
|
||||
|
||||
$this->dispatch(new UpdateDocument($invoice, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function sent($request)
|
||||
{
|
||||
$invoices = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if ($invoice->status == 'sent') {
|
||||
continue;
|
||||
}
|
||||
|
||||
event(new DocumentMarkedSent($invoice));
|
||||
}
|
||||
}
|
||||
|
||||
public function cancelled($request)
|
||||
{
|
||||
$invoices = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
if (in_array($invoice->status, ['cancelled', 'draft'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
event(new DocumentCancelled($invoice));
|
||||
}
|
||||
}
|
||||
|
||||
public function duplicate($request)
|
||||
{
|
||||
$invoices = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$clone = $invoice->duplicate();
|
||||
|
||||
event(new DocumentCreated($clone, $request));
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$invoices = $this->getSelectedRecords($request, [
|
||||
'items', 'item_taxes', 'histories', 'transactions', 'recurring', 'totals'
|
||||
]);
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
try {
|
||||
$this->dispatch(new DeleteDocument($invoice));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function export($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
return $this->exportExcel(new Export($selected), trans_choice('general.invoices', 2));
|
||||
}
|
||||
|
||||
public function download($request)
|
||||
{
|
||||
$selected = $this->getSelectedRecords($request);
|
||||
|
||||
$file_name = Document::INVOICE_TYPE . '-'. date('Y-m-d-H-i-s');
|
||||
|
||||
$class = '\App\Jobs\Document\DownloadDocument';
|
||||
|
||||
return $this->downloadPdf($selected, $class, $file_name, trans_choice('general.invoices', 2));
|
||||
}
|
||||
}
|
||||
|
||||
117
app/BulkActions/Settings/Categories.php
Normal file
117
app/BulkActions/Settings/Categories.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Settings;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Setting\DeleteCategory;
|
||||
use App\Jobs\Setting\UpdateCategory;
|
||||
use App\Models\Setting\Category;
|
||||
use App\Traits\Categories as Helper;
|
||||
|
||||
class Categories extends BulkAction
|
||||
{
|
||||
use Helper;
|
||||
|
||||
public $model = Category::class;
|
||||
|
||||
public $text = 'general.categories';
|
||||
|
||||
public $path = [
|
||||
'group' => 'settings',
|
||||
'type' => 'categories',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-settings-categories',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-settings-categories',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-settings-categories',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-settings-categories',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$types = $this->getCategoryTypes();
|
||||
|
||||
return $this->response('bulk-actions.settings.categories.edit', compact('selected', 'types'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$categories = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($categories as $category) {
|
||||
try {
|
||||
$request->merge([
|
||||
'enabled' => $category->enabled,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateCategory($category, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$categories = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($categories as $category) {
|
||||
try {
|
||||
$this->dispatch(new UpdateCategory($category, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$categories = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($categories as $category) {
|
||||
try {
|
||||
$this->dispatch(new DeleteCategory($category));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getSelectedRecords($request, $relationships = null)
|
||||
{
|
||||
if (empty($relationships)) {
|
||||
$model = $this->model::query();
|
||||
} else {
|
||||
$relationships = Arr::wrap($relationships);
|
||||
|
||||
$model = $this->model::with($relationships);
|
||||
}
|
||||
|
||||
return $model->getWithoutChildren()->find($this->getSelectedInput($request));
|
||||
}
|
||||
}
|
||||
108
app/BulkActions/Settings/Currencies.php
Normal file
108
app/BulkActions/Settings/Currencies.php
Normal file
@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Settings;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Setting\DeleteCurrency;
|
||||
use App\Jobs\Setting\UpdateCurrency;
|
||||
use App\Models\Setting\Currency;
|
||||
|
||||
class Currencies extends BulkAction
|
||||
{
|
||||
public $model = Currency::class;
|
||||
|
||||
public $text = 'general.currencies';
|
||||
|
||||
public $path = [
|
||||
'group' => 'settings',
|
||||
'type' => 'currencies',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-settings-currencies',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-settings-currencies',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-settings-currencies',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-settings-currencies',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$precisions = (object) [
|
||||
'0' => '0',
|
||||
'1' => '1',
|
||||
'2' => '2',
|
||||
'3' => '3',
|
||||
'4' => '4',
|
||||
];
|
||||
|
||||
return $this->response('bulk-actions.settings.currencies.edit', compact('selected', 'precisions'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$currencies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
try {
|
||||
$request->merge([
|
||||
'code' => $currency->code,
|
||||
'enabled' => $currency->enabled,
|
||||
]); // for update job authorize..
|
||||
|
||||
$this->dispatch(new UpdateCurrency($currency, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$currencies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
try {
|
||||
$this->dispatch(new UpdateCurrency($currency, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$currencies = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
try {
|
||||
$this->dispatch(new DeleteCurrency($currency));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
109
app/BulkActions/Settings/Taxes.php
Normal file
109
app/BulkActions/Settings/Taxes.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\BulkActions\Settings;
|
||||
|
||||
use App\Abstracts\BulkAction;
|
||||
use App\Jobs\Setting\DeleteTax;
|
||||
use App\Jobs\Setting\UpdateTax;
|
||||
use App\Models\Setting\Tax;
|
||||
|
||||
class Taxes extends BulkAction
|
||||
{
|
||||
public $model = Tax::class;
|
||||
|
||||
public $text = 'general.taxes';
|
||||
|
||||
public $path = [
|
||||
'group' => 'settings',
|
||||
'type' => 'taxes',
|
||||
];
|
||||
|
||||
public $actions = [
|
||||
'edit' => [
|
||||
'icon' => 'edit',
|
||||
'name' => 'general.edit',
|
||||
'message' => '',
|
||||
'permission' => 'update-settings-taxes',
|
||||
'type' => 'modal',
|
||||
'handle' => 'update',
|
||||
],
|
||||
'enable' => [
|
||||
'icon' => 'check_circle',
|
||||
'name' => 'general.enable',
|
||||
'message' => 'bulk_actions.message.enable',
|
||||
'permission' => 'update-settings-taxes',
|
||||
],
|
||||
'disable' => [
|
||||
'icon' => 'hide_source',
|
||||
'name' => 'general.disable',
|
||||
'message' => 'bulk_actions.message.disable',
|
||||
'permission' => 'update-settings-taxes',
|
||||
],
|
||||
'delete' => [
|
||||
'icon' => 'delete',
|
||||
'name' => 'general.delete',
|
||||
'message' => 'bulk_actions.message.delete',
|
||||
'permission' => 'delete-settings-taxes',
|
||||
],
|
||||
];
|
||||
|
||||
public function edit($request)
|
||||
{
|
||||
$selected = $this->getSelectedInput($request);
|
||||
|
||||
$types = [
|
||||
'fixed' => trans('taxes.fixed'),
|
||||
'normal' => trans('taxes.normal'),
|
||||
'inclusive' => trans('taxes.inclusive'),
|
||||
'withholding' => trans('taxes.withholding'),
|
||||
'compound' => trans('taxes.compound'),
|
||||
];
|
||||
|
||||
$disable_options = [];
|
||||
|
||||
if ($compound = Tax::compound()->first()) {
|
||||
$disable_options = ['compound'];
|
||||
}
|
||||
|
||||
return $this->response('bulk-actions.settings.taxes.edit', compact('selected', 'types', 'disable_options'));
|
||||
}
|
||||
|
||||
public function update($request)
|
||||
{
|
||||
$taxes = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($taxes as $tax) {
|
||||
try {
|
||||
$this->dispatch(new UpdateTax($tax, $this->getUpdateRequest($request)));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function disable($request)
|
||||
{
|
||||
$taxes = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($taxes as $tax) {
|
||||
try {
|
||||
$this->dispatch(new UpdateTax($tax, $request->merge(['enabled' => 0])));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy($request)
|
||||
{
|
||||
$taxes = $this->getSelectedRecords($request);
|
||||
|
||||
foreach ($taxes as $tax) {
|
||||
try {
|
||||
$this->dispatch(new DeleteTax($tax));
|
||||
} catch (\Exception $e) {
|
||||
flash($e->getMessage())->error()->important();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/BulkAction.php
Normal file
29
app/Classifiers/BulkAction.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class BulkAction implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Bulk Actions';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\BulkAction::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Event.php
Normal file
29
app/Classifiers/Event.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Event implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Events';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Event::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Export.php
Normal file
29
app/Classifiers/Export.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Export implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Exports';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Export::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Import.php
Normal file
29
app/Classifiers/Import.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Import implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Imports';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Import::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Job.php
Normal file
29
app/Classifiers/Job.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Job implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Jobs';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Job::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Observer.php
Normal file
29
app/Classifiers/Observer.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Observer implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Observers';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Observer::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Report.php
Normal file
29
app/Classifiers/Report.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Report implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Reports';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Report::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Scope.php
Normal file
29
app/Classifiers/Scope.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Scope implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Scopes';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\Illuminate\Database\Eloquent\Scope::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
29
app/Classifiers/Widget.php
Normal file
29
app/Classifiers/Widget.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace App\Classifiers;
|
||||
|
||||
use Wnx\LaravelStats\ReflectionClass;
|
||||
use Wnx\LaravelStats\Contracts\Classifier;
|
||||
|
||||
class Widget implements Classifier
|
||||
{
|
||||
public function name(): string
|
||||
{
|
||||
return 'Widgets';
|
||||
}
|
||||
|
||||
public function satisfies(ReflectionClass $class): bool
|
||||
{
|
||||
return $class->isSubclassOf(\App\Abstracts\Widget::class);
|
||||
}
|
||||
|
||||
public function countsTowardsApplicationCode(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function countsTowardsTests(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
99
app/Console/Commands/BillReminder.php
Normal file
99
app/Console/Commands/BillReminder.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Document\DocumentReminded;
|
||||
use App\Models\Common\Company;
|
||||
use App\Models\Document\Document;
|
||||
use App\Notifications\Purchase\Bill as Notification;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class BillReminder extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reminder:bill';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send reminders for bills';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Disable model cache
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
|
||||
$today = Date::today();
|
||||
|
||||
$start_date = $today->copy()->subWeek()->toDateString() . ' 00:00:00';
|
||||
$end_date = $today->copy()->addMonth()->toDateString() . ' 23:59:59';
|
||||
|
||||
// Get all companies
|
||||
$companies = Company::whereHas('bills', function (Builder $query) use ($start_date, $end_date) {
|
||||
$query->allCompanies();
|
||||
$query->whereBetween('due_at', [$start_date, $end_date]);
|
||||
$query->accrued();
|
||||
$query->notPaid();
|
||||
})
|
||||
->enabled()
|
||||
->cursor();
|
||||
|
||||
foreach ($companies as $company) {
|
||||
$this->info('Sending bill reminders for ' . $company->name . ' company.');
|
||||
|
||||
// Set company
|
||||
$company->makeCurrent();
|
||||
|
||||
// Don't send reminders if disabled
|
||||
if (! setting('schedule.send_bill_reminder')) {
|
||||
$this->info('Bill reminders disabled by ' . $company->name . '.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$days = explode(',', setting('schedule.bill_days'));
|
||||
|
||||
foreach ($days as $day) {
|
||||
$day = (int) trim($day);
|
||||
|
||||
$this->remind($day);
|
||||
}
|
||||
}
|
||||
|
||||
Company::forgetCurrent();
|
||||
}
|
||||
|
||||
protected function remind($day)
|
||||
{
|
||||
// Get due date
|
||||
$date = Date::today()->addDays($day)->toDateString();
|
||||
|
||||
// Get upcoming bills
|
||||
$bills = Document::with('contact')->bill()->accrued()->notPaid()->due($date)->cursor();
|
||||
|
||||
foreach ($bills as $bill) {
|
||||
$this->info($bill->document_number . ' bill reminded.');
|
||||
|
||||
try {
|
||||
event(new DocumentReminded($bill, Notification::class));
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
50
app/Console/Commands/CompanySeed.php
Normal file
50
app/Console/Commands/CompanySeed.php
Normal file
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
class CompanySeed extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'company:seed {company} {--class= : with Fully Qualified Name}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Run one or all seeds for a specific company';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
* @throws \Illuminate\Contracts\Container\BindingResolutionException
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$class_name = $this->input->getOption('class') ?? 'Database\Seeds\Company';
|
||||
|
||||
$class = $this->laravel->make($class_name);
|
||||
|
||||
$class->setContainer($this->laravel)->setCommand($this)->__invoke();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the console command options.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getOptions()
|
||||
{
|
||||
return [
|
||||
['class', null, InputOption::VALUE_OPTIONAL, 'The class name of the root seeder', 'Database\Seeds\Company'],
|
||||
];
|
||||
}
|
||||
}
|
||||
130
app/Console/Commands/DownloadModule.php
Normal file
130
app/Console/Commands/DownloadModule.php
Normal file
@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Jobs\Install\CopyFiles;
|
||||
use App\Jobs\Install\DownloadFile;
|
||||
use App\Jobs\Install\UnzipFile;
|
||||
use App\Traits\Jobs;
|
||||
use App\Utilities\Versions;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class DownloadModule extends Command
|
||||
{
|
||||
use Jobs;
|
||||
|
||||
const CMD_SUCCESS = 0;
|
||||
|
||||
const CMD_ERROR = 1;
|
||||
|
||||
public $alias;
|
||||
|
||||
public $company;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'module:download {alias} {company}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Download the specified module.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
set_time_limit(3600); // 1 hour
|
||||
|
||||
$this->alias = $this->argument('alias');
|
||||
$this->company = $this->argument('company');
|
||||
|
||||
company($this->company)->makeCurrent();
|
||||
|
||||
if (!$path = $this->download()) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!$this->unzip($path)) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!$this->copyFiles($path)) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
$this->info("Module [{$this->alias}] downloaded!");
|
||||
|
||||
return self::CMD_SUCCESS;
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$this->info('Downloading module...');
|
||||
|
||||
try {
|
||||
$path = $this->dispatch(new DownloadFile($this->alias, $this->getVersion()));
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function unzip($path)
|
||||
{
|
||||
$this->info('Unzipping module...');
|
||||
|
||||
try {
|
||||
$this->dispatch(new UnzipFile($this->alias, $path));
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function copyFiles($path)
|
||||
{
|
||||
$this->info('Copying module files...');
|
||||
|
||||
try {
|
||||
$this->dispatch(new CopyFiles($this->alias, $path));
|
||||
|
||||
event(new \App\Events\Module\Copied($this->alias, $this->company));
|
||||
} catch (\Exception $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getVersion()
|
||||
{
|
||||
$version = Versions::latest($this->alias);
|
||||
|
||||
if (empty($version)) {
|
||||
$current = '1.0.0';
|
||||
|
||||
$url = 'apps/' . $this->alias . '/version/' . $current . '/' . version('short');
|
||||
|
||||
$version = Versions::getLatestVersion($url, $current);
|
||||
}
|
||||
|
||||
return $version?->latest;
|
||||
}
|
||||
}
|
||||
68
app/Console/Commands/FinishUpdate.php
Normal file
68
app/Console/Commands/FinishUpdate.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Install\UpdateFinished;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class FinishUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'update:finish {alias} {company} {new} {old}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Finish the update process through CLI';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
set_time_limit(3600); // 1 hour
|
||||
|
||||
$this->info('Finishing update...');
|
||||
|
||||
$this->call('cache:clear');
|
||||
|
||||
$alias = $this->argument('alias');
|
||||
$company_id = $this->argument('company');
|
||||
$new = $this->argument('new');
|
||||
$old = $this->argument('old');
|
||||
|
||||
// Check if file mirror was successful
|
||||
$version = ($alias == 'core') ? version('short') : module($alias)->get('version');
|
||||
if ($version != $new) {
|
||||
logger($alias . ' update failed:: file version > ' . $version . ' -vs- ' . 'request version > ' . $new);
|
||||
|
||||
throw new \Exception(trans('modules.errors.finish', ['module' => $alias]));
|
||||
}
|
||||
|
||||
$company = company($company_id);
|
||||
|
||||
if (empty($company)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$company->makeCurrent();
|
||||
|
||||
// Set locale for modules
|
||||
if (($alias != 'core') && !empty($company->locale)) {
|
||||
app()->setLocale($company->locale);
|
||||
}
|
||||
|
||||
// Disable model cache during update
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
|
||||
event(new UpdateFinished($alias, $new, $old));
|
||||
}
|
||||
}
|
||||
201
app/Console/Commands/Install.php
Normal file
201
app/Console/Commands/Install.php
Normal file
@ -0,0 +1,201 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Utilities\Installer;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
|
||||
class Install extends Command
|
||||
{
|
||||
const CMD_SUCCESS = 0;
|
||||
const CMD_ERROR = 1;
|
||||
const OPT_DB_HOST = 'db-host';
|
||||
const OPT_DB_PORT = 'db-port';
|
||||
const OPT_DB_NAME = 'db-name';
|
||||
const OPT_DB_USERNAME = 'db-username';
|
||||
const OPT_DB_PASSWORD = 'db-password';
|
||||
const OPT_DB_PREFIX = 'db-prefix';
|
||||
const OPT_COMPANY_NAME = 'company-name';
|
||||
const OPT_COMPANY_EMAIL = 'company-email';
|
||||
const OPT_ADMIN_EMAIL = 'admin-email';
|
||||
const OPT_ADMIN_PASSWORD = 'admin-password';
|
||||
const OPT_LOCALE = 'locale';
|
||||
const OPT_NO_INTERACTION = 'no-interaction';
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'install
|
||||
{--db-host=localhost : Database host}
|
||||
{--db-port=3306 : Port of the database host}
|
||||
{--db-name= : Name of the database}
|
||||
{--db-username=root : Username to use to access the database}
|
||||
{--db-password= : Password to use to access the database}
|
||||
{--db-prefix= : Table name prefix}
|
||||
{--company-name=My Company : Name of the company}
|
||||
{--company-email=my@company.com : Email of the company}
|
||||
{--admin-email= : Admin user email}
|
||||
{--admin-password= : Admin user password}
|
||||
{--locale=en-GB : Language used in the app}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Allows to install Akaunting directly through CLI';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (($missing_options = $this->getMissingOptions()) && $this->option(self::OPT_NO_INTERACTION)) {
|
||||
$this->line('❌ Some options are missing and --no-interaction is present. Please run the following command for more information :');
|
||||
$this->line('❌ php artisan help install');
|
||||
$this->line('❌ Missing options are : ' . implode(', ', $missing_options));
|
||||
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
$this->line('Setting locale ' . $this->locale);
|
||||
Session::put(self::OPT_LOCALE, $this->locale);
|
||||
app()->setLocale($this->locale);
|
||||
|
||||
$this->prompt();
|
||||
|
||||
// Create the .env file
|
||||
Installer::createDefaultEnvFile();
|
||||
|
||||
$this->line('Creating database tables');
|
||||
if (! $this->createDatabaseTables()) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
DB::transaction(function () {
|
||||
$this->line('Creating company');
|
||||
Installer::createCompany($this->company_name, $this->company_email, $this->locale);
|
||||
|
||||
$this->line('Creating admin');
|
||||
Installer::createUser($this->admin_email, $this->admin_password, $this->locale);
|
||||
});
|
||||
|
||||
$this->line('Applying the final touches');
|
||||
Installer::finalTouches();
|
||||
|
||||
return self::CMD_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that all options are presents. otherwise returns an array of the missing options
|
||||
*/
|
||||
private function getMissingOptions()
|
||||
{
|
||||
$missing_options = [];
|
||||
|
||||
$constants = [
|
||||
'OPT_LOCALE',
|
||||
'OPT_DB_PORT',
|
||||
'OPT_DB_HOST',
|
||||
'OPT_DB_NAME',
|
||||
'OPT_DB_USERNAME',
|
||||
'OPT_DB_PASSWORD',
|
||||
'OPT_DB_PREFIX',
|
||||
'OPT_COMPANY_NAME',
|
||||
'OPT_COMPANY_EMAIL',
|
||||
'OPT_ADMIN_EMAIL',
|
||||
'OPT_ADMIN_PASSWORD'
|
||||
];
|
||||
|
||||
$allowed_empty = ['db_password', 'db_prefix'];
|
||||
|
||||
foreach ($constants as $const) {
|
||||
$option = constant("self::$const");
|
||||
|
||||
$property = str_replace('-', '_', $option);
|
||||
|
||||
$this->$property = $this->option($option);
|
||||
|
||||
if (!empty($this->$property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($property, $allowed_empty)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$missing_options[] = $option;
|
||||
}
|
||||
|
||||
return $missing_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the user for data if some options are missing.
|
||||
*/
|
||||
private function prompt()
|
||||
{
|
||||
if (empty($this->db_host)) {
|
||||
$this->db_host = $this->ask('What is the database host?', 'localhost');
|
||||
}
|
||||
|
||||
if (empty($this->db_port)) {
|
||||
$this->db_port = $this->ask('What is the database port?', '3306');
|
||||
}
|
||||
|
||||
if (empty($this->db_name)) {
|
||||
$this->db_name = $this->ask('What is the database name?');
|
||||
}
|
||||
|
||||
if (empty($this->db_username)) {
|
||||
$this->db_username = $this->ask('What is the database username?', 'root');
|
||||
}
|
||||
|
||||
if (!isset($this->db_password)) {
|
||||
$this->db_password = $this->secret('What is the database password?', '');
|
||||
}
|
||||
|
||||
if (empty($this->company_name)) {
|
||||
$this->company_name = $this->ask('What is the company name?', 'My Company');
|
||||
}
|
||||
|
||||
if (empty($this->company_email)) {
|
||||
$this->company_email = $this->ask('What is the company contact email?', 'my@company.com');
|
||||
}
|
||||
|
||||
if (empty($this->admin_email)) {
|
||||
$this->admin_email = $this->ask('What is the admin email?', $this->company_email);
|
||||
}
|
||||
|
||||
if (empty($this->admin_password)) {
|
||||
$this->admin_password = $this->secret('What is the admin password?');
|
||||
}
|
||||
}
|
||||
|
||||
private function createDatabaseTables()
|
||||
{
|
||||
$this->db_host = $this->option(self::OPT_DB_HOST);
|
||||
$this->db_port = $this->option(self::OPT_DB_PORT);
|
||||
$this->db_name = $this->option(self::OPT_DB_NAME);
|
||||
$this->db_username = $this->option(self::OPT_DB_USERNAME);
|
||||
$this->db_password = $this->option(self::OPT_DB_PASSWORD);
|
||||
$this->db_prefix = $this->option(self::OPT_DB_PREFIX);
|
||||
|
||||
$this->line('Connecting to database ' . $this->db_name . '@' . $this->db_host . ':' . $this->db_port);
|
||||
|
||||
if (!Installer::createDbTables($this->db_host, $this->db_port, $this->db_name, $this->db_username, $this->db_password, $this->db_prefix)) {
|
||||
$this->error('Error: Could not connect to the database! Please, make sure the details are correct.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
57
app/Console/Commands/InstallRefresh.php
Normal file
57
app/Console/Commands/InstallRefresh.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Common\Company;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class InstallRefresh extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'install:refresh {--admin-password=123456}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Allows to refresh Akaunting installation directly through CLI';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$user = user_model_class()::first();
|
||||
$company = Company::first();
|
||||
|
||||
$this->info('Resetting migrations');
|
||||
$this->callSilent('migrate:reset', [
|
||||
'--force' => true,
|
||||
]);
|
||||
|
||||
$this->info('Installing Akaunting');
|
||||
$this->callSilent('install', [
|
||||
'--db-host' => env('DB_HOST'),
|
||||
'--db-port' => env('DB_PORT'),
|
||||
'--db-name' => env('DB_DATABASE'),
|
||||
'--db-username' => env('DB_USERNAME'),
|
||||
'--db-password' => env('DB_PASSWORD'),
|
||||
'--db-prefix' => env('DB_PREFIX'),
|
||||
'--company-name' => $company->name,
|
||||
'--company-email' => $company->email,
|
||||
'--admin-email' => $user->email,
|
||||
'--admin-password' => $this->option('admin-password'),
|
||||
'--locale' => $company->locale,
|
||||
'--no-interaction' => true,
|
||||
]);
|
||||
|
||||
$this->info('Installation refreshed');
|
||||
}
|
||||
}
|
||||
99
app/Console/Commands/InvoiceReminder.php
Normal file
99
app/Console/Commands/InvoiceReminder.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Document\DocumentReminded;
|
||||
use App\Models\Common\Company;
|
||||
use App\Models\Document\Document;
|
||||
use App\Notifications\Sale\Invoice as Notification;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class InvoiceReminder extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'reminder:invoice';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Send reminders for invoices';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
// Disable model cache
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
|
||||
$today = Date::today();
|
||||
|
||||
$start_date = $today->copy()->subMonth()->toDateString() . ' 00:00:00';
|
||||
$end_date = $today->copy()->addWeek()->toDateString() . ' 23:59:59';
|
||||
|
||||
// Get all companies
|
||||
$companies = Company::whereHas('invoices', function (Builder $query) use ($start_date, $end_date) {
|
||||
$query->allCompanies();
|
||||
$query->whereBetween('due_at', [$start_date, $end_date]);
|
||||
$query->accrued();
|
||||
$query->notPaid();
|
||||
})
|
||||
->enabled()
|
||||
->cursor();
|
||||
|
||||
foreach ($companies as $company) {
|
||||
$this->info('Sending invoice reminders for ' . $company->name . ' company.');
|
||||
|
||||
// Set company
|
||||
$company->makeCurrent();
|
||||
|
||||
// Don't send reminders if disabled
|
||||
if (! setting('schedule.send_invoice_reminder')) {
|
||||
$this->info('Invoice reminders disabled by ' . $company->name . '.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$days = explode(',', setting('schedule.invoice_days'));
|
||||
|
||||
foreach ($days as $day) {
|
||||
$day = (int) trim($day);
|
||||
|
||||
$this->remind($day);
|
||||
}
|
||||
}
|
||||
|
||||
Company::forgetCurrent();
|
||||
}
|
||||
|
||||
protected function remind($day)
|
||||
{
|
||||
// Get due date
|
||||
$date = Date::today()->subDays($day)->toDateString();
|
||||
|
||||
// Get upcoming invoices
|
||||
$invoices = Document::with('contact')->invoice()->accrued()->notPaid()->due($date)->cursor();
|
||||
|
||||
foreach ($invoices as $invoice) {
|
||||
$this->info($invoice->document_number . ' invoice reminded.');
|
||||
|
||||
try {
|
||||
event(new DocumentReminded($invoice, Notification::class));
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
report($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
284
app/Console/Commands/RecurringCheck.php
Normal file
284
app/Console/Commands/RecurringCheck.php
Normal file
@ -0,0 +1,284 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Banking\TransactionCreated;
|
||||
use App\Events\Banking\TransactionRecurring;
|
||||
use App\Events\Document\DocumentCreated;
|
||||
use App\Events\Document\DocumentRecurring;
|
||||
use App\Models\Banking\Transaction;
|
||||
use App\Models\Common\Company;
|
||||
use App\Models\Common\Recurring;
|
||||
use App\Models\Document\Document;
|
||||
use App\Utilities\Date;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Recurr\RecurrenceCollection;
|
||||
|
||||
class RecurringCheck extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'recurring:check';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Check for recurring';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Checking for recurring...');
|
||||
|
||||
// Bind to container
|
||||
app()->instance(static::class, $this);
|
||||
|
||||
// Disable model cache
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
|
||||
// Get all recurring
|
||||
$recurring = Recurring::with('company')
|
||||
/*->whereHas('recurable', function (Builder $query) {
|
||||
$query->allCompanies();
|
||||
})*/
|
||||
->active()
|
||||
->allCompanies()
|
||||
->cursor();
|
||||
|
||||
//$this->info('Total recurring: ' . $recurring->count());
|
||||
|
||||
foreach ($recurring as $recur) {
|
||||
if (empty($recur->company)) {
|
||||
$this->info('Missing company.');
|
||||
|
||||
$recur->delete();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info('Recurring ID: ' . $recur->id);
|
||||
|
||||
$company_name = !empty($recur->company->name) ? $recur->company->name : 'Missing Company Name : ' . $recur->company->id;
|
||||
|
||||
$template = $recur->recurable()->where('company_id', $recur->company_id)->first();
|
||||
|
||||
// Check if company is disabled
|
||||
if (! $recur->company->enabled) {
|
||||
$this->info($company_name . ' company is disabled. Skipping...');
|
||||
|
||||
if (Date::parse($recur->company->updated_at)->format('Y-m-d') > Date::now()->subMonth(3)->format('Y-m-d')) {
|
||||
$recur->delete();
|
||||
|
||||
if ($template) {
|
||||
$template->delete();
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if company has any active user
|
||||
$has_active_users = false;
|
||||
|
||||
foreach ($recur->company->users as $company_user) {
|
||||
if (Date::parse($company_user->last_logged_in_at)->format('Y-m-d') > Date::now()->subMonth(3)->format('Y-m-d')) {
|
||||
$has_active_users = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (! $has_active_users) {
|
||||
$this->info('No active users for ' . $company_name . ' company. Skipping...');
|
||||
|
||||
$recur->delete();
|
||||
|
||||
if ($template) {
|
||||
$template->delete();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
company($recur->company_id)->makeCurrent();
|
||||
|
||||
if (! $template) {
|
||||
$this->info('Missing model.');
|
||||
|
||||
$recur->delete();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->info('Template ID: ' . $template->id);
|
||||
|
||||
// Get the remaining schedules, including the previously failed ones
|
||||
$schedules = $this->getRemainingSchedules($template, $recur);
|
||||
|
||||
// Check if all schedules created
|
||||
if ($schedules->count() == 0) {
|
||||
$this->info('All schedules created.');
|
||||
|
||||
$recur->update(['status' => Recurring::COMPLETE_STATUS]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't create schedules for the future
|
||||
$schedules = $schedules->endsBefore($recur->getRecurringRuleTomorrowDate());
|
||||
|
||||
if ($schedules->count() == 0) {
|
||||
$this->info('No schedules for today.');
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($schedules as $schedule) {
|
||||
$schedule_date = Date::parse($schedule->getStart()->format('Y-m-d'));
|
||||
|
||||
$this->info('Schedule date: ' . $schedule_date->format('Y-m-d'));
|
||||
|
||||
$this->recur($template, $schedule_date);
|
||||
}
|
||||
}
|
||||
|
||||
Company::forgetCurrent();
|
||||
|
||||
// Remove from container
|
||||
app()->forgetInstance(static::class);
|
||||
|
||||
$this->info('Recurring check done!');
|
||||
}
|
||||
|
||||
protected function recur(Document|Transaction $template, Date $schedule_date): void
|
||||
{
|
||||
DB::transaction(function () use ($template, $schedule_date) {
|
||||
if (! $model = $this->getModel($template, $schedule_date)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->info('Model created: ' . $model->id);
|
||||
|
||||
switch ($template::class) {
|
||||
case Document::class:
|
||||
event(new DocumentCreated($model, request()));
|
||||
|
||||
event(new DocumentRecurring($model));
|
||||
|
||||
break;
|
||||
case Transaction::class:
|
||||
event(new TransactionCreated($model));
|
||||
|
||||
event(new TransactionRecurring($model));
|
||||
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function getModel(Document|Transaction $template, Date $schedule_date): Document|Transaction
|
||||
{
|
||||
$function = ($template instanceof Transaction) ? 'getTransactionModel' : 'getDocumentModel';
|
||||
|
||||
try {
|
||||
return $this->$function($template, $schedule_date);
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
|
||||
report($e);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getDocumentModel(Document $template, Date $schedule_date): Document
|
||||
{
|
||||
$template->cloneable_relations = ['items', 'totals'];
|
||||
|
||||
$model = $template->duplicate();
|
||||
|
||||
// Days between issued and due date
|
||||
$diff_days = Date::parse($template->due_at)->diffInDays(Date::parse($template->issued_at));
|
||||
|
||||
$model->type = $this->getRealType($template->type);
|
||||
$model->parent_id = $template->id;
|
||||
$model->issued_at = $schedule_date->format('Y-m-d');
|
||||
$model->due_at = $schedule_date->copy()->addDays($diff_days)->format('Y-m-d');
|
||||
$model->created_from = 'core::recurring';
|
||||
$model->save();
|
||||
|
||||
$this->updateRelationTypes($model, $template->cloneable_relations);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
protected function getTransactionModel(Transaction $template, Date $schedule_date): Transaction
|
||||
{
|
||||
$template->cloneable_relations = ['taxes'];
|
||||
|
||||
$model = $template->duplicate();
|
||||
|
||||
$model->type = $this->getRealType($template->type);
|
||||
$model->parent_id = $template->id;
|
||||
$model->paid_at = $schedule_date->format('Y-m-d');
|
||||
$model->created_from = 'core::recurring';
|
||||
$model->save();
|
||||
|
||||
$this->updateRelationTypes($model, $template->cloneable_relations);
|
||||
|
||||
return $model;
|
||||
}
|
||||
|
||||
protected function getRemainingSchedules(Document|Transaction $template, Recurring $recur): RecurrenceCollection
|
||||
{
|
||||
$date_field = $this->getDateField($template);
|
||||
|
||||
$created_schedules = DB::table($template->getTable())
|
||||
->where('type', $this->getRealType($template->type))
|
||||
->where('parent_id', $template->id)
|
||||
->get($date_field)
|
||||
->transform(function ($item, $key) use ($date_field) {
|
||||
return Date::parse($item->$date_field)->format('Y-m-d');
|
||||
})
|
||||
->toArray();
|
||||
|
||||
// Skip already created schedules
|
||||
$schedules = $recur->getRecurringSchedule()->filter(function ($recurrence) use ($created_schedules) {
|
||||
return ! in_array($recurrence->getStart()->format('Y-m-d'), $created_schedules);
|
||||
});
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
protected function getDateField(Document|Transaction $template): string
|
||||
{
|
||||
return ($template instanceof Transaction) ? 'paid_at' : 'issued_at';
|
||||
}
|
||||
|
||||
public function getRealType(string $recurring_type): string
|
||||
{
|
||||
return Str::replace('-recurring', '', $recurring_type);
|
||||
}
|
||||
|
||||
public function updateRelationTypes($model, $relations)
|
||||
{
|
||||
foreach ($relations as $relation) {
|
||||
if (! method_exists($model, $relation)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$model->$relation()->update(['type' => $model->type]);
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/Console/Commands/SampleData.php
Normal file
37
app/Console/Commands/SampleData.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Database\Seeds\SampleData as SampleDataSeeder;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SampleData extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sample-data:seed {--count=100 : total records for each item} {--company=1 : the company id}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Seed for sample data';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$class = $this->laravel->make(SampleDataSeeder::class);
|
||||
|
||||
$seeder = $class->setContainer($this->laravel)->setCommand($this);
|
||||
|
||||
$seeder->__invoke();
|
||||
}
|
||||
}
|
||||
42
app/Console/Commands/StorageTempClear.php
Normal file
42
app/Console/Commands/StorageTempClear.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
class StorageTempClear extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'storage-temp:clear';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Clear all storage/app/temp files';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$filesystem = app(Filesystem::class);
|
||||
|
||||
$path = get_storage_path('app/temp');
|
||||
|
||||
foreach ($filesystem->glob("{$path}/*") as $file) {
|
||||
$filesystem->delete($file);
|
||||
}
|
||||
|
||||
$this->info('Temporary files cleared!');
|
||||
}
|
||||
}
|
||||
54
app/Console/Commands/UninstallModule.php
Normal file
54
app/Console/Commands/UninstallModule.php
Normal file
@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Abstracts\Commands\Module as Command;
|
||||
use App\Events\Module\Uninstalled;
|
||||
|
||||
class UninstallModule extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'module:uninstall {alias} {company} {locale=en-GB}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Uninstall the specified module.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->prepare();
|
||||
|
||||
if (!$this->getModel()) {
|
||||
$this->info("Module [{$this->alias}] not found.");
|
||||
return;
|
||||
}
|
||||
|
||||
$this->changeRuntime();
|
||||
|
||||
// Delete db
|
||||
$this->model->delete();
|
||||
|
||||
$this->createHistory('uninstalled');
|
||||
|
||||
event(new Uninstalled($this->alias, $this->company_id));
|
||||
|
||||
// Delete files
|
||||
module($this->alias)->delete();
|
||||
|
||||
$this->revertRuntime();
|
||||
|
||||
$this->info("Module [{$this->alias}] uninstalled.");
|
||||
}
|
||||
}
|
||||
199
app/Console/Commands/Update.php
Normal file
199
app/Console/Commands/Update.php
Normal file
@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Install\UpdateCopied;
|
||||
use App\Events\Install\UpdateDownloaded;
|
||||
use App\Events\Install\UpdateFailed;
|
||||
use App\Events\Install\UpdateUnzipped;
|
||||
use App\Jobs\Install\CopyFiles;
|
||||
use App\Jobs\Install\DownloadFile;
|
||||
use App\Jobs\Install\FinishUpdate;
|
||||
use App\Jobs\Install\UnzipFile;
|
||||
use App\Traits\Jobs;
|
||||
use App\Utilities\Versions;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class Update extends Command
|
||||
{
|
||||
use Jobs;
|
||||
|
||||
const CMD_SUCCESS = 0;
|
||||
|
||||
const CMD_ERROR = 1;
|
||||
|
||||
public $alias;
|
||||
|
||||
public $company;
|
||||
|
||||
public $new;
|
||||
|
||||
public $old;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'update {alias} {company} {new=latest}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Allows to update Akaunting and modules directly through CLI';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
set_time_limit(3600); // 1 hour
|
||||
|
||||
$this->alias = $this->argument('alias');
|
||||
$this->company = $this->argument('company');
|
||||
|
||||
if (false === $this->new = $this->getNewVersion()) {
|
||||
$message = 'Not able to get the latest version of ' . $this->alias . '!';
|
||||
|
||||
$this->error($message);
|
||||
|
||||
event(new UpdateFailed($this->alias, $this->new, $this->old, 'Version', $message));
|
||||
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
$this->old = $this->getOldVersion();
|
||||
|
||||
if (version_compare($this->old, $this->new, '>=')) {
|
||||
$message = 'The current version for the ' . $this->alias . ' is the latest version!';
|
||||
|
||||
$this->info($message);
|
||||
|
||||
return self::CMD_SUCCESS;
|
||||
}
|
||||
|
||||
company($this->company)->makeCurrent();
|
||||
|
||||
if (!$path = $this->download()) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!$this->unzip($path)) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!$this->copyFiles($path)) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
if (!$this->finish()) {
|
||||
return self::CMD_ERROR;
|
||||
}
|
||||
|
||||
return self::CMD_SUCCESS;
|
||||
}
|
||||
|
||||
public function getNewVersion()
|
||||
{
|
||||
return ($this->argument('new') == 'latest') ? Versions::latest($this->alias)?->latest : $this->argument('new');
|
||||
}
|
||||
|
||||
public function getOldVersion()
|
||||
{
|
||||
if ($this->alias == 'core') {
|
||||
return version('short');
|
||||
}
|
||||
|
||||
if ($module = module($this->alias)) {
|
||||
return $module->get('version');
|
||||
}
|
||||
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
public function download()
|
||||
{
|
||||
$this->info('Downloading update...');
|
||||
|
||||
try {
|
||||
$path = $this->dispatch(new DownloadFile($this->alias, $this->new));
|
||||
|
||||
event(new UpdateDownloaded($this->alias, $this->new, $this->old));
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
$this->error($message);
|
||||
|
||||
event(new UpdateFailed($this->alias, $this->new, $this->old, 'Download', $message));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function unzip($path)
|
||||
{
|
||||
$this->info('Unzipping update...');
|
||||
|
||||
try {
|
||||
$this->dispatch(new UnzipFile($this->alias, $path));
|
||||
|
||||
event(new UpdateUnzipped($this->alias, $this->new, $this->old));
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
$this->error($message);
|
||||
|
||||
event(new UpdateFailed($this->alias, $this->new, $this->old, 'Unzip', $message));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function copyFiles($path)
|
||||
{
|
||||
$this->info('Copying update files...');
|
||||
|
||||
try {
|
||||
$this->dispatch(new CopyFiles($this->alias, $path));
|
||||
|
||||
event(new UpdateCopied($this->alias, $this->new, $this->old));
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
$this->error($message);
|
||||
|
||||
event(new UpdateFailed($this->alias, $this->new, $this->old, 'Copy Files', $message));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function finish()
|
||||
{
|
||||
$this->info('Finishing update...');
|
||||
|
||||
try {
|
||||
$this->dispatch(new FinishUpdate($this->alias, $this->new, $this->old, $this->company));
|
||||
} catch (\Exception $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
$this->error($message);
|
||||
|
||||
event(new UpdateFailed($this->alias, $this->new, $this->old, 'Finish', $message));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
74
app/Console/Commands/UpdateAll.php
Normal file
74
app/Console/Commands/UpdateAll.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Utilities\Console;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdateAll extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'update:all {company=1}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Allows to update Akaunting and all modules at once';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
set_time_limit(0); // unlimited
|
||||
|
||||
$this->info('Starting update...');
|
||||
|
||||
// Update core
|
||||
if ($this->runUpdate('core') !== true) {
|
||||
$this->error('Not able to update core!');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Update modules
|
||||
$modules = module()->all();
|
||||
|
||||
foreach ($modules as $module) {
|
||||
$alias = $module->get('alias');
|
||||
|
||||
if ($this->runUpdate($alias) !== true) {
|
||||
$this->error('Not able to update ' . $alias . '!');
|
||||
}
|
||||
}
|
||||
|
||||
$this->info('Update finished.');
|
||||
}
|
||||
|
||||
protected function runUpdate($alias)
|
||||
{
|
||||
$this->info('Updating ' . $alias . '...');
|
||||
|
||||
$company_id = $this->argument('company');
|
||||
|
||||
$command = "update {$alias} {$company_id}";
|
||||
|
||||
if (true !== $result = Console::run($command)) {
|
||||
$message = !empty($result) ? $result : trans('modules.errors.finish', ['module' => $alias]);
|
||||
|
||||
$this->error($message);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
43
app/Console/Commands/UpdateDb.php
Normal file
43
app/Console/Commands/UpdateDb.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\Install\UpdateFinished;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UpdateDb extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'update:db {alias=core} {company=1} {new=3.0.0} {old=2.1.36}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Allows to update Akaunting database manually';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$alias = $this->argument('alias');
|
||||
$company_id = $this->argument('company');
|
||||
$new = $this->argument('new');
|
||||
$old = $this->argument('old');
|
||||
|
||||
company($company_id)->makeCurrent();
|
||||
|
||||
// Disable model cache during update
|
||||
config(['laravel-model-caching.enabled' => false]);
|
||||
|
||||
event(new UpdateFinished($alias, $new, $old));
|
||||
}
|
||||
}
|
||||
34
app/Console/Commands/UserSeed.php
Normal file
34
app/Console/Commands/UserSeed.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class UserSeed extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'user:seed {user} {company}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Seed for specific user';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$class = $this->laravel->make('Database\Seeds\User');
|
||||
|
||||
$class->setContainer($this->laravel)->setCommand($this)->__invoke();
|
||||
}
|
||||
}
|
||||
60
app/Console/Kernel.php
Normal file
60
app/Console/Kernel.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
// Not installed yet
|
||||
if (!config('app.installed')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$schedule_time = config('app.schedule_time');
|
||||
|
||||
$schedule->command('reminder:invoice')->dailyAt($schedule_time);
|
||||
$schedule->command('reminder:bill')->dailyAt($schedule_time);
|
||||
$schedule->command('recurring:check')->dailyAt($schedule_time)->runInBackground();
|
||||
$schedule->command('storage-temp:clear')->dailyAt('17:00');
|
||||
$schedule->command('model:prune')->dailyAt('17:00');
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the Closure based commands for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function commands()
|
||||
{
|
||||
require base_path('routes/console.php');
|
||||
|
||||
$this->load(__DIR__ . '/Commands');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timezone that should be used by default for scheduled events.
|
||||
*
|
||||
* @return \DateTimeZone|string|null
|
||||
*/
|
||||
protected function scheduleTimezone()
|
||||
{
|
||||
return config('app.timezone');
|
||||
}
|
||||
}
|
||||
0
app/Console/Stubs/Modules/assets/js/app.stub
Normal file
0
app/Console/Stubs/Modules/assets/js/app.stub
Normal file
0
app/Console/Stubs/Modules/assets/sass/app.stub
Normal file
0
app/Console/Stubs/Modules/assets/sass/app.stub
Normal file
32
app/Console/Stubs/Modules/command.stub
Normal file
32
app/Console/Stubs/Modules/command.stub
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class $CLASS$ extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = '$COMMAND_NAME$';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Command description.';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
28
app/Console/Stubs/Modules/component.stub
Normal file
28
app/Console/Stubs/Modules/component.stub
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace $NAMESPACE$;
|
||||
|
||||
use App\Abstracts\View\Component;
|
||||
|
||||
class $CLASS$ extends Component
|
||||
{
|
||||
/**
|
||||
* Create a new component instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the view / contents that represent the component.
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\View|string
|
||||
*/
|
||||
public function render()
|
||||
{
|
||||
return view('$ALIAS$::$VIEW_NAME$');
|
||||
}
|
||||
}
|
||||
17
app/Console/Stubs/Modules/composer.stub
Normal file
17
app/Console/Stubs/Modules/composer.stub
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"require": {
|
||||
|
||||
},
|
||||
"replace": {
|
||||
"guzzlehttp/guzzle": "*",
|
||||
"guzzlehttp/psr7": "*",
|
||||
"laravel/framework": "*",
|
||||
"omnipay/common": "*",
|
||||
"symfony/http-foundation": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"composer install --prefer-dist --no-interaction --no-scripts --no-suggest --no-progress --no-ansi"
|
||||
]
|
||||
}
|
||||
}
|
||||
61
app/Console/Stubs/Modules/controller-api.stub
Normal file
61
app/Console/Stubs/Modules/controller-api.stub
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace $CLASS_NAMESPACE$;
|
||||
|
||||
use App\Abstracts\Http\ApiController;
|
||||
use App\Abstracts\Http\FormRequest;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
|
||||
class $CLASS$ extends ApiController
|
||||
{
|
||||
/**
|
||||
* Display a listing of the resource.
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the specified resource.
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function show($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
* @param Request $request
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
* @param int $id
|
||||
* @return Response
|
||||
*/
|
||||
public function destroy($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
9
app/Console/Stubs/Modules/controller-plain.stub
Normal file
9
app/Console/Stubs/Modules/controller-plain.stub
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace $CLASS_NAMESPACE$;
|
||||
|
||||
use Illuminate\Routing\Controller;
|
||||
|
||||
class $CLASS$ extends Controller
|
||||
{
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user