Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27a511bc09 | ||
|
|
1317d57ef8 | ||
|
|
ba85f407d6 | ||
|
|
b37019f845 | ||
|
|
75b0c264dc | ||
|
|
98852686c7 | ||
|
|
72311db1bd | ||
|
|
1e530e0387 | ||
|
|
fa777d673f | ||
|
|
8570252db5 | ||
|
|
ecb029758b | ||
|
|
6ea112c04f | ||
|
|
2784318ebf | ||
|
|
bb580f4b88 | ||
|
|
8c83df558c | ||
|
|
dc8a85538f | ||
|
|
093b2acc24 | ||
|
|
df85fd6a0a | ||
|
|
db4396f160 | ||
|
|
1831560a62 | ||
|
|
223678e5bd | ||
|
|
9bb4963e8a | ||
|
|
8788f3d504 | ||
|
|
0d006846d5 | ||
|
|
d2559c8773 | ||
|
|
20a489e52d | ||
|
|
e73ab3d4ee | ||
|
|
1c7274287d | ||
|
|
5ba8639ac8 | ||
|
|
44153c2993 | ||
|
|
b9d5b8247c | ||
|
|
39879cb4b4 | ||
|
|
7d7b48a5b7 | ||
|
|
e0176139c2 | ||
|
|
29ed7f437a | ||
|
|
020a0741de | ||
|
|
d2ad2222be | ||
|
|
2358969050 | ||
|
|
c7c70dd3ef | ||
|
|
b806efd66d | ||
|
|
b85538dbc0 | ||
|
|
8918ea5124 | ||
|
|
91d37673aa | ||
|
|
b819440e76 | ||
|
|
36424bbf05 | ||
|
|
e14a248f24 | ||
|
|
acf3ca2c26 | ||
|
|
e7e9c57552 | ||
|
|
49a11f08e9 |
15
.env.example
15
.env.example
@@ -4,12 +4,12 @@ APP_DEBUG=true
|
||||
APP_LOG_LEVEL=debug
|
||||
APP_URL=http://invoiceshelf.test
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=db
|
||||
DB_PORT=3306
|
||||
DB_DATABASE=invoiceshelf
|
||||
DB_USERNAME=invoiceshelf
|
||||
DB_PASSWORD="invoiceshelf"
|
||||
DB_CONNECTION=sqlite
|
||||
DB_HOST=
|
||||
DB_PORT=
|
||||
DB_DATABASE=
|
||||
DB_USERNAME=
|
||||
DB_PASSWORD=
|
||||
|
||||
BROADCAST_DRIVER=log
|
||||
CACHE_DRIVER=file
|
||||
@@ -28,6 +28,9 @@ MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=
|
||||
|
||||
MAIL_FROM_NAME=
|
||||
MAIL_FROM_ADDRESS=
|
||||
|
||||
PUSHER_APP_ID=
|
||||
PUSHER_KEY=
|
||||
PUSHER_SECRET=
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -21,3 +21,4 @@ Homestead.yaml
|
||||
.DS_Store
|
||||
.php-cs-fixer.cache
|
||||
/storage/fonts*
|
||||
package-lock.json
|
||||
|
||||
1
Makefile
1
Makefile
@@ -25,6 +25,7 @@ dist-gen: clean composer npm-build
|
||||
@cp -r public/robots.txt InvoiceShelf/public
|
||||
@cp -r public/web.config InvoiceShelf/public
|
||||
@cp -r resources InvoiceShelf
|
||||
@cp -r lang InvoiceShelf
|
||||
@cp -r routes InvoiceShelf
|
||||
@cp -r storage InvoiceShelf
|
||||
@cp -r vendor InvoiceShelf 2> /dev/null || true
|
||||
|
||||
38
app/Bouncer/Scopes/DefaultScope.php
Normal file
38
app/Bouncer/Scopes/DefaultScope.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace InvoiceShelf\Bouncer\Scopes;
|
||||
|
||||
use Silber\Bouncer\Database\Scope\Scope;
|
||||
|
||||
class DefaultScope extends Scope
|
||||
{
|
||||
public function applyToModelQuery($query, $table = null)
|
||||
{
|
||||
if (is_null($this->scope) || $this->onlyScopeRelations) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
if (is_null($table)) {
|
||||
$table = $query->getModel()->getTable();
|
||||
}
|
||||
|
||||
return $this->applyToQuery($query, $table);
|
||||
}
|
||||
|
||||
public function applyToRelationQuery($query, $table)
|
||||
{
|
||||
if (is_null($this->scope)) {
|
||||
return $query;
|
||||
}
|
||||
|
||||
return $this->applyToQuery($query, $table);
|
||||
}
|
||||
|
||||
protected function applyToQuery($query, $table)
|
||||
{
|
||||
return $query->where(function ($query) use ($table) {
|
||||
$query->where("{$table}.scope", $this->scope)
|
||||
->orWhereNull("{$table}.scope");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ class BackupsController extends ApiController
|
||||
return [
|
||||
'path' => $backup->path(),
|
||||
'created_at' => $backup->date()->format('Y-m-d H:i:s'),
|
||||
'size' => Format::humanReadableSize($backup->size()),
|
||||
'size' => Format::humanReadableSize($backup->sizeInBytes()),
|
||||
];
|
||||
})
|
||||
->toArray();
|
||||
|
||||
@@ -41,7 +41,7 @@ class DownloadBackupController extends ApiController
|
||||
$downloadHeaders = [
|
||||
'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
|
||||
'Content-Type' => 'application/zip',
|
||||
'Content-Length' => $backup->size(),
|
||||
'Content-Length' => $backup->sizeInBytes(),
|
||||
'Content-Disposition' => 'attachment; filename="'.$fileName.'"',
|
||||
'Pragma' => 'public',
|
||||
];
|
||||
|
||||
@@ -94,6 +94,16 @@ class DiskController extends Controller
|
||||
|
||||
break;
|
||||
|
||||
case 's3compat':
|
||||
$diskData = [
|
||||
'endpoint' => '',
|
||||
'key' => '',
|
||||
'secret' => '',
|
||||
'region' => '',
|
||||
'bucket' => '',
|
||||
'root' => '',
|
||||
];
|
||||
|
||||
case 'doSpaces':
|
||||
$diskData = [
|
||||
'key' => '',
|
||||
@@ -160,6 +170,10 @@ class DiskController extends Controller
|
||||
'name' => 'Amazon S3',
|
||||
'value' => 's3',
|
||||
],
|
||||
[
|
||||
'name' => 'S3 Compatible Storage',
|
||||
'value' => 's3compat',
|
||||
],
|
||||
[
|
||||
'name' => 'Digital Ocean Spaces',
|
||||
'value' => 'doSpaces',
|
||||
|
||||
@@ -11,11 +11,13 @@ class FinishController extends Controller
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function __invoke(Request $request)
|
||||
{
|
||||
InstallUtils::createDbMarker();
|
||||
if (! InstallUtils::createDbMarker()) {
|
||||
\Log::error('Install: Unable to create db marker.');
|
||||
}
|
||||
|
||||
return response()->json(['success' => true]);
|
||||
}
|
||||
|
||||
@@ -17,10 +17,6 @@ class InstallationMiddleware
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
if (! InstallUtils::isDbCreated() || Setting::getSetting('profile_complete') !== 'COMPLETED') {
|
||||
if (InstallUtils::dbMarkerExists()) {
|
||||
InstallUtils::deleteDbMarker();
|
||||
}
|
||||
|
||||
return redirect('/installation');
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,8 @@ class CompaniesRequest extends FormRequest
|
||||
return collect($this->validated())
|
||||
->only([
|
||||
'name',
|
||||
'vat_id',
|
||||
'tax_id',
|
||||
])
|
||||
->merge([
|
||||
'owner_id' => $this->user()->id,
|
||||
|
||||
@@ -29,6 +29,12 @@ class CompanyRequest extends FormRequest
|
||||
'required',
|
||||
Rule::unique('companies')->ignore($this->header('company'), 'id'),
|
||||
],
|
||||
'vat_id' => [
|
||||
'nullable',
|
||||
],
|
||||
'tax_id' => [
|
||||
'nullable',
|
||||
],
|
||||
'slug' => [
|
||||
'nullable',
|
||||
],
|
||||
@@ -44,6 +50,8 @@ class CompanyRequest extends FormRequest
|
||||
->only([
|
||||
'name',
|
||||
'slug',
|
||||
'vat_id',
|
||||
'tax_id',
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
@@ -45,15 +45,21 @@ class EstimatesRequest extends FormRequest
|
||||
'nullable',
|
||||
],
|
||||
'discount' => [
|
||||
'numeric',
|
||||
'required',
|
||||
],
|
||||
'discount_val' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'sub_total' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'total' => [
|
||||
'integer',
|
||||
'numeric',
|
||||
'max:99999999',
|
||||
'required',
|
||||
],
|
||||
'tax' => [
|
||||
@@ -77,9 +83,11 @@ class EstimatesRequest extends FormRequest
|
||||
'required',
|
||||
],
|
||||
'items.*.quantity' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'items.*.price' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -45,15 +45,21 @@ class InvoicesRequest extends FormRequest
|
||||
'nullable',
|
||||
],
|
||||
'discount' => [
|
||||
'numeric',
|
||||
'required',
|
||||
],
|
||||
'discount_val' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'sub_total' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'total' => [
|
||||
'integer',
|
||||
'numeric',
|
||||
'max:99999999',
|
||||
'required',
|
||||
],
|
||||
'tax' => [
|
||||
@@ -77,9 +83,11 @@ class InvoicesRequest extends FormRequest
|
||||
'required',
|
||||
],
|
||||
'items.*.quantity' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'items.*.price' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
];
|
||||
|
||||
@@ -43,15 +43,21 @@ class RecurringInvoiceRequest extends FormRequest
|
||||
'nullable',
|
||||
],
|
||||
'discount' => [
|
||||
'numeric',
|
||||
'required',
|
||||
],
|
||||
'discount_val' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'sub_total' => [
|
||||
'integer',
|
||||
'required',
|
||||
],
|
||||
'total' => [
|
||||
'integer',
|
||||
'numeric',
|
||||
'max:99999999',
|
||||
'required',
|
||||
],
|
||||
'tax' => [
|
||||
|
||||
@@ -17,6 +17,8 @@ class CompanyResource extends JsonResource
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'name' => $this->name,
|
||||
'vat_id' => $this->vat_id,
|
||||
'tax_id' => $this->tax_id,
|
||||
'logo' => $this->logo,
|
||||
'logo_path' => $this->logo_path,
|
||||
'unique_hash' => $this->unique_hash,
|
||||
|
||||
@@ -44,6 +44,9 @@ class CreateBackupJob implements ShouldQueue
|
||||
config(['backup.backup.destination.disks' => [$prefix.$fileDisk->driver]]);
|
||||
|
||||
$backupJob = BackupJobFactory::createFromArray(config('backup'));
|
||||
if (! defined('SIGINT')) {
|
||||
$backupJob->disableSignals();
|
||||
}
|
||||
|
||||
if ($this->data['option'] === 'only-db') {
|
||||
$backupJob->dontBackupFilesystem();
|
||||
|
||||
@@ -401,7 +401,10 @@ class Invoice extends Model implements HasMedia
|
||||
$data['base_due_amount'] = $data['due_amount'] * $data['exchange_rate'];
|
||||
$data['customer_sequence_number'] = $serial->nextCustomerSequenceNumber;
|
||||
|
||||
$this->changeInvoiceStatus($data['due_amount']);
|
||||
$statusData = $this->getInvoiceStatusByAmount($data['due_amount']);
|
||||
if (! empty($statusData)) {
|
||||
$data = array_merge($data, $statusData);
|
||||
}
|
||||
|
||||
$this->update($data);
|
||||
|
||||
@@ -691,27 +694,52 @@ class Invoice extends Model implements HasMedia
|
||||
$this->changeInvoiceStatus($this->due_amount);
|
||||
}
|
||||
|
||||
public function changeInvoiceStatus($amount)
|
||||
/**
|
||||
* Set the invoice status from amount.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getInvoiceStatusByAmount($amount)
|
||||
{
|
||||
if ($amount < 0) {
|
||||
return [
|
||||
'error' => 'invalid_amount',
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($amount == 0) {
|
||||
$this->status = Invoice::STATUS_COMPLETED;
|
||||
$this->paid_status = Invoice::STATUS_PAID;
|
||||
$this->overdue = false;
|
||||
$data = [
|
||||
'status' => Invoice::STATUS_COMPLETED,
|
||||
'paid_status' => Invoice::STATUS_PAID,
|
||||
'overdue' => false,
|
||||
];
|
||||
} elseif ($amount == $this->total) {
|
||||
$this->status = $this->getPreviousStatus();
|
||||
$this->paid_status = Invoice::STATUS_UNPAID;
|
||||
$data = [
|
||||
'status' => $this->getPreviousStatus(),
|
||||
'paid_status' => Invoice::STATUS_UNPAID,
|
||||
];
|
||||
} else {
|
||||
$this->status = $this->getPreviousStatus();
|
||||
$this->paid_status = Invoice::STATUS_PARTIALLY_PAID;
|
||||
$data = [
|
||||
'status' => $this->getPreviousStatus(),
|
||||
'paid_status' => Invoice::STATUS_PARTIALLY_PAID,
|
||||
];
|
||||
}
|
||||
|
||||
$this->save();
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the invoice status right away
|
||||
*
|
||||
* @return string[]|void
|
||||
*/
|
||||
public function changeInvoiceStatus($amount)
|
||||
{
|
||||
$status = $this->getInvoiceStatusByAmount($amount);
|
||||
if (! empty($status)) {
|
||||
foreach ($status as $key => $value) {
|
||||
$this->setAttribute($key, $value);
|
||||
}
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
public static function deleteInvoices($ids)
|
||||
|
||||
@@ -4,7 +4,9 @@ namespace InvoiceShelf\Providers;
|
||||
|
||||
use Illuminate\Pagination\Paginator;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
use InvoiceShelf\Bouncer\Scopes\DefaultScope;
|
||||
use InvoiceShelf\Space\InstallUtils;
|
||||
use Silber\Bouncer\Database\Models as BouncerModels;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
@@ -16,7 +18,6 @@ class AppServiceProvider extends ServiceProvider
|
||||
public function boot()
|
||||
{
|
||||
Paginator::useBootstrapThree();
|
||||
$this->loadJsonTranslationsFrom(resource_path('scripts/locales'));
|
||||
|
||||
if (InstallUtils::isDbCreated()) {
|
||||
$this->addMenus();
|
||||
@@ -30,7 +31,7 @@ class AppServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
BouncerModels::scope(new DefaultScope);
|
||||
}
|
||||
|
||||
public function addMenus()
|
||||
|
||||
@@ -32,11 +32,18 @@ class Base64Mime implements Rule
|
||||
$this->attribute = $attribute;
|
||||
|
||||
try {
|
||||
$data = json_decode($value)->data;
|
||||
$decoded = json_decode(trim($value));
|
||||
$name = ! empty($decoded->name) ? $decoded->name : '';
|
||||
$data = ! empty($decoded->data) ? $decoded->data : '';
|
||||
} catch (\Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$extension = pathinfo($name, PATHINFO_EXTENSION);
|
||||
if (! in_array($extension, $this->extensions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pattern = '/^data:\w+\/[\w\+]+;base64,[\w\+\=\/]+$/';
|
||||
|
||||
if (! preg_match($pattern, $data)) {
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace InvoiceShelf\Space;
|
||||
|
||||
use Exception;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvoiceShelf\Http\Requests\DatabaseEnvironmentRequest;
|
||||
use InvoiceShelf\Http\Requests\DiskEnvironmentRequest;
|
||||
@@ -17,6 +16,11 @@ class EnvironmentManager
|
||||
*/
|
||||
private $envPath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $delimiter = "\n";
|
||||
|
||||
/**
|
||||
* Set the .env and .env.example paths.
|
||||
*/
|
||||
@@ -25,6 +29,73 @@ class EnvironmentManager
|
||||
$this->envPath = base_path('.env');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the .env contents
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
private function getEnvContents()
|
||||
{
|
||||
return file_get_contents($this->envPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates .env file - inspired by Akaunting
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function updateEnv(array $data)
|
||||
{
|
||||
if (empty($data) || ! is_array($data) || ! is_file($this->envPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$env = $this->getEnvContents();
|
||||
|
||||
$env = explode($this->delimiter, $env);
|
||||
|
||||
foreach ($data as $data_key => $data_value) {
|
||||
$updated = false;
|
||||
|
||||
foreach ($env as $env_key => $env_value) {
|
||||
$entry = explode('=', $env_value, 2);
|
||||
|
||||
// Check if new or old key
|
||||
if ($entry[0] == $data_key) {
|
||||
$env[$env_key] = $data_key.'='.$this->encode($data_value);
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Lets create if not available
|
||||
if (! $updated) {
|
||||
$env[] = $data_key.'='.$this->encode($data_value);
|
||||
}
|
||||
}
|
||||
|
||||
$env = implode($this->delimiter, $env);
|
||||
|
||||
file_put_contents(base_path('.env'), $env);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes value for .env
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
private function encode($str)
|
||||
{
|
||||
|
||||
if ((strpos($str, ' ') !== false || preg_match('/'.preg_quote('^\'£$%^&*()}{@#~?><,@|-=-_+-¬', '/').'/', $str)) && ($str[0] != '"' || $str[strlen($str) - 1] != '"')) {
|
||||
$str = '"'.$str.'"';
|
||||
}
|
||||
|
||||
return $str;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the database content to the .env file.
|
||||
*
|
||||
@@ -32,59 +103,27 @@ class EnvironmentManager
|
||||
*/
|
||||
public function saveDatabaseVariables(DatabaseEnvironmentRequest $request)
|
||||
{
|
||||
$oldDatabaseData =
|
||||
'DB_CONNECTION='.config('database.default')."\n";
|
||||
|
||||
$newDatabaseData =
|
||||
'DB_CONNECTION='.$request->database_connection."\n";
|
||||
$dbEnv = [
|
||||
'APP_URL' => $request->get('app_url'),
|
||||
'DB_CONNECTION' => $request->get('database_connection'),
|
||||
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
|
||||
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
|
||||
];
|
||||
|
||||
if ($request->has('database_username') && $request->has('database_password')) {
|
||||
if (env('DB_USERNAME') && env('DB_HOST')) {
|
||||
$oldDatabaseData = $oldDatabaseData.
|
||||
'DB_HOST='.config('database.connections.'.config('database.default').'.host')."\n".
|
||||
'DB_PORT='.config('database.connections.'.config('database.default').'.port')."\n".
|
||||
'DB_DATABASE='.config('database.connections.'.config('database.default').'.database')."\n".
|
||||
'DB_USERNAME='.config('database.connections.'.config('database.default').'.username')."\n".
|
||||
'DB_PASSWORD="'.config('database.connections.'.config('database.default').'.password')."\"\n\n";
|
||||
} else {
|
||||
$oldDatabaseData = $oldDatabaseData.
|
||||
'DB_DATABASE='.config('database.connections.'.config('database.default').'.database')."\n\n";
|
||||
}
|
||||
$dbEnv['DB_HOST'] = $request->get('database_hostname');
|
||||
$dbEnv['DB_PORT'] = $request->get('database_port');
|
||||
$dbEnv['DB_DATABASE'] = $request->get('database_name');
|
||||
$dbEnv['DB_USERNAME'] = $request->get('database_username');
|
||||
$dbEnv['DB_PASSWORD'] = $request->get('database_password');
|
||||
|
||||
$newDatabaseData = $newDatabaseData.
|
||||
'DB_HOST='.$request->database_hostname."\n".
|
||||
'DB_PORT='.$request->database_port."\n".
|
||||
'DB_DATABASE='.$request->database_name."\n".
|
||||
'DB_USERNAME='.$request->database_username."\n".
|
||||
'DB_PASSWORD="'.$request->database_password."\"\n\n";
|
||||
} else {
|
||||
if (env('DB_USERNAME') && env('DB_HOST')) {
|
||||
$oldDatabaseData = $oldDatabaseData.
|
||||
'DB_HOST='.config('database.connections.'.config('database.default').'.host')."\n".
|
||||
'DB_PORT='.config('database.connections.'.config('database.default').'.port')."\n".
|
||||
'DB_DATABASE='.config('database.connections.'.config('database.default').'.database')."\n".
|
||||
'DB_USERNAME='.config('database.connections.'.config('database.default').'.username')."\n".
|
||||
'DB_PASSWORD="'.config('database.connections.'.config('database.default').'.password')."\"\n\n";
|
||||
} else {
|
||||
$oldDatabaseData = $oldDatabaseData.
|
||||
'DB_DATABASE='.config('database.connections.'.config('database.default').'.database')."\n\n";
|
||||
}
|
||||
|
||||
$newDatabaseData = $newDatabaseData.
|
||||
'DB_DATABASE='.$request->database_name."\n\n";
|
||||
$dbEnv['DB_DATABASE'] = $request->get('database_name');
|
||||
}
|
||||
|
||||
try {
|
||||
$conn = $this->checkDatabaseConnection($request);
|
||||
|
||||
// $requirement = $this->checkVersionRequirements($request, $conn);
|
||||
|
||||
// if ($requirement) {
|
||||
// return [
|
||||
// 'error' => 'minimum_version_requirement',
|
||||
// 'requirement' => $requirement,
|
||||
// ];
|
||||
// }
|
||||
$this->checkDatabaseConnection($request);
|
||||
|
||||
if (\Schema::hasTable('users')) {
|
||||
return [
|
||||
@@ -98,29 +137,7 @@ class EnvironmentManager
|
||||
}
|
||||
|
||||
try {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$oldDatabaseData,
|
||||
$newDatabaseData,
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
'APP_URL='.config('app.url'),
|
||||
'APP_URL='.$request->app_url,
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
'SANCTUM_STATEFUL_DOMAINS='.env('SANCTUM_STATEFUL_DOMAINS'),
|
||||
'SANCTUM_STATEFUL_DOMAINS='.$request->app_domain,
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
'SESSION_DOMAIN='.config('session.domain'),
|
||||
'SESSION_DOMAIN='.explode(':', $request->app_domain)[0],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
$this->updateEnv($dbEnv);
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'database_variables_save_error',
|
||||
@@ -133,26 +150,27 @@ class EnvironmentManager
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* Returns PDO object if all ok.
|
||||
*
|
||||
* @return \Closure|\PDO
|
||||
*/
|
||||
private function checkDatabaseConnection(DatabaseEnvironmentRequest $request)
|
||||
{
|
||||
$connection = $request->database_connection;
|
||||
$connection = $request->get('database_connection');
|
||||
|
||||
$settings = config("database.connections.$connection");
|
||||
$settings = config("database.connections.$connection");
|
||||
|
||||
$connectionArray = array_merge($settings, [
|
||||
'driver' => $connection,
|
||||
'database' => $request->database_name,
|
||||
'database' => $request->get('database_name'),
|
||||
]);
|
||||
|
||||
if ($request->has('database_username') && $request->has('database_password')) {
|
||||
$connectionArray = array_merge($connectionArray, [
|
||||
'username' => $request->database_username,
|
||||
'password' => $request->database_password,
|
||||
'host' => $request->database_hostname,
|
||||
'port' => $request->database_port,
|
||||
'username' => $request->get('database_username'),
|
||||
'password' => $request->get('database_password'),
|
||||
'host' => $request->get('database_hostname'),
|
||||
'port' => $request->get('database_port'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -167,86 +185,19 @@ class EnvironmentManager
|
||||
return DB::connection()->getPdo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
private function checkVersionRequirements(DatabaseEnvironmentRequest $request, $conn)
|
||||
{
|
||||
$connection = $request->database_connection;
|
||||
|
||||
$checker = new RequirementsChecker();
|
||||
|
||||
$phpSupportInfo = $checker->checkPHPVersion(
|
||||
config('invoiceshelf.min_php_version')
|
||||
);
|
||||
|
||||
if (! $phpSupportInfo['supported']) {
|
||||
return $phpSupportInfo;
|
||||
}
|
||||
|
||||
$dbSupportInfo = [];
|
||||
|
||||
switch ($connection) {
|
||||
case 'mysql':
|
||||
$dbSupportInfo = $checker->checkMysqlVersion($conn);
|
||||
|
||||
break;
|
||||
|
||||
case 'pgsql':
|
||||
$conn = pg_connect("host={$request->database_hostname} port={$request->database_port} dbname={$request->database_name} user={$request->database_username} password={$request->database_password}");
|
||||
$dbSupportInfo = $checker->checkPgsqlVersion(
|
||||
$conn,
|
||||
config('invoiceshelf.min_pgsql_version')
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case 'sqlite':
|
||||
$dbSupportInfo = $checker->checkSqliteVersion(
|
||||
config('invoiceshelf.min_sqlite_version')
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if (! $dbSupportInfo['supported']) {
|
||||
return $dbSupportInfo;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the mail content to the .env file.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function saveMailVariables(MailEnvironmentRequest $request)
|
||||
{
|
||||
$mailData = $this->getMailData($request);
|
||||
$mailEnv = $this->getMailConfiguration($request);
|
||||
|
||||
try {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$mailData['old_mail_data'],
|
||||
$mailData['new_mail_data'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
if ($mailData['extra_old_mail_data']) {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$mailData['extra_old_mail_data'],
|
||||
$mailData['extra_mail_data'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
} else {
|
||||
file_put_contents(
|
||||
$this->envPath,
|
||||
"\n".$mailData['extra_mail_data'],
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
$this->updateEnv($mailEnv);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'mail_variables_save_error',
|
||||
@@ -258,193 +209,103 @@ class EnvironmentManager
|
||||
];
|
||||
}
|
||||
|
||||
private function getMailData($request)
|
||||
/**
|
||||
* Returns the mail configuration
|
||||
*
|
||||
* @param MailEnvironmentRequest $request
|
||||
* @return array
|
||||
*/
|
||||
private function getMailConfiguration($request)
|
||||
{
|
||||
$mailFromCredential = '';
|
||||
$extraMailData = '';
|
||||
$extraOldMailData = '';
|
||||
$oldMailData = '';
|
||||
$newMailData = '';
|
||||
$mailEnv = [];
|
||||
|
||||
if (env('MAIL_FROM_ADDRESS') !== null && env('MAIL_FROM_NAME') !== null) {
|
||||
$mailFromCredential =
|
||||
'MAIL_FROM_ADDRESS='.config('mail.from.address')."\n".
|
||||
'MAIL_FROM_NAME="'.config('mail.from.name')."\"\n\n";
|
||||
}
|
||||
$driver = $request->get('mail_driver');
|
||||
|
||||
switch ($request->mail_driver) {
|
||||
switch ($driver) {
|
||||
case 'smtp':
|
||||
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.$request->mail_username."\n".
|
||||
'MAIL_PASSWORD='.$request->mail_password."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
$mailEnv = [
|
||||
'MAIL_DRIVER' => $request->get('mail_driver'),
|
||||
'MAIL_HOST' => $request->get('mail_host'),
|
||||
'MAIL_PORT' => $request->get('mail_port'),
|
||||
'MAIL_USERNAME' => $request->get('mail_username'),
|
||||
'MAIL_PASSWORD' => $request->get('mail_password'),
|
||||
'MAIL_ENCRYPTION' => $request->get('mail_encryption'),
|
||||
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
|
||||
'MAIL_FROM_NAME' => $request->get('from_name'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'mailgun':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
$extraMailData =
|
||||
'MAILGUN_DOMAIN='.$request->mail_mailgun_domain."\n".
|
||||
'MAILGUN_SECRET='.$request->mail_mailgun_secret."\n".
|
||||
'MAILGUN_ENDPOINT='.$request->mail_mailgun_endpoint."\n";
|
||||
|
||||
if (env('MAILGUN_DOMAIN') !== null && env('MAILGUN_SECRET') !== null && env('MAILGUN_ENDPOINT') !== null) {
|
||||
$extraOldMailData =
|
||||
'MAILGUN_DOMAIN='.config('services.mailgun.domain')."\n".
|
||||
'MAILGUN_SECRET='.config('services.mailgun.secret')."\n".
|
||||
'MAILGUN_ENDPOINT='.config('services.mailgun.endpoint')."\n";
|
||||
}
|
||||
$mailEnv = [
|
||||
'MAIL_DRIVER' => $request->get('mail_driver'),
|
||||
'MAIL_HOST' => $request->get('mail_host'),
|
||||
'MAIL_PORT' => $request->get('mail_port'),
|
||||
'MAIL_USERNAME' => config('mail.username'),
|
||||
'MAIL_PASSWORD' => config('mail.password'),
|
||||
'MAIL_ENCRYPTION' => $request->get('mail_encryption'),
|
||||
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
|
||||
'MAIL_FROM_NAME' => $request->get('from_name'),
|
||||
'MAILGUN_DOMAIN' => $request->get('mail_mailgun_domain'),
|
||||
'MAILGUN_SECRET' => $request->get('mail_mailgun_secret'),
|
||||
'MAILGUN_ENDPOINT' => $request->get('mail_mailgun_endpoint'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'ses':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.$request->mail_host."\n".
|
||||
'MAIL_PORT='.$request->mail_port."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.$request->mail_encryption."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
|
||||
$extraMailData =
|
||||
'SES_KEY='.$request->mail_ses_key."\n".
|
||||
'SES_SECRET='.$request->mail_ses_secret."\n";
|
||||
|
||||
if (env('SES_KEY') !== null && env('SES_SECRET') !== null) {
|
||||
$extraOldMailData =
|
||||
'SES_KEY='.config('services.ses.key')."\n".
|
||||
'SES_SECRET='.config('services.ses.secret')."\n";
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 'mail':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
$mailEnv = [
|
||||
'MAIL_DRIVER' => $request->get('mail_driver'),
|
||||
'MAIL_HOST' => $request->get('mail_host'),
|
||||
'MAIL_PORT' => $request->get('mail_port'),
|
||||
'MAIL_USERNAME' => config('mail.username'),
|
||||
'MAIL_PASSWORD' => config('mail.password'),
|
||||
'MAIL_ENCRYPTION' => $request->get('mail_encryption'),
|
||||
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
|
||||
'MAIL_FROM_NAME' => $request->get('from_name'),
|
||||
'SES_KEY' => $request->get('mail_ses_key'),
|
||||
'SES_SECRET' => $request->get('mail_ses_secret'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'sendmail':
|
||||
$oldMailData =
|
||||
'MAIL_DRIVER='.config('mail.driver')."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
$mailFromCredential;
|
||||
case 'mail':
|
||||
|
||||
$newMailData =
|
||||
'MAIL_DRIVER='.$request->mail_driver."\n".
|
||||
'MAIL_HOST='.config('mail.host')."\n".
|
||||
'MAIL_PORT='.config('mail.port')."\n".
|
||||
'MAIL_USERNAME='.config('mail.username')."\n".
|
||||
'MAIL_PASSWORD='.config('mail.password')."\n".
|
||||
'MAIL_ENCRYPTION='.config('mail.encryption')."\n\n".
|
||||
'MAIL_FROM_ADDRESS='.$request->from_mail."\n".
|
||||
'MAIL_FROM_NAME="'.$request->from_name."\"\n\n";
|
||||
$mailEnv = [
|
||||
'MAIL_DRIVER' => $request->get('mail_driver'),
|
||||
'MAIL_HOST' => config('mail.host'),
|
||||
'MAIL_PORT' => config('mail.port'),
|
||||
'MAIL_USERNAME' => config('mail.username'),
|
||||
'MAIL_PASSWORD' => config('mail.password'),
|
||||
'MAIL_ENCRYPTION' => config('mail.encryption'),
|
||||
'MAIL_FROM_ADDRESS' => $request->get('from_mail'),
|
||||
'MAIL_FROM_NAME' => $request->get('from_name'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return [
|
||||
'old_mail_data' => $oldMailData,
|
||||
'new_mail_data' => $newMailData,
|
||||
'extra_mail_data' => $extraMailData,
|
||||
'extra_old_mail_data' => $extraOldMailData,
|
||||
];
|
||||
return $mailEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the disk content to the .env file.
|
||||
*
|
||||
* @param Request $request
|
||||
* @return array
|
||||
*/
|
||||
public function saveDiskVariables(DiskEnvironmentRequest $request)
|
||||
{
|
||||
$diskData = $this->getDiskData($request);
|
||||
$diskEnv = $this->getDiskConfiguration($request);
|
||||
|
||||
try {
|
||||
if (! $diskData['old_default_driver']) {
|
||||
file_put_contents($this->envPath, $diskData['default_driver'], FILE_APPEND);
|
||||
} else {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$diskData['old_default_driver'],
|
||||
$diskData['default_driver'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
}
|
||||
|
||||
if (! $diskData['old_disk_data']) {
|
||||
file_put_contents($this->envPath, $diskData['new_disk_data'], FILE_APPEND);
|
||||
} else {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
$diskData['old_disk_data'],
|
||||
$diskData['new_disk_data'],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
}
|
||||
$this->updateEnv($diskEnv);
|
||||
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'disk_variables_save_error',
|
||||
@@ -456,113 +317,75 @@ class EnvironmentManager
|
||||
];
|
||||
}
|
||||
|
||||
private function getDiskData($request)
|
||||
/**
|
||||
* Returns the disk configuration
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getDiskConfiguration(DiskEnvironmentRequest $request)
|
||||
{
|
||||
$oldDefaultDriver = '';
|
||||
$defaultDriver = '';
|
||||
$oldDiskData = '';
|
||||
$newDiskData = '';
|
||||
$diskEnv = [];
|
||||
|
||||
if ($request->default_driver) {
|
||||
if (env('FILESYSTEM_DRIVER') !== null) {
|
||||
$defaultDriver = "\n".'FILESYSTEM_DRIVER='.$request->default_driver."\n";
|
||||
$driver = $request->get('app_domain');
|
||||
|
||||
$oldDefaultDriver =
|
||||
"\n".'FILESYSTEM_DRIVER='.config('filesystems.default')."\n";
|
||||
} else {
|
||||
$defaultDriver =
|
||||
"\n".'FILESYSTEM_DRIVER='.$request->default_driver."\n";
|
||||
}
|
||||
if ($driver) {
|
||||
$diskEnv['FILESYSTEM_DRIVER'] = $driver;
|
||||
}
|
||||
|
||||
switch ($request->selected_driver) {
|
||||
switch ($request->get('selected_driver')) {
|
||||
case 's3':
|
||||
if (env('AWS_KEY') !== null) {
|
||||
$oldDiskData = "\n".
|
||||
'AWS_KEY='.config('filesystems.disks.s3.key')."\n".
|
||||
'AWS_SECRET="'.config('filesystems.disks.s3.secret')."\"\n".
|
||||
'AWS_REGION='.config('filesystems.disks.s3.region')."\n".
|
||||
'AWS_BUCKET='.config('filesystems.disks.s3.bucket')."\n".
|
||||
'AWS_ROOT='.config('filesystems.disks.s3.root')."\n";
|
||||
}
|
||||
|
||||
$newDiskData = "\n".
|
||||
'AWS_KEY='.$request->aws_key."\n".
|
||||
'AWS_SECRET="'.$request->aws_secret."\"\n".
|
||||
'AWS_REGION='.$request->aws_region."\n".
|
||||
'AWS_BUCKET='.$request->aws_bucket."\n".
|
||||
'AWS_ROOT='.$request->aws_root."\n";
|
||||
$diskEnv = [
|
||||
'AWS_KEY' => $request->get('aws_key'),
|
||||
'AWS_SECRET' => $request->get('aws_secret'),
|
||||
'AWS_REGION' => $request->get('aws_region'),
|
||||
'AWS_BUCKET' => $request->get('aws_bucket'),
|
||||
'AWS_ROOT' => $request->get('aws_root'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'doSpaces':
|
||||
if (env('DO_SPACES_KEY') !== null) {
|
||||
$oldDiskData = "\n".
|
||||
'DO_SPACES_KEY='.config('filesystems.disks.doSpaces.key')."\n".
|
||||
'DO_SPACES_SECRET="'.config('filesystems.disks.doSpaces.secret')."\"\n".
|
||||
'DO_SPACES_REGION='.config('filesystems.disks.doSpaces.region')."\n".
|
||||
'DO_SPACES_BUCKET='.config('filesystems.disks.doSpaces.bucket')."\n".
|
||||
'DO_SPACES_ENDPOINT='.config('filesystems.disks.doSpaces.endpoint')."\n";
|
||||
'DO_SPACES_ROOT='.config('filesystems.disks.doSpaces.root')."\n";
|
||||
}
|
||||
|
||||
$newDiskData = "\n".
|
||||
'DO_SPACES_KEY='.$request->do_spaces_key."\n".
|
||||
'DO_SPACES_SECRET="'.$request->do_spaces_secret."\"\n".
|
||||
'DO_SPACES_REGION='.$request->do_spaces_region."\n".
|
||||
'DO_SPACES_BUCKET='.$request->do_spaces_bucket."\n".
|
||||
'DO_SPACES_ENDPOINT='.$request->do_spaces_endpoint."\n";
|
||||
'DO_SPACES_ROOT='.$request->do_spaces_root."\n\n";
|
||||
$diskEnv = [
|
||||
'DO_SPACES_KEY' => $request->get('do_spaces_key'),
|
||||
'DO_SPACES_SECRET' => $request->get('do_spaces_secret'),
|
||||
'DO_SPACES_REGION' => $request->get('do_spaces_region'),
|
||||
'DO_SPACES_BUCKET' => $request->get('do_spaces_bucket'),
|
||||
'DO_SPACES_ENDPOINT' => $request->get('do_spaces_endpoint'),
|
||||
'DO_SPACES_ROOT' => $request->get('do_spaces_root'),
|
||||
];
|
||||
|
||||
break;
|
||||
|
||||
case 'dropbox':
|
||||
if (env('DROPBOX_TOKEN') !== null) {
|
||||
$oldDiskData = "\n".
|
||||
'DROPBOX_TOKEN='.config('filesystems.disks.dropbox.token')."\n".
|
||||
'DROPBOX_KEY='.config('filesystems.disks.dropbox.key')."\n".
|
||||
'DROPBOX_SECRET="'.config('filesystems.disks.dropbox.secret')."\"\n".
|
||||
'DROPBOX_APP='.config('filesystems.disks.dropbox.app')."\n".
|
||||
'DROPBOX_ROOT='.config('filesystems.disks.dropbox.root')."\n";
|
||||
}
|
||||
|
||||
$newDiskData = "\n".
|
||||
'DROPBOX_TOKEN='.$request->dropbox_token."\n".
|
||||
'DROPBOX_KEY='.$request->dropbox_key."\n".
|
||||
'DROPBOX_SECRET="'.$request->dropbox_secret."\"\n".
|
||||
'DROPBOX_APP='.$request->dropbox_app."\n".
|
||||
'DROPBOX_ROOT='.$request->dropbox_root."\n";
|
||||
$diskEnv = [
|
||||
'DROPBOX_TOKEN' => $request->get('dropbox_token'),
|
||||
'DROPBOX_KEY' => $request->get('dropbox_key'),
|
||||
'DROPBOX_SECRET' => $request->get('dropbox_secret'),
|
||||
'DROPBOX_APP' => $request->get('dropbox_app'),
|
||||
'DROPBOX_ROOT' => $request->get('dropbox_root'),
|
||||
];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return [
|
||||
'old_disk_data' => $oldDiskData,
|
||||
'new_disk_data' => $newDiskData,
|
||||
'default_driver' => $defaultDriver,
|
||||
'old_default_driver' => $oldDefaultDriver,
|
||||
];
|
||||
return $diskEnv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save sanctum statful domain to the .env file.
|
||||
* Save sanctum stateful domain to the .env file.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function saveDomainVariables(DomainEnvironmentRequest $request)
|
||||
{
|
||||
try {
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
'SANCTUM_STATEFUL_DOMAINS='.env('SANCTUM_STATEFUL_DOMAINS'),
|
||||
'SANCTUM_STATEFUL_DOMAINS='.$request->app_domain,
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
|
||||
file_put_contents($this->envPath, str_replace(
|
||||
'SESSION_DOMAIN='.config('session.domain'),
|
||||
'SESSION_DOMAIN='.explode(':', $request->app_domain)[0],
|
||||
file_get_contents($this->envPath)
|
||||
));
|
||||
$this->updateEnv([
|
||||
'SANCTUM_STATEFUL_DOMAINS' => $request->get('app_domain'),
|
||||
'SESSION_DOMAIN' => explode(':', $request->get('app_domain'))[0],
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
return [
|
||||
'error' => 'domain_verification_failed',
|
||||
@@ -573,4 +396,34 @@ class EnvironmentManager
|
||||
'success' => 'domain_variable_save_successfully',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Order the env contents
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function reoderEnv()
|
||||
{
|
||||
$contents = $this->getEnvContents();
|
||||
$contents = explode($this->delimiter, $contents);
|
||||
if (empty($contents)) {
|
||||
return;
|
||||
}
|
||||
natsort($contents);
|
||||
|
||||
$formatted = '';
|
||||
$previous = '';
|
||||
foreach ($contents as $current) {
|
||||
$parts_line = explode('_', $current);
|
||||
$parts_last = explode('_', $previous);
|
||||
if ($parts_line[0] != $parts_last[0]) {
|
||||
$formatted .= $this->delimiter;
|
||||
}
|
||||
$formatted .= $current.$this->delimiter;
|
||||
$previous = $current;
|
||||
|
||||
}
|
||||
|
||||
file_put_contents($this->envPath, trim($formatted));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace InvoiceShelf\Space;
|
||||
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use League\Flysystem\FilesystemException;
|
||||
|
||||
class InstallUtils
|
||||
@@ -9,11 +11,11 @@ class InstallUtils
|
||||
/**
|
||||
* Check if database is created
|
||||
*
|
||||
* @return bool|int|string
|
||||
* @return bool
|
||||
*/
|
||||
public static function isDbCreated()
|
||||
{
|
||||
return self::tableExists('users');
|
||||
return self::dbMarkerExists() && self::tableExists('users');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -30,19 +32,14 @@ class InstallUtils
|
||||
}
|
||||
|
||||
try {
|
||||
$flag = self::dbMarkerExists();
|
||||
if ($flag) {
|
||||
$flag &= \Schema::hasTable($table);
|
||||
}
|
||||
} catch (\Illuminate\Database\QueryException $e) {
|
||||
$flag = false;
|
||||
} catch (\Exception $e) {
|
||||
$flag = \Schema::hasTable($table);
|
||||
} catch (QueryException|\Exception $e) {
|
||||
$flag = false;
|
||||
}
|
||||
|
||||
$cache[$table] = $flag;
|
||||
|
||||
return $flag;
|
||||
return $cache[$table];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,34 +50,43 @@ class InstallUtils
|
||||
public static function dbMarkerExists()
|
||||
{
|
||||
try {
|
||||
$flag = \Storage::disk('local')->has('database_created');
|
||||
return \Storage::disk('local')->has('database_created');
|
||||
} catch (FilesystemException $e) {
|
||||
$flag = false;
|
||||
Log::error('Unable to verify db marker: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return $flag;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the database marker
|
||||
*
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
public static function createDbMarker()
|
||||
{
|
||||
\Storage::disk('local')->put('database_created', 'database_created');
|
||||
try {
|
||||
return \Storage::disk('local')->put('database_created', time());
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Unable to create db marker: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the database marker
|
||||
*
|
||||
* @return void
|
||||
* @return bool
|
||||
*/
|
||||
public static function deleteDbMarker()
|
||||
{
|
||||
try {
|
||||
\Storage::disk('local')->delete('database_created');
|
||||
return \Storage::disk('local')->delete('database_created');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Unable to delete db marker: '.$e->getMessage());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ trait GeneratesMenuTrait
|
||||
{
|
||||
public function generateMenu($key, $user)
|
||||
{
|
||||
$menu = [];
|
||||
$new_items = [];
|
||||
|
||||
foreach (\Menu::get($key)->items->toArray() as $data) {
|
||||
$menu = \Menu::get($key);
|
||||
$items = $menu ? $menu->items->toArray() : [];
|
||||
|
||||
foreach ($items as $data) {
|
||||
if ($user->checkAccess($data)) {
|
||||
$menu[] = [
|
||||
$new_items[] = [
|
||||
'title' => $data->title,
|
||||
'link' => $data->link->path['url'],
|
||||
'icon' => $data->data['icon'],
|
||||
@@ -20,6 +23,6 @@ trait GeneratesMenuTrait
|
||||
}
|
||||
}
|
||||
|
||||
return $menu;
|
||||
return $new_items;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,23 @@
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"laravel/framework": "^10.10",
|
||||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/helpers": "^1.5",
|
||||
"laravel/ui": "^4.4",
|
||||
"ext-json": "*",
|
||||
"aws/aws-sdk-php": "^3.297",
|
||||
"barryvdh/laravel-dompdf": "^v2.0",
|
||||
"invoiceshelf/modules": "^1.0.0",
|
||||
"doctrine/dbal": "^3.8",
|
||||
"dragonmantank/cron-expression": "^3.3",
|
||||
"guzzlehttp/guzzle": "^7.2",
|
||||
"invoiceshelf/modules": "^1.0.0",
|
||||
"jasonmccreary/laravel-test-assertions": "^2.3",
|
||||
"laravel/framework": "^10.10",
|
||||
"laravel/helpers": "^1.5",
|
||||
"laravel/sanctum": "^3.3",
|
||||
"laravel/tinker": "^2.8",
|
||||
"laravel/ui": "^4.4",
|
||||
"lavary/laravel-menu": "^1.8",
|
||||
"league/flysystem-aws-s3-v3": "^3.23",
|
||||
"predis/predis": "^2.2",
|
||||
"silber/bouncer": "^v1.0",
|
||||
"silber/bouncer": "v1.0.1",
|
||||
"spatie/flysystem-dropbox": "^3.0",
|
||||
"spatie/laravel-backup": "^8.5",
|
||||
"spatie/laravel-medialibrary": "^10.15",
|
||||
@@ -38,7 +39,7 @@
|
||||
"nunomaduro/collision": "^7.0",
|
||||
"phpunit/phpunit": "^10.1",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"barryvdh/laravel-ide-helper": "^2.13",
|
||||
"barryvdh/laravel-ide-helper": "v3.0.0",
|
||||
"beyondcode/laravel-dump-server": "^1.9",
|
||||
"pestphp/pest": "^2.33",
|
||||
"pestphp/pest-plugin-faker": "^2.0",
|
||||
|
||||
2237
composer.lock
generated
2237
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -64,6 +64,16 @@ return [
|
||||
'root' => env('AWS_ROOT'),
|
||||
],
|
||||
|
||||
's3compat' => [
|
||||
'driver' => 's3',
|
||||
'endpoint' => env('S3_COMPAT_ENDPOINT'),
|
||||
'use_path_style_endpoint' => true,
|
||||
'key' => env('S3_COMPAT_KEY'),
|
||||
'secret' => env('S3_COMPAT_SECRET'),
|
||||
'region' => env('S3_COMPAT_REGION'),
|
||||
'bucket' => env('S3_COMPAT_BUCKET'),
|
||||
],
|
||||
|
||||
'media' => [
|
||||
'driver' => 'local',
|
||||
'root' => public_path('media'),
|
||||
|
||||
@@ -39,8 +39,8 @@ return [
|
||||
'resources/sass/module' => 'Resources/sass/module.scss',
|
||||
'resources/scripts/stores/sample-store' => 'Resources/scripts/stores/sample-store.js',
|
||||
'resources/scripts/views/SamplePage' => 'Resources/scripts/views/SamplePage.vue',
|
||||
'resources/locales/en' => 'Resources/locales/en.json',
|
||||
'resources/locales/locales' => 'Resources/locales/locales.js',
|
||||
'lang/en' => 'Lang/en.json',
|
||||
'lang/locales' => 'Lang/locales.js',
|
||||
'package' => 'package.json',
|
||||
'postcss.config' => 'postcss.config.js',
|
||||
'tailwind.config' => 'tailwind.config.js',
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
files:
|
||||
- source: /resources/scripts/locales/en.json
|
||||
translation: /resources/scripts/locales/%two_letters_code%.json
|
||||
- source: /lang/en.json
|
||||
translation: /lang/%two_letters_code%.json
|
||||
|
||||
@@ -24,8 +24,5 @@ class AddUserIdToExpensesTable extends Migration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
|
||||
}
|
||||
public function down() {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class TaxesAmountAsSigned extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('taxes', function (Blueprint $table) {
|
||||
$table->bigInteger('amount')->change();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('taxes', function (Blueprint $table) {
|
||||
$table->unsignedBigInteger('amount')->change();
|
||||
});
|
||||
}
|
||||
}
|
||||
39
database/migrations/2024_02_11_075831_update_version_110.php
Normal file
39
database/migrations/2024_02_11_075831_update_version_110.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvoiceShelf\Models\Setting;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
|
||||
try {
|
||||
DB::update("UPDATE abilities SET entity_type = REPLACE(entity_type, 'Crater', 'InvoiceShelf')");
|
||||
DB::update("UPDATE assigned_roles SET entity_type = REPLACE(entity_type, 'Crater', 'InvoiceShelf')");
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
|
||||
Setting::setSetting('version', '1.1.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
try {
|
||||
DB::update("UPDATE abilities SET entity_type = REPLACE(entity_type, 'InvoiceShelf', 'Crater')");
|
||||
DB::update("UPDATE assigned_roles SET entity_type = REPLACE(entity_type, 'InvoiceShelf', 'Crater')");
|
||||
} catch (\Exception $e) {
|
||||
|
||||
}
|
||||
|
||||
Setting::setSetting('version', '1.0.0');
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->dateTime('expires_at')->nullable()->after('last_used_at');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('personal_access_tokens', function (Blueprint $table) {
|
||||
$table->dropColumn('expires_at');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->after('unique_hash', function (Blueprint $table) {
|
||||
$table->string('vat_id')->nullable();
|
||||
$table->string('tax_id')->nullable();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('companies', function (Blueprint $table) {
|
||||
$table->dropColumn('vat_id');
|
||||
$table->dropColumn('tax_id');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
try {
|
||||
DB::update("UPDATE media SET model_type = REPLACE(model_type, 'Crater', 'InvoiceShelf')");
|
||||
DB::update("UPDATE email_logs SET mailable_type = REPLACE(model_type, 'Crater', 'InvoiceShelf')");
|
||||
DB::update("UPDATE notifications SET notifiable_type = REPLACE(notifiable_type, 'Crater', 'InvoiceShelf')");
|
||||
DB::update("UPDATE personal_access_tokens SET tokenable_type = REPLACE(tokenable_type, 'Crater', 'InvoiceShelf')");
|
||||
DB::update("UPDATE custom_field_values SET custom_field_valuable_type = REPLACE(custom_field_valuable_type, 'Crater', 'InvoiceShelf')");
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
try {
|
||||
DB::update("UPDATE media SET model_type = REPLACE(model_type, 'InvoiceShelf', 'Crater')");
|
||||
DB::update("UPDATE email_logs SET mailable_type = REPLACE(model_type, 'InvoiceShelf', 'Crater')");
|
||||
DB::update("UPDATE notifications SET notifiable_type = REPLACE(notifiable_type, 'InvoiceShelf', 'Crater')");
|
||||
DB::update("UPDATE personal_access_tokens SET tokenable_type = REPLACE(tokenable_type, 'InvoiceShelf', 'Crater')");
|
||||
DB::update("UPDATE custom_field_values SET custom_field_valuable_type = REPLACE(custom_field_valuable_type, 'InvoiceShelf', 'Crater')");
|
||||
} catch (\Exception $e) {
|
||||
}
|
||||
}
|
||||
};
|
||||
23
database/migrations/2024_05_04_110000_update_version_121.php
Normal file
23
database/migrations/2024_05_04_110000_update_version_121.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use InvoiceShelf\Models\Setting;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.2.1');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.2.0');
|
||||
}
|
||||
};
|
||||
23
database/migrations/2024_05_04_110000_update_version_122.php
Normal file
23
database/migrations/2024_05_04_110000_update_version_122.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use InvoiceShelf\Models\Setting;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.2.2');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.2.1');
|
||||
}
|
||||
};
|
||||
23
database/migrations/2024_07_12_235756_update_version_130.php
Normal file
23
database/migrations/2024_07_12_235756_update_version_130.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use InvoiceShelf\Models\Setting;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.3.0');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Setting::setSetting('version', '1.2.2');
|
||||
}
|
||||
};
|
||||
@@ -616,6 +616,14 @@ class CurrenciesTableSeeder extends Seeder
|
||||
'decimal_separator' => ',',
|
||||
'swap_currency_symbol' => true,
|
||||
],
|
||||
[
|
||||
'name' => 'Libyan Dinar',
|
||||
'code' => 'LYD',
|
||||
'symbol' => 'LD',
|
||||
'precision' => '3',
|
||||
'thousand_separator' => ',',
|
||||
'decimal_separator' => '.',
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($currencies as $currency) {
|
||||
|
||||
@@ -863,6 +863,8 @@
|
||||
"company_info": {
|
||||
"company_info": "Firmeninfo",
|
||||
"company_name": "Name des Unternehmens",
|
||||
"vat_id": "Umsatzsteuer-Identifikationsnummer",
|
||||
"tax_id": "Steuernummer",
|
||||
"company_logo": "Firmenlogo",
|
||||
"section_description": "Informationen zu Ihrem Unternehmen, die auf Rechnungen, Angeboten und anderen von InvoiceShelf erstellten Dokumenten angezeigt werden.",
|
||||
"phone": "Telefon",
|
||||
@@ -1267,6 +1269,12 @@
|
||||
"aws_region": "AWS-Region",
|
||||
"aws_bucket": "AWS Bucket",
|
||||
"aws_root": "AWS-Pfad",
|
||||
"s3_endpoint": "S3 Endpunkt",
|
||||
"s3_key": "S3 Schlüssel",
|
||||
"s3_secret": "S3 Geheimnis",
|
||||
"s3_region": "S3 Region",
|
||||
"s3_bucket": "S3 Bucket",
|
||||
"s3_root": "S3 Pfad",
|
||||
"do_spaces_type": "Do Spaces-Typ",
|
||||
"do_spaces_key": "Do Spaces Key",
|
||||
"do_spaces_secret": "Do Spaces Secret",
|
||||
@@ -1522,5 +1530,7 @@
|
||||
"pdf_bill_to": "Rechnungsanschrift",
|
||||
"pdf_ship_to": "Lieferanschrift",
|
||||
"pdf_received_from": "Erhalten von:",
|
||||
"pdf_tax_label": "Steuer"
|
||||
"pdf_tax_label": "Steuer",
|
||||
"pdf_tax_id": "Steuer-Nr.",
|
||||
"pdf_vat_id": "USt.-ID"
|
||||
}
|
||||
@@ -863,6 +863,8 @@
|
||||
"company_info": {
|
||||
"company_info": "Company info",
|
||||
"company_name": "Company Name",
|
||||
"tax_id": "Tax Identification Number",
|
||||
"vat_id": "VAT Identification Number",
|
||||
"company_logo": "Company Logo",
|
||||
"section_description": "Information about your company that will be displayed on invoices, estimates and other documents created by InvoiceShelf.",
|
||||
"phone": "Phone",
|
||||
@@ -1267,6 +1269,12 @@
|
||||
"aws_region": "AWS Region",
|
||||
"aws_bucket": "AWS Bucket",
|
||||
"aws_root": "AWS Root",
|
||||
"s3_endpoint": "S3 Endpoint",
|
||||
"s3_key": "S3 Key",
|
||||
"s3_secret": "S3 Secret",
|
||||
"s3_region": "S3 Region",
|
||||
"s3_bucket": "S3 Bucket",
|
||||
"s3_root": "S3 Root",
|
||||
"do_spaces_type": "Do Spaces type",
|
||||
"do_spaces_key": "Do Spaces key",
|
||||
"do_spaces_secret": "Do Spaces Secret",
|
||||
@@ -1522,5 +1530,7 @@
|
||||
"pdf_bill_to": "Bill to,",
|
||||
"pdf_ship_to": "Ship to,",
|
||||
"pdf_received_from": "Received from:",
|
||||
"pdf_tax_label": "Tax"
|
||||
"pdf_tax_label": "Tax",
|
||||
"pdf_tax_id": "Tax-ID",
|
||||
"pdf_vat_id": "VAT-ID"
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<img src="https://github.com/InvoiceShelf/InvoiceShelf/assets/5760249/70c0ea3a-b497-4d9a-96f5-b15d112d6f59">
|
||||
<img src="https://github.com/user-attachments/assets/a6ae2080-e865-4fde-b41d-5a09964d7de2">
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -41,7 +41,7 @@ The Web Application is made using Laravel & VueJS while the Mobile Apps are buil
|
||||
## Discord
|
||||
|
||||
Join the InvoiceShelf discord server to discuss:
|
||||
[Invite Link](https://discord.gg/KMpTGTpk)
|
||||
[Invite Link](https://discord.gg/eHXf4zWhsR)
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from '@/scripts/App.vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
import messages from '@/scripts/locales/locales'
|
||||
import messages from '/lang/locales'
|
||||
import router from '@/scripts/router/index'
|
||||
import { defineGlobalComponents } from './global-components'
|
||||
import utils from '@/scripts/helpers/utilities.js'
|
||||
|
||||
@@ -271,23 +271,19 @@ const price = computed({
|
||||
} else {
|
||||
updateItemAttribute('price', newValue)
|
||||
}
|
||||
setDiscount()
|
||||
},
|
||||
})
|
||||
|
||||
const subtotal = computed(() => props.itemData.price * props.itemData.quantity)
|
||||
const subtotal = computed(() => Math.round(props.itemData.price * props.itemData.quantity))
|
||||
|
||||
const discount = computed({
|
||||
get: () => {
|
||||
return props.itemData.discount
|
||||
},
|
||||
set: (newValue) => {
|
||||
if (props.itemData.discount_type === 'percentage') {
|
||||
updateItemAttribute('discount_val', (subtotal.value * newValue) / 100)
|
||||
} else {
|
||||
updateItemAttribute('discount_val', Math.round(newValue * 100))
|
||||
}
|
||||
|
||||
updateItemAttribute('discount', newValue)
|
||||
setDiscount()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -313,7 +309,7 @@ const showRemoveButton = computed(() => {
|
||||
const totalSimpleTax = computed(() => {
|
||||
return Math.round(
|
||||
sumBy(props.itemData.taxes, function (tax) {
|
||||
if (!tax.compound_tax) {
|
||||
if (tax.amount) {
|
||||
return tax.amount
|
||||
}
|
||||
return 0
|
||||
@@ -321,18 +317,7 @@ const totalSimpleTax = computed(() => {
|
||||
)
|
||||
})
|
||||
|
||||
const totalCompoundTax = computed(() => {
|
||||
return Math.round(
|
||||
sumBy(props.itemData.taxes, function (tax) {
|
||||
if (tax.compound_tax) {
|
||||
return tax.amount
|
||||
}
|
||||
return 0
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const totalTax = computed(() => totalSimpleTax.value + totalCompoundTax.value)
|
||||
const totalTax = computed(() => totalSimpleTax.value)
|
||||
|
||||
const rules = {
|
||||
name: {
|
||||
@@ -399,7 +384,7 @@ const v$ = useVuelidate(
|
||||
|
||||
function updateTax(data) {
|
||||
props.store.$patch((state) => {
|
||||
state[props.storeProp].items[props.index]['taxes'][data.index] = data.item
|
||||
state[props.storeProp].items[props.index]['taxes'][data.index] = data.item
|
||||
})
|
||||
|
||||
let lastTax = props.itemData.taxes[props.itemData.taxes.length - 1]
|
||||
@@ -416,6 +401,16 @@ function updateTax(data) {
|
||||
syncItemToStore()
|
||||
}
|
||||
|
||||
function setDiscount() {
|
||||
const newValue = props.store[props.storeProp].items[props.index].discount
|
||||
|
||||
if (props.itemData.discount_type === 'percentage'){
|
||||
updateItemAttribute('discount_val', Math.round((subtotal.value * newValue) / 100))
|
||||
}else{
|
||||
updateItemAttribute('discount_val', Math.round(newValue * 100))
|
||||
}
|
||||
}
|
||||
|
||||
function searchVal(val) {
|
||||
updateItemAttribute('name', val)
|
||||
}
|
||||
@@ -485,10 +480,12 @@ function syncItemToStore() {
|
||||
total: total.value,
|
||||
sub_total: subtotal.value,
|
||||
totalSimpleTax: totalSimpleTax.value,
|
||||
totalCompoundTax: totalCompoundTax.value,
|
||||
totalTax: totalTax.value,
|
||||
tax: totalTax.value,
|
||||
taxes: [...itemTaxes],
|
||||
tax_type_ids: itemTaxes.flatMap(_t =>
|
||||
_t.tax_type_id ? _t.tax_type_id : [],
|
||||
),
|
||||
}
|
||||
|
||||
props.store.updateItem(data)
|
||||
|
||||
@@ -146,14 +146,14 @@ const filteredTypes = computed(() => {
|
||||
})
|
||||
|
||||
const taxAmount = computed(() => {
|
||||
if (localTax.compound_tax && props.discountedTotal) {
|
||||
return ((props.discountedTotal + props.totalTax) * localTax.percent) / 100
|
||||
}
|
||||
|
||||
if (props.discountedTotal && localTax.percent) {
|
||||
const taxPerItemEnabled = props.store[props.storeProp].tax_per_item === 'YES'
|
||||
const discountPerItemEnabled = props.store[props.storeProp].discount_per_item === 'YES'
|
||||
if (taxPerItemEnabled && !discountPerItemEnabled){
|
||||
return getTaxAmount()
|
||||
}
|
||||
return (props.discountedTotal * localTax.percent) / 100
|
||||
}
|
||||
|
||||
return 0
|
||||
})
|
||||
|
||||
@@ -171,6 +171,13 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
watch(
|
||||
() => taxAmount.value,
|
||||
() => {
|
||||
updateRowTax()
|
||||
},
|
||||
)
|
||||
|
||||
// Set SelectedTax
|
||||
if (props.taxData.tax_type_id > 0) {
|
||||
selectedTax.value = taxTypeStore.taxTypes.find(
|
||||
@@ -183,7 +190,6 @@ updateRowTax()
|
||||
function onSelectTax(val) {
|
||||
localTax.percent = val.percent
|
||||
localTax.tax_type_id = val.id
|
||||
localTax.compound_tax = val.compound_tax
|
||||
localTax.name = val.name
|
||||
|
||||
updateRowTax()
|
||||
@@ -220,6 +226,27 @@ function openTaxModal() {
|
||||
function removeTax(index) {
|
||||
props.store.$patch((state) => {
|
||||
state[props.storeProp].items[props.itemIndex].taxes.splice(index, 1)
|
||||
state[props.storeProp].items[props.itemIndex].tax = 0
|
||||
state[props.storeProp].items[props.itemIndex].totalTax = 0
|
||||
})
|
||||
}
|
||||
|
||||
function getTaxAmount() {
|
||||
let total = 0
|
||||
let discount = 0
|
||||
const itemTotal = props.discountedTotal
|
||||
const modelDiscount = props.store[props.storeProp].discount ? props.store[props.storeProp].discount : 0
|
||||
const type = props.store[props.storeProp].discount_type
|
||||
if (modelDiscount > 0) {
|
||||
props.store[props.storeProp].items.forEach((_i) => {
|
||||
total += _i.total
|
||||
})
|
||||
const proportion = (itemTotal / total).toFixed(2)
|
||||
discount = type === 'fixed' ? modelDiscount * 100 : (total * modelDiscount) / 100
|
||||
const itemDiscount = Math.round(discount * proportion)
|
||||
const discounted = itemTotal - itemDiscount
|
||||
return Math.round((discounted * localTax.percent) / 100)
|
||||
}
|
||||
return Math.round((props.discountedTotal * localTax.percent) / 100)
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, inject, ref } from 'vue'
|
||||
import { computed, inject, ref, watch } from 'vue'
|
||||
import Guid from 'guid'
|
||||
import Tax from './CreateTotalTaxes.vue'
|
||||
import TaxStub from '@/scripts/admin/stub/abilities'
|
||||
@@ -227,19 +227,20 @@ const utils = inject('$utils')
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
watch(
|
||||
() => props.store[props.storeProp].items,
|
||||
(val) => {
|
||||
setDiscount()
|
||||
}, { deep: true },
|
||||
)
|
||||
|
||||
const totalDiscount = computed({
|
||||
get: () => {
|
||||
return props.store[props.storeProp].discount
|
||||
},
|
||||
set: (newValue) => {
|
||||
if (props.store[props.storeProp].discount_type === 'percentage') {
|
||||
props.store[props.storeProp].discount_val = Math.round(
|
||||
(props.store.getSubTotal * newValue) / 100
|
||||
)
|
||||
} else {
|
||||
props.store[props.storeProp].discount_val = Math.round(newValue * 100)
|
||||
}
|
||||
props.store[props.storeProp].discount = newValue
|
||||
setDiscount()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -265,7 +266,7 @@ const itemWiseTaxes = computed(() => {
|
||||
} else if (tax.tax_type_id) {
|
||||
taxes.push({
|
||||
tax_type_id: tax.tax_type_id,
|
||||
amount: tax.amount,
|
||||
amount: Math.round(tax.amount),
|
||||
percent: tax.percent,
|
||||
name: tax.name,
|
||||
})
|
||||
@@ -284,6 +285,19 @@ const defaultCurrency = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
function setDiscount() {
|
||||
const newValue = props.store[props.storeProp].discount
|
||||
|
||||
if (props.store[props.storeProp].discount_type === 'percentage') {
|
||||
props.store[props.storeProp].discount_val
|
||||
= Math.round((props.store.getSubTotal * newValue) / 100)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
props.store[props.storeProp].discount_val = Math.round(newValue * 100)
|
||||
}
|
||||
|
||||
function selectFixed() {
|
||||
if (props.store[props.storeProp].discount_type === 'fixed') {
|
||||
return
|
||||
@@ -295,24 +309,21 @@ function selectFixed() {
|
||||
}
|
||||
|
||||
function selectPercentage() {
|
||||
if (props.store[props.storeProp].discount_type === 'percentage') {
|
||||
if (props.store[props.storeProp].discount_type === 'percentage'){
|
||||
return
|
||||
}
|
||||
props.store[props.storeProp].discount_val =
|
||||
(props.store.getSubTotal * props.store[props.storeProp].discount) / 100
|
||||
|
||||
const val = Math.round(props.store[props.storeProp].discount * 100) / 100
|
||||
|
||||
props.store[props.storeProp].discount_val
|
||||
= Math.round((props.store.getSubTotal * val) / 100)
|
||||
|
||||
props.store[props.storeProp].discount_type = 'percentage'
|
||||
}
|
||||
|
||||
function onSelectTax(selectedTax) {
|
||||
let amount = 0
|
||||
|
||||
if (selectedTax.compound_tax && props.store.getSubtotalWithDiscount) {
|
||||
amount = Math.round(
|
||||
((props.store.getSubtotalWithDiscount + props.store.getTotalSimpleTax) *
|
||||
selectedTax.percent) /
|
||||
100
|
||||
)
|
||||
} else if (props.store.getSubtotalWithDiscount && selectedTax.percent) {
|
||||
if (props.store.getSubtotalWithDiscount && selectedTax.percent) {
|
||||
amount = Math.round(
|
||||
(props.store.getSubtotalWithDiscount * selectedTax.percent) / 100
|
||||
)
|
||||
@@ -323,7 +334,6 @@ function onSelectTax(selectedTax) {
|
||||
id: Guid.raw(),
|
||||
name: selectedTax.name,
|
||||
percent: selectedTax.percent,
|
||||
compound_tax: selectedTax.compound_tax,
|
||||
tax_type_id: selectedTax.id,
|
||||
amount,
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user