Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecb029758b | ||
|
|
6ea112c04f | ||
|
|
2784318ebf | ||
|
|
bb580f4b88 | ||
|
|
8c83df558c | ||
|
|
dc8a85538f | ||
|
|
093b2acc24 | ||
|
|
df85fd6a0a | ||
|
|
db4396f160 | ||
|
|
1831560a62 | ||
|
|
223678e5bd | ||
|
|
9bb4963e8a | ||
|
|
8788f3d504 | ||
|
|
0d006846d5 | ||
|
|
d2559c8773 | ||
|
|
20a489e52d | ||
|
|
e73ab3d4ee | ||
|
|
1c7274287d | ||
|
|
5ba8639ac8 |
@@ -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
|
||||
|
||||
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',
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -64,8 +64,6 @@ class EnvironmentManager
|
||||
if ($entry[0] == $data_key) {
|
||||
$env[$env_key] = $data_key.'='.$this->encode($data_value);
|
||||
$updated = true;
|
||||
} else {
|
||||
$env[$env_key] = $this->encode($env_value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +88,7 @@ class EnvironmentManager
|
||||
private function encode($str)
|
||||
{
|
||||
|
||||
if (strpos($str, ' ') !== false || preg_match('/'.preg_quote('^\'£$%^&*()}{@#~?><,@|-=-_+-¬', '/').'/', $str)) {
|
||||
if ((strpos($str, ' ') !== false || preg_match('/'.preg_quote('^\'£$%^&*()}{@#~?><,@|-=-_+-¬', '/').'/', $str)) && ($str[0] != '"' || $str[strlen($str) - 1] != '"')) {
|
||||
$str = '"'.$str.'"';
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"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",
|
||||
|
||||
459
composer.lock
generated
459
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
|
||||
|
||||
@@ -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) {
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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,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,
|
||||
}
|
||||
|
||||
@@ -21,13 +21,7 @@
|
||||
>
|
||||
<template #default="slotProps">
|
||||
<div
|
||||
class="
|
||||
z-0
|
||||
flex
|
||||
justify-end
|
||||
p-4
|
||||
border-t border-solid border-gray-light
|
||||
"
|
||||
class="z-0 flex justify-end p-4 border-t border-solid border-gray-light"
|
||||
>
|
||||
<BaseButton
|
||||
class="mr-3 text-sm"
|
||||
@@ -66,12 +60,14 @@ import { computed, ref, watchEffect } from 'vue'
|
||||
import Dropbox from '@/scripts/admin/components/modal-components/disks/DropboxDisk.vue'
|
||||
import Local from '@/scripts/admin/components/modal-components/disks/LocalDisk.vue'
|
||||
import S3 from '@/scripts/admin/components/modal-components/disks/S3Disk.vue'
|
||||
import S3compat from '@/scripts/admin/components/modal-components/disks/S3CompatDisk.vue'
|
||||
import DoSpaces from '@/scripts/admin/components/modal-components/disks/DoSpacesDisk.vue'
|
||||
export default {
|
||||
components: {
|
||||
Dropbox,
|
||||
Local,
|
||||
S3,
|
||||
S3compat,
|
||||
DoSpaces,
|
||||
},
|
||||
setup() {
|
||||
@@ -109,20 +105,28 @@ export default {
|
||||
}
|
||||
|
||||
async function createNewDisk(data) {
|
||||
Object.assign(diskStore.diskConfigData, data)
|
||||
isLoading.value = true
|
||||
try {
|
||||
Object.assign(diskStore.diskConfigData, data)
|
||||
isLoading.value = true
|
||||
|
||||
let formData = {
|
||||
id: modalStore.id,
|
||||
...data,
|
||||
let formData = {
|
||||
id: modalStore.id,
|
||||
...data,
|
||||
}
|
||||
|
||||
let response = null
|
||||
const action = isEdit.value
|
||||
? diskStore.updateDisk
|
||||
: diskStore.createDisk
|
||||
|
||||
response = await action(formData)
|
||||
modalStore.refreshData()
|
||||
closeDiskModal()
|
||||
} catch (e) {
|
||||
// error is handled by the disk store
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
let response = null
|
||||
const action = isEdit.value ? diskStore.updateDisk : diskStore.createDisk
|
||||
response = await action(formData)
|
||||
isLoading.value = false
|
||||
modalStore.refreshData()
|
||||
closeDiskModal()
|
||||
}
|
||||
|
||||
function closeDiskModal() {
|
||||
|
||||
@@ -77,17 +77,6 @@
|
||||
@input="v$.currentTaxType.description.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('tax_types.compound_tax')"
|
||||
variant="horizontal"
|
||||
class="flex flex-row-reverse"
|
||||
>
|
||||
<BaseSwitch
|
||||
v-model="taxTypeStore.currentTaxType.compound_tax"
|
||||
class="flex items-center"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
</div>
|
||||
<div
|
||||
@@ -209,14 +198,7 @@ async function submitTaxTypeData() {
|
||||
|
||||
function SelectTax(taxData) {
|
||||
let amount = 0
|
||||
if (taxData.compound_tax && estimateStore.getSubtotalWithDiscount) {
|
||||
amount = Math.round(
|
||||
((estimateStore.getSubtotalWithDiscount +
|
||||
estimateStore.getTotalSimpleTax) *
|
||||
taxData.percent) /
|
||||
100
|
||||
)
|
||||
} else if (estimateStore.getSubtotalWithDiscount && taxData.percent) {
|
||||
if (estimateStore.getSubtotalWithDiscount && taxData.percent) {
|
||||
amount = Math.round(
|
||||
(estimateStore.getSubtotalWithDiscount * taxData.percent) / 100
|
||||
)
|
||||
@@ -226,7 +208,6 @@ function SelectTax(taxData) {
|
||||
id: Guid.raw(),
|
||||
name: taxData.name,
|
||||
percent: taxData.percent,
|
||||
compound_tax: taxData.compound_tax,
|
||||
tax_type_id: taxData.id,
|
||||
amount,
|
||||
}
|
||||
@@ -242,7 +223,6 @@ function selectItemTax(taxData) {
|
||||
id: Guid.raw(),
|
||||
name: taxData.name,
|
||||
percent: taxData.percent,
|
||||
compound_tax: taxData.compound_tax,
|
||||
tax_type_id: taxData.id,
|
||||
}
|
||||
modalStore.refreshData(data)
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<form @submit.prevent="submitData">
|
||||
<div class="px-8 py-6">
|
||||
<BaseInputGrid>
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.name')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.name.$error &&
|
||||
v$.s3CompatDiskConfigData.name.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model="diskStore.s3CompatDiskConfigData.name"
|
||||
type="text"
|
||||
name="name"
|
||||
:invalid="v$.s3CompatDiskConfigData.name.$error"
|
||||
@input="v$.s3CompatDiskConfigData.name.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.driver')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.selected_driver.$error &&
|
||||
v$.s3CompatDiskConfigData.selected_driver.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseMultiselect
|
||||
v-model="selected_driver"
|
||||
:invalid="v$.s3CompatDiskConfigData.selected_driver.$error"
|
||||
value-prop="value"
|
||||
:options="disks"
|
||||
searchable
|
||||
label="name"
|
||||
:can-deselect="false"
|
||||
@update:modelValue="onChangeDriver(data)"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_endpoint')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.root.$error &&
|
||||
v$.s3CompatDiskConfigData.root.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.endpoint"
|
||||
type="url"
|
||||
name="endpoint"
|
||||
placeholder="http://127.0.0.1:9005"
|
||||
:invalid="v$.s3CompatDiskConfigData.endpoint.$error"
|
||||
@input="v$.s3CompatDiskConfigData.endpoint.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_root')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.root.$error &&
|
||||
v$.s3CompatDiskConfigData.root.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.root"
|
||||
type="text"
|
||||
name="root"
|
||||
placeholder="Ex. /user/root/"
|
||||
:invalid="v$.s3CompatDiskConfigData.root.$error"
|
||||
@input="v$.s3CompatDiskConfigData.root.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_key')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.key.$error &&
|
||||
v$.s3CompatDiskConfigData.key.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.key"
|
||||
type="text"
|
||||
name="key"
|
||||
placeholder="Ex. KEIS4S39SERSDS"
|
||||
:invalid="v$.s3CompatDiskConfigData.key.$error"
|
||||
@input="v$.s3CompatDiskConfigData.key.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_secret')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.secret.$error &&
|
||||
v$.s3CompatDiskConfigData.secret.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.secret"
|
||||
type="text"
|
||||
name="secret"
|
||||
placeholder="Ex. ********"
|
||||
:invalid="v$.s3CompatDiskConfigData.secret.$error"
|
||||
@input="v$.s3CompatDiskConfigData.secret.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_region')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.region.$error &&
|
||||
v$.s3CompatDiskConfigData.region.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.region"
|
||||
type="text"
|
||||
name="region"
|
||||
placeholder="Ex. us-west"
|
||||
:invalid="v$.s3CompatDiskConfigData.region.$error"
|
||||
@input="v$.s3CompatDiskConfigData.region.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.disk.s3_bucket')"
|
||||
:error="
|
||||
v$.s3CompatDiskConfigData.bucket.$error &&
|
||||
v$.s3CompatDiskConfigData.bucket.$errors[0].$message
|
||||
"
|
||||
required
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="diskStore.s3CompatDiskConfigData.bucket"
|
||||
type="text"
|
||||
name="bucket"
|
||||
placeholder="Ex. AppName"
|
||||
:invalid="v$.s3CompatDiskConfigData.bucket.$error"
|
||||
@input="v$.s3CompatDiskConfigData.bucket.$touch()"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</BaseInputGrid>
|
||||
<div v-if="!isDisabled" class="flex items-center mt-6">
|
||||
<div class="relative flex items-center w-12">
|
||||
<BaseSwitch v-model="set_as_default" class="flex" />
|
||||
</div>
|
||||
<div class="ml-4 right">
|
||||
<p class="p-0 mb-1 text-base leading-snug text-black box-title">
|
||||
{{ $t('settings.disk.is_default') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<slot :disk-data="{ isLoading, submitData }" />
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useDiskStore } from '@/scripts/admin/stores/disk'
|
||||
import { useModalStore } from '@/scripts/stores/modal'
|
||||
import { computed, onBeforeUnmount, reactive, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { required, helpers } from '@vuelidate/validators'
|
||||
export default {
|
||||
props: {
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
require: true,
|
||||
default: false,
|
||||
},
|
||||
disks: {
|
||||
type: Array,
|
||||
require: true,
|
||||
default: Array,
|
||||
},
|
||||
},
|
||||
|
||||
emits: ['submit', 'onChangeDisk'],
|
||||
|
||||
setup(props, { emit }) {
|
||||
const diskStore = useDiskStore()
|
||||
const modalStore = useModalStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
let set_as_default = ref(false)
|
||||
let isLoading = ref(false)
|
||||
let selected_disk = ref(null)
|
||||
let is_current_disk = ref(null)
|
||||
|
||||
const selected_driver = computed({
|
||||
get: () => diskStore.selected_driver,
|
||||
set: (value) => {
|
||||
diskStore.selected_driver = value
|
||||
diskStore.s3CompatDiskConfigData.selected_driver = value
|
||||
},
|
||||
})
|
||||
|
||||
const rules = computed(() => {
|
||||
return {
|
||||
s3CompatDiskConfigData: {
|
||||
name: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
endpoint: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
root: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
key: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
secret: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
region: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
bucket: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
selected_driver: {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => diskStore),
|
||||
)
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
diskStore.s3CompatDiskConfigData = {
|
||||
name: null,
|
||||
selected_driver: 's3compat',
|
||||
key: null,
|
||||
secret: null,
|
||||
region: null,
|
||||
bucket: null,
|
||||
root: null,
|
||||
}
|
||||
})
|
||||
|
||||
loadData()
|
||||
|
||||
async function loadData() {
|
||||
isLoading.value = true
|
||||
let data = reactive({
|
||||
disk: 's3compat',
|
||||
})
|
||||
|
||||
if (props.isEdit) {
|
||||
Object.assign(diskStore.s3CompatDiskConfigData, modalStore.data)
|
||||
set_as_default.value = modalStore.data.set_as_default
|
||||
|
||||
if (set_as_default.value) {
|
||||
is_current_disk.value = true
|
||||
}
|
||||
} else {
|
||||
let diskData = await diskStore.fetchDiskEnv(data)
|
||||
Object.assign(diskStore.s3CompatDiskConfigData, diskData.data)
|
||||
}
|
||||
selected_disk.value = props.disks.find((v) => v.value == 's3compat')
|
||||
isLoading.value = false
|
||||
}
|
||||
|
||||
const isDisabled = computed(() => {
|
||||
return props.isEdit && set_as_default.value && is_current_disk.value
|
||||
? true
|
||||
: false
|
||||
})
|
||||
|
||||
async function submitData() {
|
||||
v$.value.s3CompatDiskConfigData.$touch()
|
||||
if (v$.value.s3CompatDiskConfigData.$invalid) {
|
||||
return true
|
||||
}
|
||||
|
||||
let data = {
|
||||
credentials: diskStore.s3CompatDiskConfigData,
|
||||
name: diskStore.s3CompatDiskConfigData.name,
|
||||
driver: selected_disk.value.value,
|
||||
set_as_default: set_as_default.value,
|
||||
}
|
||||
|
||||
emit('submit', data)
|
||||
return false
|
||||
}
|
||||
|
||||
function onChangeDriver() {
|
||||
emit('onChangeDisk', diskStore.s3CompatDiskConfigData.selected_driver)
|
||||
}
|
||||
|
||||
return {
|
||||
v$,
|
||||
diskStore,
|
||||
modalStore,
|
||||
set_as_default,
|
||||
isLoading,
|
||||
selected_disk,
|
||||
selected_driver,
|
||||
is_current_disk,
|
||||
loadData,
|
||||
submitData,
|
||||
onChangeDriver,
|
||||
isDisabled,
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -51,6 +51,17 @@ export const useDiskStore = (useWindow = false) => {
|
||||
bucket: '',
|
||||
root: '',
|
||||
},
|
||||
|
||||
s3CompatDiskConfigData: {
|
||||
name: '',
|
||||
selected_driver: 's3compat',
|
||||
key: '',
|
||||
secret: '',
|
||||
region: '',
|
||||
bucket: '',
|
||||
root: '',
|
||||
endpoint: '',
|
||||
},
|
||||
}),
|
||||
|
||||
getters: {
|
||||
|
||||
@@ -143,7 +143,8 @@ export const useEstimateStore = (useWindow = false) => {
|
||||
axios
|
||||
.get(`/api/v1/estimates/${id}`)
|
||||
.then((response) => {
|
||||
Object.assign(this.newEstimate, response.data.data)
|
||||
this.setEstimateData(response.data.data)
|
||||
this.setCustomerAddresses(this.newEstimate.customer)
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -154,6 +155,41 @@ export const useEstimateStore = (useWindow = false) => {
|
||||
})
|
||||
},
|
||||
|
||||
setEstimateData(estimate) {
|
||||
Object.assign(this.newEstimate, estimate)
|
||||
if (this.newEstimate.tax_per_item === 'YES') {
|
||||
this.newEstimate.items.forEach((_i) => {
|
||||
if (_i.taxes && !_i.taxes.length){
|
||||
_i.taxes.push({ ...taxStub, id: Guid.raw() })
|
||||
}
|
||||
})
|
||||
}
|
||||
if (this.newEstimate.discount_per_item === 'YES') {
|
||||
this.newEstimate.items.forEach((_i, index) => {
|
||||
if (_i.discount_type === 'fixed'){
|
||||
this.newEstimate.items[index].discount = _i.discount / 100
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (this.newEstimate.discount_type === 'fixed'){
|
||||
this.newEstimate.discount = this.newEstimate.discount / 100
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
setCustomerAddresses(customer) {
|
||||
const customer_business = customer.customer_business
|
||||
|
||||
if (customer_business?.billing_address){
|
||||
this.newEstimate.customer.billing_address = customer_business.billing_address
|
||||
}
|
||||
|
||||
if (customer_business?.shipping_address){
|
||||
this.newEstimate.customer.shipping_address = customer_business.shipping_address
|
||||
}
|
||||
},
|
||||
|
||||
addSalesTaxUs() {
|
||||
const taxTypeStore = useTaxTypeStore()
|
||||
let salesTax = { ...taxStub }
|
||||
|
||||
@@ -79,8 +79,10 @@ export const useGlobalStore = (useWindow = false) => {
|
||||
companyStore.selectedCompanyCurrency =
|
||||
response.data.current_company_currency
|
||||
|
||||
global.locale =
|
||||
response.data.current_user_settings.language || 'en'
|
||||
if(typeof global.locale !== 'string') {
|
||||
global.locale.value =
|
||||
response.data.current_user_settings.language || 'en'
|
||||
}
|
||||
|
||||
this.isAppLoaded = true
|
||||
resolve(response)
|
||||
|
||||
@@ -133,8 +133,8 @@ export const useInvoiceStore = (useWindow = false) => {
|
||||
axios
|
||||
.get(`/api/v1/invoices/${id}`)
|
||||
.then((response) => {
|
||||
Object.assign(this.newInvoice, response.data.data)
|
||||
this.newInvoice.customer = response.data.data.customer
|
||||
this.setInvoiceData(response.data.data)
|
||||
this.setCustomerAddresses(this.newInvoice.customer)
|
||||
resolve(response)
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -144,6 +144,38 @@ export const useInvoiceStore = (useWindow = false) => {
|
||||
})
|
||||
},
|
||||
|
||||
setInvoiceData(invoice) {
|
||||
Object.assign(this.newInvoice, invoice)
|
||||
|
||||
if (this.newInvoice.tax_per_item === 'YES') {
|
||||
this.newInvoice.items.forEach((_i) => {
|
||||
if (_i.taxes && !_i.taxes.length)
|
||||
_i.taxes.push({ ...taxStub, id: Guid.raw() })
|
||||
})
|
||||
}
|
||||
|
||||
if (this.newInvoice.discount_per_item === 'YES') {
|
||||
this.newInvoice.items.forEach((_i, index) => {
|
||||
if (_i.discount_type === 'fixed')
|
||||
this.newInvoice.items[index].discount = _i.discount / 100
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (this.newInvoice.discount_type === 'fixed')
|
||||
this.newInvoice.discount = this.newInvoice.discount / 100
|
||||
}
|
||||
},
|
||||
|
||||
setCustomerAddresses(customer) {
|
||||
const customer_business = customer.customer_business
|
||||
|
||||
if (customer_business?.billing_address)
|
||||
this.newInvoice.customer.billing_address = customer_business.billing_address
|
||||
|
||||
if (customer_business?.shipping_address)
|
||||
this.newInvoice.customer.shipping_address = customer_business.shipping_address
|
||||
},
|
||||
|
||||
addSalesTaxUs() {
|
||||
const taxTypeStore = useTaxTypeStore()
|
||||
let salesTax = { ...taxStub }
|
||||
|
||||
@@ -106,7 +106,7 @@ export const useUserStore = (useWindow = false) => {
|
||||
.then((response) => {
|
||||
if (data.settings.language) {
|
||||
this.currentUserSettings.language = data.settings.language
|
||||
global.locale = data.settings.language
|
||||
global.locale.value = data.settings.language
|
||||
}
|
||||
if (data.settings.default_estimate_template) {
|
||||
this.currentUserSettings.default_estimate_template =
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
@@ -257,11 +258,30 @@ async function submitForm() {
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
let data = {
|
||||
let data = cloneDeep({
|
||||
...estimateStore.newEstimate,
|
||||
sub_total: estimateStore.getSubTotal,
|
||||
total: estimateStore.getTotal,
|
||||
tax: estimateStore.getTotalTax,
|
||||
})
|
||||
if (data.discount_per_item === 'YES') {
|
||||
data.items.forEach((item, index) => {
|
||||
if (item.discount_type === 'fixed'){
|
||||
data.items[index].discount = Math.round(item.discount * 100)
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (data.discount_type === 'fixed'){
|
||||
data.discount = Math.round(data.discount * 100)
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!estimateStore.newEstimate.tax_per_item === 'YES'
|
||||
&& data.taxes.length
|
||||
){
|
||||
data.tax_type_ids = data.taxes.map(_t => _t.tax_type_id)
|
||||
}
|
||||
|
||||
const action = isEdit.value
|
||||
|
||||
@@ -131,6 +131,25 @@
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseInputGroup :label="$t('settings.company_info.tax_id')">
|
||||
<BaseInput
|
||||
v-model.trim="companyForm.tax_id"
|
||||
type="text"
|
||||
name="tax_id"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup
|
||||
:label="$t('settings.company_info.vat_id')"
|
||||
class="mt-4"
|
||||
>
|
||||
<BaseInput
|
||||
v-model.trim="companyForm.vat_id"
|
||||
type="text"
|
||||
name="vat_id"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
|
||||
<BaseButton :loading="isSaving" :disabled="isSaving" class="mt-4">
|
||||
@@ -162,6 +181,8 @@ let logoFileName = ref(null)
|
||||
|
||||
const companyForm = reactive({
|
||||
name: null,
|
||||
tax_id: null,
|
||||
vat_id: null,
|
||||
address: {
|
||||
address_street_1: '',
|
||||
address_street_2: '',
|
||||
@@ -200,13 +221,13 @@ const rules = {
|
||||
address_street_1: {
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.address_maxlength', { count: 255 }),
|
||||
maxLength(255)
|
||||
maxLength(255),
|
||||
),
|
||||
},
|
||||
address_street_2: {
|
||||
maxLength: helpers.withMessage(
|
||||
t('validation.address_maxlength', { count: 255 }),
|
||||
maxLength(255)
|
||||
maxLength(255),
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -241,7 +262,7 @@ async function next() {
|
||||
JSON.stringify({
|
||||
name: logoFileName.value,
|
||||
data: logoFileBlob.value,
|
||||
})
|
||||
}),
|
||||
)
|
||||
await companyStore.updateCompanyLogo(logoData)
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ import {
|
||||
decimal,
|
||||
} from '@vuelidate/validators'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { cloneDeep } from 'lodash'
|
||||
|
||||
import { useInvoiceStore } from '@/scripts/admin/stores/invoice'
|
||||
import { useModuleStore } from '@/scripts/admin/stores/module'
|
||||
@@ -258,11 +259,29 @@ async function submitForm() {
|
||||
|
||||
isSaving.value = true
|
||||
|
||||
let data = {
|
||||
let data = cloneDeep({
|
||||
...invoiceStore.newInvoice,
|
||||
sub_total: invoiceStore.getSubTotal,
|
||||
total: invoiceStore.getTotal,
|
||||
tax: invoiceStore.getTotalTax,
|
||||
})
|
||||
if (data.discount_per_item === 'YES') {
|
||||
data.items.forEach((item, index) => {
|
||||
if (item.discount_type === 'fixed'){
|
||||
data.items[index].discount = item.discount * 100
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
if (data.discount_type === 'fixed'){
|
||||
data.discount = data.discount * 100
|
||||
}
|
||||
}
|
||||
if (
|
||||
!invoiceStore.newInvoice.tax_per_item === 'YES'
|
||||
&& data.taxes.length
|
||||
){
|
||||
data.tax_type_ids = data.taxes.map(_t => _t.tax_type_id)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
label="name"
|
||||
value-prop="code"
|
||||
track-by="name"
|
||||
:searchable="true"
|
||||
open-direction="top"
|
||||
/>
|
||||
</BaseInputGroup>
|
||||
|
||||
@@ -84,6 +84,16 @@
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<BaseInputGroup :label="$t('settings.company_info.tax_id')">
|
||||
<BaseInput v-model="companyForm.tax_id" type="text" />
|
||||
</BaseInputGroup>
|
||||
|
||||
<BaseInputGroup :label="$t('settings.company_info.vat_id')">
|
||||
<BaseInput v-model="companyForm.vat_id" type="text" />
|
||||
</BaseInputGroup>
|
||||
</div>
|
||||
</BaseInputGrid>
|
||||
|
||||
<BaseButton
|
||||
@@ -111,24 +121,7 @@
|
||||
<div class="mt-5">
|
||||
<button
|
||||
type="button"
|
||||
class="
|
||||
inline-flex
|
||||
items-center
|
||||
justify-center
|
||||
px-4
|
||||
py-2
|
||||
border border-transparent
|
||||
font-medium
|
||||
rounded-md
|
||||
text-red-700
|
||||
bg-red-100
|
||||
hover:bg-red-200
|
||||
focus:outline-none
|
||||
focus:ring-2
|
||||
focus:ring-offset-2
|
||||
focus:ring-red-500
|
||||
sm:text-sm
|
||||
"
|
||||
class="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
|
||||
@click="removeCompany"
|
||||
>
|
||||
{{ $t('general.delete') }}
|
||||
@@ -161,6 +154,8 @@ let isSaving = ref(false)
|
||||
const companyForm = reactive({
|
||||
name: null,
|
||||
logo: null,
|
||||
tax_id: null,
|
||||
vat_id: null,
|
||||
address: {
|
||||
address_street_1: '',
|
||||
address_street_2: '',
|
||||
@@ -194,7 +189,7 @@ const rules = computed(() => {
|
||||
required: helpers.withMessage(t('validation.required'), required),
|
||||
minLength: helpers.withMessage(
|
||||
t('validation.name_min_length'),
|
||||
minLength(3)
|
||||
minLength(3),
|
||||
),
|
||||
},
|
||||
address: {
|
||||
@@ -207,7 +202,7 @@ const rules = computed(() => {
|
||||
|
||||
const v$ = useVuelidate(
|
||||
rules,
|
||||
computed(() => companyForm)
|
||||
computed(() => companyForm),
|
||||
)
|
||||
|
||||
globalStore.fetchCountries()
|
||||
@@ -243,7 +238,7 @@ async function updateCompanyData() {
|
||||
JSON.stringify({
|
||||
name: logoFileName.value,
|
||||
data: logoFileBlob.value,
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
logoData.append('is_company_logo_removed', isCompanyLogoRemoved.value)
|
||||
|
||||
@@ -93,7 +93,7 @@ import FileDiskModal from '@/scripts/admin/components/modal-components/FileDiskM
|
||||
|
||||
const utils = inject('utils')
|
||||
|
||||
const modelStore = useModalStore()
|
||||
const modalStore = useModalStore()
|
||||
const diskStore = useDiskStore()
|
||||
const companyStore = useCompanyStore()
|
||||
const dialogStore = useDialogStore()
|
||||
@@ -189,7 +189,7 @@ function isNotSystemDisk(disk) {
|
||||
}
|
||||
|
||||
function openCreateDiskModal() {
|
||||
modelStore.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('settings.disk.new_disk'),
|
||||
componentName: 'FileDiskModal',
|
||||
variant: 'lg',
|
||||
@@ -198,7 +198,7 @@ function openCreateDiskModal() {
|
||||
}
|
||||
|
||||
function openEditDiskModal(data) {
|
||||
modelStore.openModal({
|
||||
modalStore.openModal({
|
||||
title: t('settings.disk.edit_file_disk'),
|
||||
componentName: 'FileDiskModal',
|
||||
variant: 'lg',
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user