Merge branch 'add-customization' into 'master'

Add customization

See merge request mohit.panjvani/crater-web!134
This commit is contained in:
Mohit Panjwani
2019-12-12 10:05:16 +00:00
23 changed files with 1000 additions and 92 deletions

View File

@ -54,10 +54,13 @@ class Estimate extends Model
'sub_total' => 'float' 'sub_total' => 'float'
]; ];
public static function getNextEstimateNumber() public static function getNextEstimateNumber($value)
{ {
// Get the last created order // Get the last created order
$lastOrder = Estimate::orderBy('created_at', 'desc')->first(); $lastOrder = Estimate::where('estimate_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) { if (!$lastOrder) {
// We get here if there is no order at all // We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end. // If there is no number set it to 0, which will be 1 at the end.
@ -99,10 +102,16 @@ class Estimate extends Model
public function getEstimateNumAttribute() public function getEstimateNumAttribute()
{ {
$position = $this->strposX($this->estimate_number, "-", 2) + 1; $position = $this->strposX($this->estimate_number, "-", 1) + 1;
return substr($this->estimate_number, $position); return substr($this->estimate_number, $position);
} }
public function getEstimatePrefixAttribute()
{
$prefix = explode("-",$this->estimate_number)[0];
return $prefix;
}
private function strposX($haystack, $needle, $number) private function strposX($haystack, $needle, $number)
{ {
if ($number == '1') { if ($number == '1') {

View File

@ -153,6 +153,58 @@ class CompanyController extends Controller
]); ]);
} }
public function getCustomizeSetting (Request $request)
{
$invoice_prefix = CompanySetting::getSetting('invoice_prefix', $request->header('company'));
$invoice_auto_generate = CompanySetting::getSetting('invoice_auto_generate', $request->header('company'));
$estimate_prefix = CompanySetting::getSetting('estimate_prefix', $request->header('company'));
$estimate_auto_generate = CompanySetting::getSetting('estimate_auto_generate', $request->header('company'));
$payment_prefix = CompanySetting::getSetting('payment_prefix', $request->header('company'));
$payment_auto_generate = CompanySetting::getSetting('payment_auto_generate', $request->header('company'));
return response()->json([
'invoice_prefix' => $invoice_prefix,
'invoice_auto_generate' => $invoice_auto_generate,
'estimate_prefix' => $estimate_prefix,
'estimate_auto_generate' => $estimate_auto_generate,
'payment_prefix' => $payment_prefix,
'payment_auto_generate' => $payment_auto_generate,
]);
}
public function updateCustomizeSetting (Request $request)
{
$sets = [];
if ($request->type == "PAYMENTS") {
$sets = [
'payment_prefix'
];
}
if ($request->type == "INVOICES") {
$sets = [
'invoice_prefix',
];
}
if ($request->type == "ESTIMATES") {
$sets = [
'estimate_prefix',
];
}
foreach ($sets as $key) {
CompanySetting::setSetting($key, $request->$key, $request->header('company'));
}
return response()->json([
'success' => true
]);
}
public function updateSetting(SettingRequest $request) public function updateSetting(SettingRequest $request)
{ {
CompanySetting::setSetting($request->key, $request->value, $request->header('company')); CompanySetting::setSetting($request->key, $request->value, $request->header('company'));

View File

@ -56,25 +56,41 @@ class EstimatesController extends Controller
public function create(Request $request) public function create(Request $request)
{ {
$nextEstimateNumber = 'EST-'.Estimate::getNextEstimateNumber(); $estimate_prefix = CompanySetting::getSetting('estimate_prefix', $request->header('company'));
$estimate_num_auto_generate = CompanySetting::getSetting('estimate_auto_generate', $request->header('company'));
$nextEstimateNumberAttribute = null;
if ($estimate_num_auto_generate == "YES") {
$nextEstimateNumberAttribute = Estimate::getNextEstimateNumber($estimate_prefix);
}
$tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company')); $tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company'));
$discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company')); $discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company'));
$customers = User::where('role', 'customer')->get(); $customers = User::where('role', 'customer')->get();
return response()->json([ return response()->json([
'customers' => $customers, 'customers' => $customers,
'nextEstimateNumber' => $nextEstimateNumber, 'nextEstimateNumber' => $nextEstimateNumberAttribute,
'taxes' => Tax::whereCompany($request->header('company'))->latest()->get(), 'taxes' => Tax::whereCompany($request->header('company'))->latest()->get(),
'items' => Item::whereCompany($request->header('company'))->get(), 'items' => Item::whereCompany($request->header('company'))->get(),
'tax_per_item' => $tax_per_item, 'tax_per_item' => $tax_per_item,
'discount_per_item' => $discount_per_item, 'discount_per_item' => $discount_per_item,
'estimateTemplates' => EstimateTemplate::all(), 'estimateTemplates' => EstimateTemplate::all(),
'shareable_link' => '' 'shareable_link' => '',
'estimate_prefix' => $estimate_prefix
]); ]);
} }
public function store(EstimatesRequest $request) public function store(EstimatesRequest $request)
{ {
$estimate_number = explode("-",$request->estimate_number);
$number_attributes['estimate_number'] = $estimate_number[0].'-'.sprintf('%06d', intval($estimate_number[1]));
Validator::make($number_attributes, [
'estimate_number' => 'required|unique:estimates,estimate_number'
])->validate();
$estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date); $estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date);
$expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date); $expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date);
$status = Estimate::STATUS_DRAFT; $status = Estimate::STATUS_DRAFT;
@ -101,7 +117,7 @@ class EstimatesController extends Controller
$estimate = Estimate::create([ $estimate = Estimate::create([
'estimate_date' => $estimate_date, 'estimate_date' => $estimate_date,
'expiry_date' => $expiry_date, 'expiry_date' => $expiry_date,
'estimate_number' => $request->estimate_number, 'estimate_number' => $number_attributes['estimate_number'],
'reference_number' => $request->reference_number, 'reference_number' => $request->reference_number,
'user_id' => $request->user_id, 'user_id' => $request->user_id,
'company_id' => $request->header('company'), 'company_id' => $request->header('company'),
@ -216,26 +232,33 @@ class EstimatesController extends Controller
return response()->json( [ return response()->json( [
'customers' => $customers, 'customers' => $customers,
'nextEstimateNumber' => $estimate->estimate_number, 'nextEstimateNumber' => $estimate->getEstimateNumAttribute(),
'taxes' => Tax::latest()->whereCompany($request->header('company'))->get(), 'taxes' => Tax::latest()->whereCompany($request->header('company'))->get(),
'estimate' => $estimate, 'estimate' => $estimate,
'items' => Item::whereCompany($request->header('company'))->latest()->get(), 'items' => Item::whereCompany($request->header('company'))->latest()->get(),
'estimateTemplates' => EstimateTemplate::all(), 'estimateTemplates' => EstimateTemplate::all(),
'tax_per_item' => $estimate->tax_per_item, 'tax_per_item' => $estimate->tax_per_item,
'discount_per_item' => $estimate->discount_per_item, 'discount_per_item' => $estimate->discount_per_item,
'shareable_link' => url('/estimates/pdf/'.$estimate->unique_hash) 'shareable_link' => url('/estimates/pdf/'.$estimate->unique_hash),
'estimate_prefix' => $estimate->getEstimatePrefixAttribute()
]); ]);
} }
public function update(EstimatesRequest $request, $id) public function update(EstimatesRequest $request, $id)
{ {
$estimate_number = explode("-",$request->estimate_number);
$number_attributes['estimate_number'] = $estimate_number[0].'-'.sprintf('%06d', intval($estimate_number[1]));
Validator::make($number_attributes, [
'estimate_number' => 'required|unique:estimates,estimate_number'.','.$id
])->validate();
$estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date); $estimate_date = Carbon::createFromFormat('d/m/Y', $request->estimate_date);
$expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date); $expiry_date = Carbon::createFromFormat('d/m/Y', $request->expiry_date);
$estimate = Estimate::find($id); $estimate = Estimate::find($id);
$estimate->estimate_date = $estimate_date; $estimate->estimate_date = $estimate_date;
$estimate->expiry_date = $expiry_date; $estimate->expiry_date = $expiry_date;
$estimate->estimate_number = $request->estimate_number; $estimate->estimate_number = $number_attributes['estimate_number'];
$estimate->reference_number = $request->reference_number; $estimate->reference_number = $request->reference_number;
$estimate->user_id = $request->user_id; $estimate->user_id = $request->user_id;
$estimate->estimate_template_id = $request->estimate_template_id; $estimate->estimate_template_id = $request->estimate_template_id;

View File

@ -66,14 +66,22 @@ class InvoicesController extends Controller
{ {
$tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company')); $tax_per_item = CompanySetting::getSetting('tax_per_item', $request->header('company'));
$discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company')); $discount_per_item = CompanySetting::getSetting('discount_per_item', $request->header('company'));
$nextInvoiceNumber = "INV-".Invoice::getNextInvoiceNumber(); $invoice_prefix = CompanySetting::getSetting('invoice_prefix', $request->header('company'));
$invoice_num_auto_generate = CompanySetting::getSetting('invoice_auto_generate', $request->header('company'));
$nextInvoiceNumberAttribute = null;
if ($invoice_num_auto_generate == "YES") {
$nextInvoiceNumberAttribute = Invoice::getNextInvoiceNumber($invoice_prefix);
}
return response()->json([ return response()->json([
'nextInvoiceNumber' => $nextInvoiceNumber, 'nextInvoiceNumber' => $nextInvoiceNumberAttribute,
'items' => Item::with('taxes')->whereCompany($request->header('company'))->get(), 'items' => Item::with('taxes')->whereCompany($request->header('company'))->get(),
'invoiceTemplates' => InvoiceTemplate::all(), 'invoiceTemplates' => InvoiceTemplate::all(),
'tax_per_item' => $tax_per_item, 'tax_per_item' => $tax_per_item,
'discount_per_item' => $discount_per_item 'discount_per_item' => $discount_per_item,
'invoice_prefix' => $invoice_prefix
]); ]);
} }
@ -85,6 +93,13 @@ class InvoicesController extends Controller
*/ */
public function store(Requests\InvoicesRequest $request) public function store(Requests\InvoicesRequest $request)
{ {
$invoice_number = explode("-",$request->invoice_number);
$number_attributes['invoice_number'] = $invoice_number[0].'-'.sprintf('%06d', intval($invoice_number[1]));
Validator::make($number_attributes, [
'invoice_number' => 'required|unique:invoices,invoice_number'
])->validate();
$invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date); $invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date);
$due_date = Carbon::createFromFormat('d/m/Y', $request->due_date); $due_date = Carbon::createFromFormat('d/m/Y', $request->due_date);
$status = Invoice::STATUS_DRAFT; $status = Invoice::STATUS_DRAFT;
@ -99,7 +114,7 @@ class InvoicesController extends Controller
$invoice = Invoice::create([ $invoice = Invoice::create([
'invoice_date' => $invoice_date, 'invoice_date' => $invoice_date,
'due_date' => $due_date, 'due_date' => $due_date,
'invoice_number' => $request->invoice_number, 'invoice_number' => $number_attributes['invoice_number'],
'reference_number' => $request->reference_number, 'reference_number' => $request->reference_number,
'user_id' => $request->user_id, 'user_id' => $request->user_id,
'company_id' => $request->header('company'), 'company_id' => $request->header('company'),
@ -222,12 +237,13 @@ class InvoicesController extends Controller
])->find($id); ])->find($id);
return response()->json([ return response()->json([
'nextInvoiceNumber' => $invoice->invoice_number, 'nextInvoiceNumber' => $invoice->getInvoiceNumAttribute(),
'invoice' => $invoice, 'invoice' => $invoice,
'invoiceTemplates' => InvoiceTemplate::all(), 'invoiceTemplates' => InvoiceTemplate::all(),
'tax_per_item' => $invoice->tax_per_item, 'tax_per_item' => $invoice->tax_per_item,
'discount_per_item' => $invoice->discount_per_item, 'discount_per_item' => $invoice->discount_per_item,
'shareable_link' => url('/invoices/pdf/'.$invoice->unique_hash) 'shareable_link' => url('/invoices/pdf/'.$invoice->unique_hash),
'invoice_prefix' => $invoice->getInvoicePrefixAttribute()
]); ]);
} }
@ -240,6 +256,13 @@ class InvoicesController extends Controller
*/ */
public function update(Requests\InvoicesRequest $request, $id) public function update(Requests\InvoicesRequest $request, $id)
{ {
$invoice_number = explode("-",$request->invoice_number);
$number_attributes['invoice_number'] = $invoice_number[0].'-'.sprintf('%06d', intval($invoice_number[1]));
Validator::make($number_attributes, [
'invoice_number' => 'required|unique:invoices,invoice_number'.','.$id
])->validate();
$invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date); $invoice_date = Carbon::createFromFormat('d/m/Y', $request->invoice_date);
$due_date = Carbon::createFromFormat('d/m/Y', $request->due_date); $due_date = Carbon::createFromFormat('d/m/Y', $request->due_date);
@ -268,7 +291,7 @@ class InvoicesController extends Controller
$invoice->invoice_date = $invoice_date; $invoice->invoice_date = $invoice_date;
$invoice->due_date = $due_date; $invoice->due_date = $due_date;
$invoice->invoice_number = $request->invoice_number; $invoice->invoice_number = $number_attributes['invoice_number'];
$invoice->reference_number = $request->reference_number; $invoice->reference_number = $request->reference_number;
$invoice->user_id = $request->user_id; $invoice->user_id = $request->user_id;
$invoice->invoice_template_id = $request->invoice_template_id; $invoice->invoice_template_id = $request->invoice_template_id;

View File

@ -202,6 +202,45 @@ class OnboardingController extends Controller
); );
} }
$invoices = [
'invoice_auto_generate' => 'YES',
'invoice_prefix' => 'INV'
];
foreach ($invoices as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$estimates = [
'estimate_prefix' => 'EST',
'estimate_auto_generate' => 'YES'
];
foreach ($estimates as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$payments = [
'payment_prefix' => 'PAY',
'payment_auto_generate' => 'YES'
];
foreach ($payments as $key => $value) {
CompanySetting::setSetting(
$key,
$value,
$user->company_id
);
}
$colors = [ $colors = [
'primary_text_color' => '#5851D8', 'primary_text_color' => '#5851D8',
'heading_text_color' => '#595959', 'heading_text_color' => '#595959',

View File

@ -10,6 +10,7 @@ use Carbon\Carbon;
use function MongoDB\BSON\toJSON; use function MongoDB\BSON\toJSON;
use Crater\User; use Crater\User;
use Crater\Http\Requests\PaymentRequest; use Crater\Http\Requests\PaymentRequest;
use Validator;
class PaymentController extends Controller class PaymentController extends Controller
{ {
@ -50,13 +51,22 @@ class PaymentController extends Controller
*/ */
public function create(Request $request) public function create(Request $request)
{ {
$nextPaymentNumber = 'PAY-'.Payment::getNextPaymentNumber(); $payment_prefix = CompanySetting::getSetting('payment_prefix', $request->header('company'));
$payment_num_auto_generate = CompanySetting::getSetting('payment_auto_generate', $request->header('company'));
$nextPaymentNumberAttribute = null;
if ($payment_num_auto_generate == "YES") {
$nextPaymentNumberAttribute = Payment::getNextPaymentNumber($payment_prefix);
}
return response()->json([ return response()->json([
'customers' => User::where('role', 'customer') 'customers' => User::where('role', 'customer')
->whereCompany($request->header('company')) ->whereCompany($request->header('company'))
->get(), ->get(),
'nextPaymentNumber' => $nextPaymentNumber 'nextPaymentNumber' => $nextPaymentNumberAttribute,
'payment_prefix' => $payment_prefix
]); ]);
} }
@ -68,6 +78,13 @@ class PaymentController extends Controller
*/ */
public function store(PaymentRequest $request) public function store(PaymentRequest $request)
{ {
$payment_number = explode("-",$request->payment_number);
$number_attributes['payment_number'] = $payment_number[0].'-'.sprintf('%06d', intval($payment_number[1]));
Validator::make($number_attributes, [
'payment_number' => 'required|unique:payments,payment_number'
])->validate();
$payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date); $payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date);
if ($request->has('invoice_id') && $request->invoice_id != null) { if ($request->has('invoice_id') && $request->invoice_id != null) {
@ -90,7 +107,7 @@ class PaymentController extends Controller
$payment = Payment::create([ $payment = Payment::create([
'payment_date' => $payment_date, 'payment_date' => $payment_date,
'payment_number' => $request->payment_number, 'payment_number' => $number_attributes['payment_number'],
'user_id' => $request->user_id, 'user_id' => $request->user_id,
'company_id' => $request->header('company'), 'company_id' => $request->header('company'),
'invoice_id' => $request->invoice_id, 'invoice_id' => $request->invoice_id,
@ -135,7 +152,8 @@ class PaymentController extends Controller
'customers' => User::where('role', 'customer') 'customers' => User::where('role', 'customer')
->whereCompany($request->header('company')) ->whereCompany($request->header('company'))
->get(), ->get(),
'nextPaymentNumber' => $payment->payment_number, 'nextPaymentNumber' => $payment->getPaymentNumAttribute(),
'payment_prefix' => $payment->getPaymentPrefixAttribute(),
'payment' => $payment, 'payment' => $payment,
'invoices' => $invoices 'invoices' => $invoices
]); ]);
@ -150,6 +168,13 @@ class PaymentController extends Controller
*/ */
public function update(PaymentRequest $request, $id) public function update(PaymentRequest $request, $id)
{ {
$payment_number = explode("-",$request->payment_number);
$number_attributes['payment_number'] = $payment_number[0].'-'.sprintf('%06d', intval($payment_number[1]));
Validator::make($number_attributes, [
'payment_number' => 'required|unique:payments,payment_number'.','.$id
])->validate();
$payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date); $payment_date = Carbon::createFromFormat('d/m/Y', $request->payment_date);
$payment = Payment::find($id); $payment = Payment::find($id);
@ -178,7 +203,7 @@ class PaymentController extends Controller
} }
$payment->payment_date = $payment_date; $payment->payment_date = $payment_date;
$payment->payment_number = $request->payment_number; $payment->payment_number = $number_attributes['payment_number'];
$payment->user_id = $request->user_id; $payment->user_id = $request->user_id;
$payment->invoice_id = $request->invoice_id; $payment->invoice_id = $request->invoice_id;
$payment->payment_mode = $request->payment_mode; $payment->payment_mode = $request->payment_mode;

View File

@ -66,10 +66,14 @@ class Invoice extends Model
'formattedDueDate' 'formattedDueDate'
]; ];
public static function getNextInvoiceNumber() public static function getNextInvoiceNumber($value)
{ {
// Get the last created order // Get the last created order
$lastOrder = Invoice::orderBy('created_at', 'desc')->first(); $lastOrder = Invoice::where('invoice_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$lastOrder) { if (!$lastOrder) {
// We get here if there is no order at all // We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end. // If there is no number set it to 0, which will be 1 at the end.
@ -143,10 +147,15 @@ class Invoice extends Model
public function getInvoiceNumAttribute() public function getInvoiceNumAttribute()
{ {
$position = $this->strposX($this->invoice_number, "-", 2) + 1; $position = $this->strposX($this->invoice_number, "-", 1) + 1;
return substr($this->invoice_number, $position); return substr($this->invoice_number, $position);
} }
public function getInvoicePrefixAttribute () {
$prefix = explode("-", $this->invoice_number)[0];
return $prefix;
}
public function getFormattedCreatedAtAttribute($value) public function getFormattedCreatedAtAttribute($value)
{ {
$dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id); $dateFormat = CompanySetting::getSetting('carbon_date_format', $this->company_id);

View File

@ -32,10 +32,34 @@ class Payment extends Model
'formattedPaymentDate' 'formattedPaymentDate'
]; ];
public static function getNextPaymentNumber()
private function strposX($haystack, $needle, $number)
{
if ($number == '1') {
return strpos($haystack, $needle);
} elseif ($number > '1') {
return strpos(
$haystack,
$needle,
$this->strposX($haystack, $needle, $number - 1) + strlen($needle)
);
} else {
return error_log('Error: Value for parameter $number is out of range');
}
}
public function getPaymentNumAttribute()
{
$position = $this->strposX($this->payment_number, "-", 1) + 1;
return substr($this->payment_number, $position);
}
public static function getNextPaymentNumber($value)
{ {
// Get the last created order // Get the last created order
$payment = Payment::orderBy('created_at', 'desc')->first(); $payment = Payment::where('payment_number', 'LIKE', $value . '-%')
->orderBy('created_at', 'desc')
->first();
if (!$payment) { if (!$payment) {
// We get here if there is no order at all // We get here if there is no order at all
// If there is no number set it to 0, which will be 1 at the end. // If there is no number set it to 0, which will be 1 at the end.
@ -54,6 +78,13 @@ class Payment extends Model
return sprintf('%06d', intval($number) + 1); return sprintf('%06d', intval($number) + 1);
} }
public function getPaymentPrefixAttribute ()
{
$prefix= explode("-",$this->payment_number)[0];
return $prefix;
}
public function invoice() public function invoice()
{ {
return $this->belongsTo(Invoice::class); return $this->belongsTo(Invoice::class);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,71 @@
<template>
<div class="base-prefix-input" @click="focusInput">
<font-awesome-icon v-if="icon" :icon="icon" class="icon" />
<p class="prefix-label"><span class="mr-1">{{ prefix }}</span>-</p>
<input
ref="basePrefixInput"
v-model="inputValue"
:type="type"
class="prefix-input-field"
@input="handleInput"
@change="handleChange"
@keyup="handleKeyupEnter"
@keydown="handleKeyDownEnter"
@blur="handleFocusOut"
>
</div>
</template>
<script>
export default {
props: {
prefix: {
type: String,
default: null,
required: true
},
icon: {
type: String,
default: null
},
value: {
type: [String, Number, File],
default: ''
},
type: {
type: String,
default: 'text'
}
},
data () {
return {
inputValue: this.value
}
},
watch: {
'value' () {
this.inputValue = this.value
}
},
methods: {
focusInput () {
this.$refs.basePrefixInput.focus()
},
handleInput (e) {
this.$emit('input', this.inputValue)
},
handleChange (e) {
this.$emit('change', this.inputValue)
},
handleKeyupEnter (e) {
this.$emit('keyup', this.inputValue)
},
handleKeyDownEnter (e) {
this.$emit('keydown', e, this.inputValue)
},
handleFocusOut (e) {
this.$emit('blur', this.inputValue)
}
}
}
</script>

View File

@ -8,6 +8,7 @@ import BaseTextArea from './BaseTextArea.vue'
import BaseSelect from './base-select/BaseSelect.vue' import BaseSelect from './base-select/BaseSelect.vue'
import BaseLoader from './BaseLoader.vue' import BaseLoader from './BaseLoader.vue'
import BaseCustomerSelect from './BaseCustomerSelect.vue' import BaseCustomerSelect from './BaseCustomerSelect.vue'
import BasePrefixInput from './BasePrefixInput.vue'
import BasePopup from './popup/BasePopup.vue' import BasePopup from './popup/BasePopup.vue'
import CustomerSelectPopup from './popup/CustomerSelectPopup.vue' import CustomerSelectPopup from './popup/CustomerSelectPopup.vue'
@ -23,6 +24,7 @@ Vue.component('base-input', BaseInput)
Vue.component('base-switch', BaseSwitch) Vue.component('base-switch', BaseSwitch)
Vue.component('base-text-area', BaseTextArea) Vue.component('base-text-area', BaseTextArea)
Vue.component('base-loader', BaseLoader) Vue.component('base-loader', BaseLoader)
Vue.component('base-prefix-input', BasePrefixInput)
Vue.component('table-component', TableComponent) Vue.component('table-component', TableComponent)
Vue.component('table-column', TableColumn) Vue.component('table-column', TableColumn)

View File

@ -124,7 +124,7 @@ export default {
}, },
percent: { percent: {
required, required,
between: between(0.10, 100) between: between(-1, 100)
}, },
description: { description: {
maxLength: maxLength(255) maxLength: maxLength(255)

View File

@ -530,6 +530,7 @@
"menu_title": { "menu_title": {
"account_settings": "Account Settings", "account_settings": "Account Settings",
"company_information": "Company Information", "company_information": "Company Information",
"customization": "Customization",
"preferences": "Preferences", "preferences": "Preferences",
"notifications": "Notifications", "notifications": "Notifications",
"tax_types": "Tax Types", "tax_types": "Tax Types",
@ -598,6 +599,67 @@
"save": "Save", "save": "Save",
"updated_message": "Company information updated successfully" "updated_message": "Company information updated successfully"
}, },
"customization": {
"customization": "customization",
"save": "Save",
"addresses": {
"title": "Addresses",
"section_description": "You can set Customer Billing Address and Customer Shipping Address Format (Displayed in PDF only). ",
"customer_billing_address": "Customer Billing Address",
"customer_shipping_address": "Customer Shipping Address",
"company_address": "Company Address",
"insert_fields": "Insert Fields",
"contact": "Contact",
"address": "Address",
"display_name": "Display Name",
"primary_contact_name": "Primary Contact Name",
"email": "Email",
"website": "Website",
"name": "Name",
"country": "Country",
"state": "State",
"city": "City",
"company_name": "Company Name",
"address_street_1": "Address Street 1",
"address_street_2": "Address Street 2",
"phone": "Phone",
"zip_code": "Zip Code",
"address_setting_updated": "Address Setting updated successfully"
},
"updated_message": "Company information updated successfully",
"invoices": {
"title": "Invoices",
"notes": "Notes",
"invoice_prefix": "Invoice Prefix",
"invoice_settings": "Invoice Settings",
"autogenerate_invoice_number": "Autogenerate Invoice Number",
"invoice_setting_description": "Disable this, If you don't wish to auto-generate invoice numbers each time you create a new invoice.",
"enter_invoice_prefix": "Enter invoice prefix",
"terms_and_conditions": "Terms and Conditions",
"invoice_setting_updated": "Invoice Setting updated successfully"
},
"estimates": {
"title": "Estimates",
"estimate_prefix": "Estimate Prefix",
"estimate_settings": "Estimate Settings",
"autogenerate_estimate_number": "Autogenerate Estimate Number",
"estimate_setting_description": "Disable this, If you don't wish to auto-generate estimate numbers each time you create a new estimate.",
"enter_estimate_prefix": "Enter estmiate prefix",
"estimate_setting_updated": "Estimate Setting updated successfully"
},
"payments": {
"title": "Payments",
"payment_prefix": "Payment Prefix",
"payment_settings": "Payment Settings",
"autogenerate_payment_number": "Autogenerate Payment Number",
"payment_setting_description": "Disable this, If you don't wish to auto-generate payment numbers each time you create a new payment.",
"enter_payment_prefix": "Enter Payment Prefix",
"payment_setting_updated": "Payment Setting updated successfully"
}
},
"account_settings": { "account_settings": {
"profile_picture": "Profile Picture", "profile_picture": "Profile Picture",
"name": "Name", "name": "Name",
@ -812,6 +874,7 @@
"maximum_options_error": "Maximum of {max} options selected. First remove a selected option to select another.", "maximum_options_error": "Maximum of {max} options selected. First remove a selected option to select another.",
"notes_maxlength": "Notes should not be greater than 255 characters.", "notes_maxlength": "Notes should not be greater than 255 characters.",
"address_maxlength": "Address should not be greater than 255 characters.", "address_maxlength": "Address should not be greater than 255 characters.",
"ref_number_maxlength": "Ref Number should not be greater than 255 characters." "ref_number_maxlength": "Ref Number should not be greater than 255 characters.",
"prefix_maxlength": "Prefix should not be greater than 5 characters."
} }
} }

View File

@ -66,6 +66,7 @@ import ReportLayout from './views/reports/layout/Index.vue'
// Settings // Settings
import SettingsLayout from './views/settings/layout/Index.vue' import SettingsLayout from './views/settings/layout/Index.vue'
import CompanyInfo from './views/settings/CompanyInfo.vue' import CompanyInfo from './views/settings/CompanyInfo.vue'
import Customization from './views/settings/Customization.vue'
import Notifications from './views/settings/Notifications.vue' import Notifications from './views/settings/Notifications.vue'
import Preferences from './views/settings/Preferences.vue' import Preferences from './views/settings/Preferences.vue'
import UserProfile from './views/settings/UserProfile.vue' import UserProfile from './views/settings/UserProfile.vue'
@ -309,6 +310,11 @@ const routes = [
name: 'company.info', name: 'company.info',
component: CompanyInfo component: CompanyInfo
}, },
{
path: 'customization',
name: 'customization',
component: Customization
},
{ {
path: 'user-profile', path: 'user-profile',
name: 'user.profile', name: 'user.profile',

View File

@ -127,14 +127,15 @@
<div class="row mt-4"> <div class="row mt-4">
<div class="col collapse-input"> <div class="col collapse-input">
<label>{{ $t('estimates.estimate_number') }}<span class="text-danger"> * </span></label> <label>{{ $t('estimates.estimate_number') }}<span class="text-danger"> * </span></label>
<base-input <base-prefix-input
:invalid="$v.newEstimate.estimate_number.$error" v-model="estimateNumAttribute"
:read-only="true" :invalid="$v.estimateNumAttribute.$error"
v-model="newEstimate.estimate_number" :prefix="estimatePrefix"
icon="hashtag" icon="hashtag"
@input="$v.newEstimate.estimate_number.$touch()" @input="$v.estimateNumAttribute.$touch()"
/> />
<span v-show="$v.newEstimate.estimate_number.$error && !$v.newEstimate.estimate_number.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span> <span v-show="$v.estimateNumAttribute.$error && !$v.estimateNumAttribute.required" class="text-danger mt-1"> {{ $tc('estimates.errors.required') }} </span>
<span v-show="!$v.estimateNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div> </div>
<div class="col collapse-input"> <div class="col collapse-input">
<label>{{ $t('estimates.ref_number') }}</label> <label>{{ $t('estimates.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid' import Guid from 'guid'
import TaxStub from '../../stub/tax' import TaxStub from '../../stub/tax'
import Tax from './EstimateTax' import Tax from './EstimateTax'
const { required, between, maxLength } = require('vuelidate/lib/validators') const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default { export default {
components: { components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null, discountPerItem: null,
initLoading: false, initLoading: false,
isLoading: false, isLoading: false,
maxDiscount: 0 maxDiscount: 0,
estimatePrefix: null,
estimateNumAttribute: null
} }
}, },
validations () { validations () {
@ -373,9 +376,6 @@ export default {
expiry_date: { expiry_date: {
required required
}, },
estimate_number: {
required
},
discount_val: { discount_val: {
between: between(0, this.subtotal) between: between(0, this.subtotal)
}, },
@ -388,6 +388,10 @@ export default {
}, },
selectedCustomer: { selectedCustomer: {
required required
},
estimateNumAttribute: {
required,
numeric
} }
} }
}, },
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency this.selectedCurrency = this.defaultCurrency
this.estimateTemplates = response.data.estimateTemplates this.estimateTemplates = response.data.estimateTemplates
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumber
} }
this.initLoading = false this.initLoading = false
return return
@ -574,8 +580,9 @@ export default {
let today = new Date() let today = new Date()
this.newEstimate.estimate_date = moment(today).toString() this.newEstimate.estimate_date = moment(today).toString()
this.newEstimate.expiry_date = moment(today).add(7, 'days').toString() this.newEstimate.expiry_date = moment(today).add(7, 'days').toString()
this.newEstimate.estimate_number = response.data.nextEstimateNumber
this.itemList = response.data.items this.itemList = response.data.items
this.estimatePrefix = response.data.estimate_prefix
this.estimateNumAttribute = response.data.nextEstimateNumber
} }
this.initLoading = false this.initLoading = false
}, },
@ -604,6 +611,7 @@ export default {
} }
this.isLoading = true this.isLoading = true
this.newEstimate.estimate_number = this.estimatePrefix + '-' + this.estimateNumAttribute
let data = { let data = {
...this.newEstimate, ...this.newEstimate,
@ -637,7 +645,11 @@ export default {
this.isLoading = false this.isLoading = false
}).catch((err) => { }).catch((err) => {
this.isLoading = false this.isLoading = false
console.log(err) if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
}) })
}, },
submitUpdate (data) { submitUpdate (data) {
@ -650,7 +662,11 @@ export default {
this.isLoading = false this.isLoading = false
}).catch((err) => { }).catch((err) => {
this.isLoading = false this.isLoading = false
console.log(err) if (err.response.data.errors.estimate_number) {
window.toastr['error'](err.response.data.errors.estimate_number)
return true
}
window.toastr['error'](err.response.data.message)
}) })
}, },
checkItemsData (index, isValid) { checkItemsData (index, isValid) {

View File

@ -127,14 +127,15 @@
<div class="row mt-4"> <div class="row mt-4">
<div class="col collapse-input"> <div class="col collapse-input">
<label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label> <label>{{ $t('invoices.invoice_number') }}<span class="text-danger"> * </span></label>
<base-input <base-prefix-input
:invalid="$v.newInvoice.invoice_number.$error" v-model="invoiceNumAttribute"
:read-only="true" :invalid="$v.invoiceNumAttribute.$error"
v-model="newInvoice.invoice_number" :prefix="invoicePrefix"
icon="hashtag" icon="hashtag"
@input="$v.newInvoice.invoice_number.$touch()" @input="$v.invoiceNumAttribute.$touch()"
/> />
<span v-show="$v.newInvoice.invoice_number.$error && !$v.newInvoice.invoice_number.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span> <span v-show="$v.invoiceNumAttribute.$error && !$v.invoiceNumAttribute.required" class="text-danger mt-1"> {{ $tc('validation.required') }} </span>
<span v-show="!$v.invoiceNumAttribute.numeric" class="text-danger mt-1"> {{ $tc('validation.numbers_only') }} </span>
</div> </div>
<div class="col collapse-input"> <div class="col collapse-input">
<label>{{ $t('invoices.ref_number') }}</label> <label>{{ $t('invoices.ref_number') }}</label>
@ -320,7 +321,7 @@ import { validationMixin } from 'vuelidate'
import Guid from 'guid' import Guid from 'guid'
import TaxStub from '../../stub/tax' import TaxStub from '../../stub/tax'
import Tax from './InvoiceTax' import Tax from './InvoiceTax'
const { required, between, maxLength } = require('vuelidate/lib/validators') const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default { export default {
components: { components: {
@ -361,7 +362,9 @@ export default {
discountPerItem: null, discountPerItem: null,
initLoading: false, initLoading: false,
isLoading: false, isLoading: false,
maxDiscount: 0 maxDiscount: 0,
invoicePrefix: null,
invoiceNumAttribute: null
} }
}, },
validations () { validations () {
@ -373,9 +376,6 @@ export default {
due_date: { due_date: {
required required
}, },
invoice_number: {
required
},
discount_val: { discount_val: {
between: between(0, this.subtotal) between: between(0, this.subtotal)
}, },
@ -388,6 +388,10 @@ export default {
}, },
selectedCustomer: { selectedCustomer: {
required required
},
invoiceNumAttribute: {
required,
numeric
} }
} }
}, },
@ -559,6 +563,8 @@ export default {
this.taxPerItem = response.data.tax_per_item this.taxPerItem = response.data.tax_per_item
this.selectedCurrency = this.defaultCurrency this.selectedCurrency = this.defaultCurrency
this.invoiceTemplates = response.data.invoiceTemplates this.invoiceTemplates = response.data.invoiceTemplates
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumber
} }
this.initLoading = false this.initLoading = false
return return
@ -574,8 +580,9 @@ export default {
let today = new Date() let today = new Date()
this.newInvoice.invoice_date = moment(today).toString() this.newInvoice.invoice_date = moment(today).toString()
this.newInvoice.due_date = moment(today).add(7, 'days').toString() this.newInvoice.due_date = moment(today).add(7, 'days').toString()
this.newInvoice.invoice_number = response.data.nextInvoiceNumber
this.itemList = response.data.items this.itemList = response.data.items
this.invoicePrefix = response.data.invoice_prefix
this.invoiceNumAttribute = response.data.nextInvoiceNumber
} }
this.initLoading = false this.initLoading = false
}, },
@ -604,6 +611,7 @@ export default {
} }
this.isLoading = true this.isLoading = true
this.newInvoice.invoice_number = this.invoicePrefix + '-' + this.invoiceNumAttribute
let data = { let data = {
...this.newInvoice, ...this.newInvoice,
@ -637,6 +645,10 @@ export default {
this.isLoading = false this.isLoading = false
}).catch((err) => { }).catch((err) => {
this.isLoading = false this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err) console.log(err)
}) })
}, },
@ -653,6 +665,10 @@ export default {
} }
}).catch((err) => { }).catch((err) => {
this.isLoading = false this.isLoading = false
if (err.response.data.errors.invoice_number) {
window.toastr['error'](err.response.data.errors.invoice_number)
return true
}
console.log(err) console.log(err)
}) })
}, },

