diff --git a/app/Http/Requests/CompaniesRequest.php b/app/Http/Requests/CompaniesRequest.php
index 5394592b..661d8777 100644
--- a/app/Http/Requests/CompaniesRequest.php
+++ b/app/Http/Requests/CompaniesRequest.php
@@ -3,7 +3,6 @@
namespace Crater\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
-use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
class CompaniesRequest extends FormRequest
@@ -34,6 +33,10 @@ class CompaniesRequest extends FormRequest
'currency' => [
'required'
],
+ 'slug' => [
+ 'required',
+ Rule::unique('companies')
+ ],
'address.name' => [
'nullable',
],
@@ -68,11 +71,11 @@ class CompaniesRequest extends FormRequest
{
return collect($this->validated())
->only([
- 'name'
+ 'name',
+ 'slug'
])
->merge([
- 'owner_id' => $this->user()->id,
- 'slug' => Str::slug($this->name)
+ 'owner_id' => $this->user()->id
])
->toArray();
}
diff --git a/app/Http/Requests/CompanyRequest.php b/app/Http/Requests/CompanyRequest.php
index c86cd645..dc22b1ae 100644
--- a/app/Http/Requests/CompanyRequest.php
+++ b/app/Http/Requests/CompanyRequest.php
@@ -30,7 +30,8 @@ class CompanyRequest extends FormRequest
Rule::unique('companies')->ignore($this->header('company'), 'id'),
],
'slug' => [
- 'nullable'
+ 'required',
+ Rule::unique('companies')->ignore($this->header('company'), 'id'),
],
'address.country_id' => [
'required',
diff --git a/app/Models/Company.php b/app/Models/Company.php
index bf87820b..a0d9fddb 100644
--- a/app/Models/Company.php
+++ b/app/Models/Company.php
@@ -217,7 +217,7 @@ class Company extends Model implements HasMedia
'estimate_billing_address_format' => $billingAddressFormat,
'payment_company_address_format' => $companyAddressFormat,
'payment_from_customer_address_format' => $paymentFromCustomerAddress,
- 'currency' => request()->currency ?? 13,
+ 'currency' => request()->currency ?? 1,
'time_zone' => 'Asia/Kolkata',
'language' => 'en',
'fiscal_year' => '1-12',
diff --git a/resources/scripts/admin/components/modal-components/CompanyModal.vue b/resources/scripts/admin/components/modal-components/CompanyModal.vue
index 019a065c..011218a1 100644
--- a/resources/scripts/admin/components/modal-components/CompanyModal.vue
+++ b/resources/scripts/admin/components/modal-components/CompanyModal.vue
@@ -48,6 +48,24 @@
/>
+
+
+
+
import { useModalStore } from '@/scripts/stores/modal'
-import { computed, onMounted, ref, reactive } from 'vue'
+import { computed, onMounted, ref, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { required, minLength, helpers } from '@vuelidate/validators'
import { useVuelidate } from '@vuelidate/core'
@@ -152,6 +170,7 @@ let companyLogoName = ref(null)
const newCompanyForm = reactive({
name: null,
+ slug: null,
currency: '',
address: {
country_id: null,
@@ -162,6 +181,9 @@ const modalActive = computed(() => {
return modalStore.active && modalStore.componentName === 'CompanyModal'
})
+const slugValidator = (value) => {
+ return value == slugify(value)
+}
const rules = {
newCompanyForm: {
name: {
@@ -171,6 +193,17 @@ const rules = {
minLength(3)
),
},
+ slug: {
+ required: helpers.withMessage(t('validation.required'), required),
+ minLength: helpers.withMessage(
+ t('validation.name_min_length', { count: 3 }),
+ minLength(3)
+ ),
+ slugValidator: helpers.withMessage(
+ t('validation.invalid_slug'),
+ slugValidator
+ ),
+ },
address: {
country_id: {
required: helpers.withMessage(t('validation.required'), required),
@@ -243,6 +276,7 @@ async function submitCompanyData() {
function resetNewCompanyForm() {
newCompanyForm.name = ''
+ newCompanyForm.slug = ''
newCompanyForm.currency = ''
newCompanyForm.address.country_id = ''
@@ -257,4 +291,24 @@ function closeCompanyModal() {
v$.value.$reset()
}, 300)
}
+
+// watcher for if change company name then auto fill company slug value
+watch(
+ () => newCompanyForm.name,
+ (currentValue) => {
+ newCompanyForm.slug = slugify(currentValue)
+ }
+)
+
+function slugify(string) {
+ return string
+ .toString()
+ .trim()
+ .toLowerCase()
+ .replace(/\s+/g, '-')
+ .replace(/[^\w\-]+/g, '')
+ .replace(/\-\-+/g, '-')
+ .replace(/^-+/, '')
+ .replace(/-+$/, '')
+}
diff --git a/resources/scripts/admin/stores/company.js b/resources/scripts/admin/stores/company.js
index 1e10489b..f5462ae0 100644
--- a/resources/scripts/admin/stores/company.js
+++ b/resources/scripts/admin/stores/company.js
@@ -184,6 +184,20 @@ export const useCompanyStore = (useWindow = false) => {
setDefaultCurrency(data) {
this.defaultCurrency = data.currency
},
+
+ checkCompanyHasCurrencyTransactions() {
+ return new Promise((resolve, reject) => {
+ axios
+ .get(`/api/v1/company/has-transactions`)
+ .then((response) => {
+ resolve(response)
+ })
+ .catch((err) => {
+ handleError(err)
+ reject(err)
+ })
+ })
+ },
},
})()
}
diff --git a/resources/scripts/admin/views/installation/Step7CompanyInfo.vue b/resources/scripts/admin/views/installation/Step7CompanyInfo.vue
index 3890ccbd..9cd2f0c4 100644
--- a/resources/scripts/admin/views/installation/Step7CompanyInfo.vue
+++ b/resources/scripts/admin/views/installation/Step7CompanyInfo.vue
@@ -34,6 +34,24 @@
/>
+
+
+
+
-
-
diff --git a/resources/scripts/admin/views/settings/CompanyInfoSettings.vue b/resources/scripts/admin/views/settings/CompanyInfoSettings.vue
index 357b7dd6..e55880e5 100644
--- a/resources/scripts/admin/views/settings/CompanyInfoSettings.vue
+++ b/resources/scripts/admin/views/settings/CompanyInfoSettings.vue
@@ -28,6 +28,19 @@
/>
+
+
+
+
@@ -160,6 +173,7 @@ let isSaving = ref(false)
const companyForm = reactive({
name: null,
+ slug: null,
logo: null,
address: {
address_street_1: '',
@@ -193,7 +207,14 @@ const rules = computed(() => {
name: {
required: helpers.withMessage(t('validation.required'), required),
minLength: helpers.withMessage(
- t('validation.name_min_length'),
+ t('validation.name_min_length', { count: 3 }),
+ minLength(3)
+ ),
+ },
+ slug: {
+ required: helpers.withMessage(t('validation.required'), required),
+ minLength: helpers.withMessage(
+ t('validation.name_min_length', { count: 3 }),
minLength(3)
),
},
diff --git a/resources/scripts/admin/views/settings/PreferencesSetting.vue b/resources/scripts/admin/views/settings/PreferencesSetting.vue
index 6c46f645..531cb83d 100644
--- a/resources/scripts/admin/views/settings/PreferencesSetting.vue
+++ b/resources/scripts/admin/views/settings/PreferencesSetting.vue
@@ -8,7 +8,11 @@
@@ -21,7 +25,7 @@
:searchable="true"
track-by="name"
:invalid="v$.currency.$error"
- disabled
+ :disabled="isCurrencyDisabled"
class="w-full"
>
@@ -187,6 +191,7 @@ const { t, tm } = useI18n()
let isSaving = ref(false)
let isDataSaving = ref(false)
let isFetchingInitialData = ref(false)
+let isCurrencyDisabled = ref(true)
const settingsForm = reactive({ ...companyStore.selectedCompanySettings })
@@ -282,10 +287,14 @@ setInitialData()
async function setInitialData() {
isFetchingInitialData.value = true
Promise.all([
+ companyStore.checkCompanyHasCurrencyTransactions(),
globalStore.fetchCurrencies(),
globalStore.fetchDateFormats(),
globalStore.fetchTimeZones(),
]).then(([res1]) => {
+ if (res1.data?.has_transactions == false) {
+ isCurrencyDisabled.value = false
+ }
isFetchingInitialData.value = false
})
}
diff --git a/resources/scripts/locales/en.json b/resources/scripts/locales/en.json
index 5c224428..a462b9fb 100644
--- a/resources/scripts/locales/en.json
+++ b/resources/scripts/locales/en.json
@@ -863,6 +863,8 @@
"company_info": {
"company_info": "Company info",
"company_name": "Company Name",
+ "company_slug": "Company Slug",
+ "company_slug_help_text": "A unique URL friendly name for your company (It will appear on Customer Portal URL)",
"company_logo": "Company Logo",
"section_description": "Information about your company that will be displayed on invoices, estimates and other documents created by Crater.",
"phone": "Phone",
@@ -1324,6 +1326,8 @@
"company_info": "Company Information",
"company_info_desc": "This information will be displayed on invoices. Note that you can edit this later on settings page.",
"company_name": "Company Name",
+ "company_slug": "Company Slug",
+ "company_slug_help_text": "A unique URL friendly name for your company (It will appear on Customer Portal URL)",
"company_logo": "Company Logo",
"logo_preview": "Logo Preview",
"preferences": "Company Preferences",
@@ -1454,7 +1458,8 @@
"at_least_one_ability": "Please select atleast one Permission.",
"valid_driver_key": "Please enter a valid {driver} key.",
"valid_exchange_rate": "Please enter a valid exchange rate.",
- "company_name_not_same": "Company name must match with given name."
+ "company_name_not_same": "Company name must match with given name.",
+ "invalid_slug": "Invalid Slug"
},
"errors": {
"starter_plan": "This feature is available on Starter plan and onwards!",
@@ -1523,4 +1528,4 @@
"pdf_ship_to": "Ship to,",
"pdf_received_from": "Received from:",
"pdf_tax_label": "Tax"
-}
+}
\ No newline at end of file
diff --git a/tests/Feature/Admin/EstimateTest.php b/tests/Feature/Admin/EstimateTest.php
index c726c043..0a06f83d 100644
--- a/tests/Feature/Admin/EstimateTest.php
+++ b/tests/Feature/Admin/EstimateTest.php
@@ -415,32 +415,31 @@ test('update estimate with EUR currency', function () {
$response = putJson('api/v1/estimates/'.$estimate->id, $estimate2);
- $this->assertDatabaseHas('estimates', [
- 'id' => $estimate['id'],
- 'template_name' => $estimate2['template_name'],
- 'estimate_number' => $estimate2['estimate_number'],
- 'discount_type' => $estimate2['discount_type'],
- 'discount_val' => $estimate2['discount_val'],
- 'sub_total' => $estimate2['sub_total'],
- 'discount' => $estimate2['discount'],
- 'customer_id' => $estimate2['customer_id'],
- 'total' => $estimate2['total'],
- 'tax' => $estimate2['tax'],
- 'exchange_rate' => $estimate2['exchange_rate'],
- 'base_discount_val' => $estimate2['base_discount_val'],
- 'base_sub_total' => $estimate2['base_sub_total'],
- 'base_total' => $estimate2['base_total'],
- 'base_tax' => $estimate2['base_tax'],
- ]);
+ $estimate_assert = collect($estimate2)
+ ->only([
+ 'id',
+ 'template_name',
+ 'estimate_number',
+ 'discount_type',
+ 'discount_val',
+ 'sub_total',
+ 'discount',
+ 'customer_id',
+ 'total',
+ 'tax'
+ ])
+ ->toArray();
- $this->assertDatabaseHas('estimate_items', [
- 'estimate_id' => $estimate2['items'][0]['estimate_id'],
- 'exchange_rate' => $estimate2['items'][0]['exchange_rate'],
- 'base_price' => $estimate2['items'][0]['base_price'],
- 'base_discount_val' => $estimate2['items'][0]['base_discount_val'],
- 'base_tax' => $estimate2['items'][0]['base_tax'],
- 'base_total' => $estimate2['items'][0]['base_total'],
- ]);
+ $this->assertDatabaseHas('estimates', $estimate_assert);
+
+ $estimate_item_assert = collect($estimate2['items'][0])
+ ->only([
+ 'estimate_id',
+ 'amount'
+ ])
+ ->toArray();
+
+ $this->assertDatabaseHas('estimate_items', $estimate_item_assert);
$response->assertStatus(200);
});
diff --git a/tests/Feature/Admin/ExpenseTest.php b/tests/Feature/Admin/ExpenseTest.php
index 0e46c0f4..38a5c8dd 100644
--- a/tests/Feature/Admin/ExpenseTest.php
+++ b/tests/Feature/Admin/ExpenseTest.php
@@ -37,13 +37,15 @@ test('create expense', function () {
postJson('api/v1/expenses', $expense)->assertStatus(201);
- $this->assertDatabaseHas('expenses', [
- 'notes' => $expense['notes'],
- 'expense_category_id' => $expense['expense_category_id'],
- 'amount' => $expense['amount'],
- 'exchange_rate' => $expense['exchange_rate'],
- 'base_amount' => $expense['base_amount'],
- ]);
+ $expense = collect($expense)
+ ->only([
+ 'notes',
+ 'expense_category_id',
+ 'amount'
+ ])
+ ->toArray();
+
+ $this->assertDatabaseHas('expenses', $expense);
});
test('store validates using a form request', function () {
@@ -146,11 +148,13 @@ test('update expense with EUR currency', function () {
putJson('api/v1/expenses/'.$expense->id, $expense2)->assertOk();
- $this->assertDatabaseHas('expenses', [
- 'id' => $expense->id,
- 'expense_category_id' => $expense2['expense_category_id'],
- 'amount' => $expense2['amount'],
- 'exchange_rate' => $expense2['exchange_rate'],
- 'base_amount' => $expense2['base_amount'],
- ]);
+ $expense2 = collect($expense2)
+ ->only([
+ 'id',
+ 'expense_category_id',
+ 'amount'
+ ])
+ ->toArray();
+
+ $this->assertDatabaseHas('expenses', $expense2);
});
diff --git a/tests/Feature/Admin/InvoiceTest.php b/tests/Feature/Admin/InvoiceTest.php
index ef8a716e..dce605a5 100644
--- a/tests/Feature/Admin/InvoiceTest.php
+++ b/tests/Feature/Admin/InvoiceTest.php
@@ -9,7 +9,6 @@ use Crater\Models\Tax;
use Crater\Models\User;
use Illuminate\Support\Facades\Artisan;
use Laravel\Sanctum\Sanctum;
-
use function Pest\Laravel\getJson;
use function Pest\Laravel\postJson;
use function Pest\Laravel\putJson;
@@ -431,31 +430,36 @@ test('update invoice with EUR currency', function () {
putJson('api/v1/invoices/'.$invoice->id, $invoice2)->assertOk();
- $this->assertDatabaseHas('invoices', [
- 'id' => $invoice['id'],
- 'invoice_number' => $invoice2['invoice_number'],
- 'sub_total' => $invoice2['sub_total'],
- 'total' => $invoice2['total'],
- 'tax' => $invoice2['tax'],
- 'discount' => $invoice2['discount'],
- 'customer_id' => $invoice2['customer_id'],
- 'template_name' => $invoice2['template_name'],
- 'exchange_rate' => $invoice2['exchange_rate'],
- 'base_total' => $invoice2['base_total'],
- ]);
+ $invoice_assert = collect($invoice2)
+ ->only([
+ 'invoice_number',
+ 'template_name',
+ 'sub_total',
+ 'total',
+ 'tax',
+ 'discount',
+ 'customer_id',
+ ])
+ ->toArray();
- $this->assertDatabaseHas('invoice_items', [
- 'invoice_id' => $invoice2['items'][0]['invoice_id'],
- 'item_id' => $invoice2['items'][0]['item_id'],
- 'name' => $invoice2['items'][0]['name'],
- 'exchange_rate' => $invoice2['items'][0]['exchange_rate'],
- 'base_price' => $invoice2['items'][0]['base_price'],
- 'base_total' => $invoice2['items'][0]['base_total'],
- ]);
+ $this->assertDatabaseHas('invoices', $invoice_assert);
- $this->assertDatabaseHas('taxes', [
- 'amount' => $invoice2['taxes'][0]['amount'],
- 'name' => $invoice2['taxes'][0]['name'],
- 'base_amount' => $invoice2['taxes'][0]['base_amount'],
- ]);
+ $invoice_item_assert = collect($invoice2['items'][0])
+ ->only([
+ 'invoice_id',
+ 'item_id',
+ 'name',
+ ])
+ ->toArray();
+
+ $this->assertDatabaseHas('invoice_items', $invoice_item_assert);
+
+ $invoice_tax_assert = collect($invoice2['taxes'][0])
+ ->only([
+ 'name',
+ 'amount'
+ ])
+ ->toArray();
+
+ $this->assertDatabaseHas('taxes', $invoice_tax_assert);
});