View File

@ -40,16 +40,15 @@
<div class="col-sm-6"> <div class="col-sm-6">
<div class="form-group"> <div class="form-group">
<label class="form-label">{{ $t('payments.payment_number') }}</label><span class="text-danger"> *</span> <label class="form-label">{{ $t('payments.payment_number') }}</label><span class="text-danger"> *</span>
<base-input <base-prefix-input
:invalid="$v.formData.payment_number.$error" :invalid="$v.paymentNumAttribute.$error"
v-model.trim="formData.payment_number" v-model.trim="paymentNumAttribute"
read-only :prefix="paymentPrefix"
type="text" @input="$v.paymentNumAttribute.$touch()"
name="email"
@input="$v.formData.payment_number.$touch()"
/> />
<div v-if="$v.formData.payment_number.$error"> <div v-if="$v.paymentNumAttribute.$error">
<span v-if="!$v.formData.payment_number.required" class="text-danger">{{ $tc('validation.required') }}</span> <span v-if="!$v.paymentNumAttribute.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.paymentNumAttribute.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -155,7 +154,7 @@ import { mapActions, mapGetters } from 'vuex'
import MultiSelect from 'vue-multiselect' import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate' import { validationMixin } from 'vuelidate'
import moment from 'moment' import moment from 'moment'
const { required, between, maxLength } = require('vuelidate/lib/validators') const { required, between, maxLength, numeric } = require('vuelidate/lib/validators')
export default { export default {
components: { MultiSelect }, components: { MultiSelect },
@ -184,7 +183,9 @@ export default {
invoiceList: [], invoiceList: [],
isLoading: false, isLoading: false,
maxPayableAmount: Number.MAX_SAFE_INTEGER, maxPayableAmount: Number.MAX_SAFE_INTEGER,
isSettingInitialData: true isSettingInitialData: true,
paymentNumAttribute: null,
paymentPrefix: ''
} }
}, },
validations () { validations () {
@ -193,9 +194,6 @@ export default {
required required
}, },
formData: { formData: {
payment_number: {
required
},
payment_date: { payment_date: {
required required
}, },
@ -206,6 +204,10 @@ export default {
notes: { notes: {
maxLength: maxLength(255) maxLength: maxLength(255)
} }
},
paymentNumAttribute: {
required,
numeric
} }
} }
}, },
@ -297,6 +299,8 @@ export default {
this.customer = response.data.payment.user this.customer = response.data.payment.user
this.formData.payment_date = moment(response.data.payment.payment_date, 'YYYY-MM-DD').toString() this.formData.payment_date = moment(response.data.payment.payment_date, 'YYYY-MM-DD').toString()
this.formData.amount = parseFloat(response.data.payment.amount) this.formData.amount = parseFloat(response.data.payment.amount)
this.paymentPrefix = response.data.payment_prefix
this.paymentNumAttribute = response.data.nextPaymentNumber
if (response.data.payment.invoice !== null) { if (response.data.payment.invoice !== null) {
this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount) this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount)
this.invoice = response.data.payment.invoice this.invoice = response.data.payment.invoice
@ -305,7 +309,8 @@ export default {
} else { } else {
let response = await this.fetchCreatePayment() let response = await this.fetchCreatePayment()
this.customerList = response.data.customers this.customerList = response.data.customers
this.formData.payment_number = response.data.nextPaymentNumber this.paymentNumAttribute = response.data.nextPaymentNumber
this.paymentPrefix = response.data.payment_prefix
this.formData.payment_date = moment(new Date()).toString() this.formData.payment_date = moment(new Date()).toString()
} }
return true return true
@ -332,6 +337,9 @@ export default {
if (this.$v.$invalid) { if (this.$v.$invalid) {
return true return true
} }
this.formData.payment_number = this.paymentPrefix + '-' + this.paymentNumAttribute
if (this.isEdit) { if (this.isEdit) {
let data = { let data = {
editData: { editData: {
@ -340,35 +348,53 @@ export default {
}, },
id: this.$route.params.id id: this.$route.params.id
} }
let response = await this.updatePayment(data) try {
if (response.data.success) { let response = await this.updatePayment(data)
window.toastr['success'](this.$t('payments.updated_message')) if (response.data.success) {
this.$router.push('/admin/payments') window.toastr['success'](this.$t('payments.updated_message'))
return true this.$router.push('/admin/payments')
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
} }
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} else { } else {
let data = { let data = {
...this.formData, ...this.formData,
payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY') payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY')
} }
this.isLoading = true this.isLoading = true
let response = await this.addPayment(data) try {
if (response.data.success) { let response = await this.addPayment(data)
window.toastr['success'](this.$t('payments.created_message')) if (response.data.success) {
this.$router.push('/admin/payments') window.toastr['success'](this.$t('payments.created_message'))
this.isLoading = true this.$router.push('/admin/payments')
return true this.isLoading = true
return true
}
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} catch (err) {
this.isLoading = false
if (err.response.data.errors.payment_number) {
window.toastr['error'](err.response.data.errors.payment_number)
return true
}
window.toastr['error'](err.response.data.message)
} }
if (response.data.error === 'invalid_amount') {
window.toastr['error'](this.$t('invalid_amount_message'))
return false
}
window.toastr['error'](response.data.error)
} }
} }
} }

View File

@ -0,0 +1,385 @@
<template>
<div class="setting-main-container customization">
<div class="card setting-card">
<ul class="tabs">
<li class="tab" @click="setActiveTab('INVOICES')">
<a :class="['tab-link', {'a-active': activeTab === 'INVOICES'}]" href="#">{{ $t('settings.customization.invoices.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('ESTIMATES')">
<a :class="['tab-link', {'a-active': activeTab === 'ESTIMATES'}]" href="#">{{ $t('settings.customization.estimates.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('PAYMENTS')">
<a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.title') }}</a>
</li>
</ul>
<!-- Invoices Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'INVOICES'" class="invoice-tab">
<form action="" class="form-section" @submit.prevent="updateInvoiceSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label>
<base-input
v-model="invoices.invoice_prefix"
:invalid="$v.invoices.invoice_prefix.$error"
class="prefix-input"
@input="$v.invoices.invoice_prefix.$touch()"
@keyup="changeToUppercase('INVOICES')"
/>
<span v-show="!$v.invoices.invoice_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.invoices.invoice_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.invoices.invoice_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
<hr>
</form>
<div class="col-md-12 mt-3">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.invoices.invoice_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="invoiceAutogenerate"
class="btn-switch"
@change="setInvoiceSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.invoices.autogenerate_invoice_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.invoices.invoice_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
<!-- Estimates Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'ESTIMATES'" class="estimate-tab">
<form action="" class="form-section" @submit.prevent="updateEstimateSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label>
<base-input
v-model="estimates.estimate_prefix"
:invalid="$v.estimates.estimate_prefix.$error"
class="prefix-input"
@input="$v.estimates.estimate_prefix.$touch()"
@keyup="changeToUppercase('ESTIMATES')"
/>
<span v-show="!$v.estimates.estimate_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.estimates.estimate_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.estimates.estimate_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
<hr>
</form>
<div class="col-md-12 mt-3">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.estimates.estimate_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="estimateAutogenerate"
class="btn-switch"
@change="setEstimateSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.estimates.autogenerate_estimate_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.estimates.estimate_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
<!-- Payments Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'PAYMENTS'" class="payment-tab">
<form action="" class="form-section" @submit.prevent="updatePaymentSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label>
<base-input
v-model="payments.payment_prefix"
:invalid="$v.payments.payment_prefix.$error"
class="prefix-input"
@input="$v.payments.payment_prefix.$touch()"
@keyup="changeToUppercase('PAYMENTS')"
/>
<span v-show="!$v.payments.payment_prefix.required" class="text-danger mt-1">{{ $t('validation.required') }}</span>
<span v-if="!$v.payments.payment_prefix.maxLength" class="text-danger">{{ $t('validation.prefix_maxlength') }}</span>
<span v-if="!$v.payments.payment_prefix.alpha" class="text-danger">{{ $t('validation.characters_only') }}</span>
</div>
</div>
<div class="row mb-3">
<div class="col-md-12">
<base-button
icon="save"
color="theme"
type="submit"
>
{{ $t('settings.customization.save') }}
</base-button>
</div>
</div>
</form>
<hr>
<div class="col-md-12 mt-4">
<div class="page-header">
<h3 class="page-title">
{{ $t('settings.customization.payments.payment_settings') }}
</h3>
<div class="flex-box">
<div class="left">
<base-switch
v-model="paymentAutogenerate"
class="btn-switch"
@change="setPaymentSetting"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.customization.payments.autogenerate_payment_number') }} </p>
<p class="box-desc"> {{ $t('settings.customization.payments.payment_setting_description') }} </p>
</div>
</div>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { validationMixin } from 'vuelidate'
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
activeTab: 'INVOICES',
invoiceAutogenerate: false,
estimateAutogenerate: false,
paymentAutogenerate: false,
invoices: {
invoice_prefix: null,
invoice_notes: null,
invoice_terms_and_conditions: null
},
estimates: {
estimate_prefix: null,
estimate_notes: null,
estimate_terms_and_conditions: null
},
payments: {
payment_prefix: null
},
currentData: null
}
},
watch: {
activeTab () {
this.loadData()
}
},
validations: {
invoices: {
invoice_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
estimates: {
estimate_prefix: {
required,
maxLength: maxLength(5),
alpha
}
},
payments: {
payment_prefix: {
required,
maxLength: maxLength(5),
alpha
}
}
},
created () {
this.loadData()
},
methods: {
async setInvoiceSetting () {
let data = {
key: 'invoice_auto_generate',
value: this.invoiceAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async setEstimateSetting () {
let data = {
key: 'estimate_auto_generate',
value: this.estimateAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
changeToUppercase (currentTab) {
if (currentTab === 'INVOICES') {
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()
return true
}
if (currentTab === 'ESTIMATES') {
this.estimates.estimate_prefix = this.estimates.estimate_prefix.toUpperCase()
return true
}
if (currentTab === 'PAYMENTS') {
this.payments.payment_prefix = this.payments.payment_prefix.toUpperCase()
return true
}
},
async setPaymentSetting () {
let data = {
key: 'payment_auto_generate',
value: this.paymentAutogenerate ? 'YES' : 'NO'
}
let response = await window.axios.put('/api/settings/update-setting', data)
if (response.data) {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async loadData () {
let res = await window.axios.get('/api/settings/get-customize-setting')
if (res.data) {
this.invoices.invoice_prefix = res.data.invoice_prefix
this.invoices.invoice_notes = res.data.invoice_notes
this.invoices.invoice_terms_and_conditions = res.data.invoice_terms_and_conditions
this.estimates.estimate_prefix = res.data.estimate_prefix
this.estimates.estimate_notes = res.data.estimate_notes
this.estimates.estimate_terms_and_conditions = res.data.estimate_terms_and_conditions
this.payments.payment_prefix = res.data.payment_prefix
if (res.data.invoice_auto_generate === 'YES') {
this.invoiceAutogenerate = true
} else {
this.invoiceAutogenerate = false
}
if (res.data.estimate_auto_generate === 'YES') {
this.estimateAutogenerate = true
} else {
this.estimateAutogenerate = false
}
if (res.data.payment_auto_generate === 'YES') {
this.paymentAutogenerate = true
} else {
this.paymentAutogenerate = false
}
}
},
async updateInvoiceSetting () {
this.$v.invoices.$touch()
if (this.$v.invoices.$invalid) {
return false
}
let data = {type: 'INVOICES', ...this.invoices}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.invoices.invoice_setting_updated'))
}
},
async updateEstimateSetting () {
this.$v.estimates.$touch()
if (this.$v.estimates.$invalid) {
return false
}
let data = {type: 'ESTIMATES', ...this.estimates}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.estimates.estimate_setting_updated'))
}
},
async updatePaymentSetting () {
this.$v.payments.$touch()
if (this.$v.payments.$invalid) {
return false
}
let data = {type: 'PAYMENTS', ...this.payments}
if (this.updateSetting(data)) {
window.toastr['success'](this.$t('settings.customization.payments.payment_setting_updated'))
}
},
async updateSetting (data) {
let res = await window.axios.put('/api/settings/update-customize-setting', data)
if (res.data.success) {
return true
}
return false
},
setActiveTab (val) {
this.activeTab = val
}
}
}
</script>
<style>
.fade-customize-enter-active {
transition: opacity 0.9s;
}
.fade-customize-leave-active {
transition: opacity 0s;
}
.fade-customize-enter, .fade-customize-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@ -45,6 +45,12 @@ export default {
icon: 'building', icon: 'building',
iconType: 'far' iconType: 'far'
}, },
{
link: '/admin/settings/customization',
title: 'settings.menu_title.customization',
icon: 'edit',
iconType: 'fa'
},
{ {
link: '/admin/settings/preferences', link: '/admin/settings/preferences',
title: 'settings.menu_title.preferences', title: 'settings.menu_title.preferences',

View File

@ -0,0 +1,54 @@
.base-prefix-input {
display: flex;
position: relative;
width: 100%;
height: 40px;
padding: 2px 2px;
flex-direction: row;
background: #FFFFFF;
border: 1px solid $ls-color-gray--light;
border-radius: 5px;
.icon {
width: 13px;
height: 18px;
color: $ls-color-gray;
font-style: normal;
font-weight: 900;
font-size: 14px;
line-height: 16px;
margin-top: 17px;
margin-left: 20px;
z-index: 1;
transform: translate(-50%,-50%);
}
p {
padding: 0 0 0 0;
margin: 0 0 0 0;
}
.prefix-label {
display: flex;
height: 18px;
color: #55547A;
font-weight: 500;
font-size: 14px;
line-height: 16px;
padding: 9px 2px 9px 10px;
}
.prefix-input-field {
width: 100%;
padding: 8px 13px;
padding-left: 1px;
text-align: left;
background: #FFFFFF;
border: none;
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 21px;
}
}

View File

@ -48,6 +48,7 @@
@import 'components/base/base-text-area'; @import 'components/base/base-text-area';
@import "components/base/base-switch"; @import "components/base/base-switch";
@import 'components/base/base-loader/index'; @import 'components/base/base-loader/index';
@import 'components/base/base-prefix-input';
// Components // Components
@ -91,6 +92,7 @@
@import 'pages/login'; @import 'pages/login';
@import 'pages/login-3'; @import 'pages/login-3';
@import 'pages/404'; @import 'pages/404';
@import 'pages/customization';
@import 'pages/settings'; @import 'pages/settings';
@import 'pages/invoices/create'; @import 'pages/invoices/create';
@import 'pages/invoices/view'; @import 'pages/invoices/view';

View File

@ -0,0 +1,39 @@
.customization {
.prefix-input {
max-width: 30%;
}
.form-section {
padding: 8px 15px;
}
.invoice-customization-card {
border: 1px solid #EBF1FA;border-radius: 5px;
}
@media (max-width: $x-small-breakpoint) {
.address-customization-card {
.address-fields-container {
display: flex;
flex-wrap: wrap;
.fields-list {
border-right: 0px;
}
}
}
.tabs {
.tab {
padding: 10px 10px;
}
}
}
}

View File

@ -348,6 +348,16 @@ Route::group(['middleware' => 'api'], function () {
'uses' => 'CompanyController@updateSetting' 'uses' => 'CompanyController@updateSetting'
]); ]);
Route::get('/get-customize-setting', [
'as' => 'admin.get.customize.setting',
'uses' => 'CompanyController@getCustomizeSetting'
]);
Route::put('/update-customize-setting', [
'as' => 'admin.update.customize.setting',
'uses' => 'CompanyController@updateCustomizeSetting'
]);
Route::get('/environment/mail', [ Route::get('/environment/mail', [
'as' => 'admin.environment.mail', 'as' => 'admin.environment.mail',
'uses' => 'EnvironmentController@getMailDrivers' 'uses' => 'EnvironmentController@getMailDrivers'