fix conflicts

This commit is contained in:
satyaprakash10
2019-11-12 19:26:43 +05:30
56 changed files with 2428 additions and 508 deletions

View File

@ -35,6 +35,8 @@
<div v-if="$v.formData.price.$error">
<span v-if="!$v.formData.price.required" class="text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.formData.price.numeric" class="text-danger">{{ $tc('validation.numbers_only') }}</span>
<span v-if="!$v.formData.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span>
<span v-if="!$v.formData.price.minValue" class="text-danger">{{ $t('validation.price_minvalue') }}</span>
</div>
</div>
</div>
@ -138,7 +140,8 @@ export default {
price: {
required,
numeric,
minValue: minValue(0.1)
minValue: minValue(0.1),
maxLength: maxLength(10)
},
description: {
maxLength: maxLength(255)

View File

@ -9,6 +9,7 @@
<script>
import Chart from 'chart.js'
import Utils from '../../helpers/utilities'
import { mapGetters } from 'vuex'
export default {
props: {
@ -46,9 +47,20 @@ export default {
type: Function,
require: false,
default: Function
},
FormatGraphMoney: {
type: Function,
require: false,
default: Function
}
},
computed: {
...mapGetters('currency', [
'defaultCurrency'
])
},
watch: {
labels (val) {
this.update()
@ -56,6 +68,7 @@ export default {
},
mounted () {
let self = this
let context = this.$refs.graph.getContext('2d')
let options = {
responsive: true,
@ -64,7 +77,7 @@ export default {
enabled: true,
callbacks: {
label: function (tooltipItem, data) {
return Utils.formatGraphMoney(tooltipItem.value)
return self.FormatGraphMoney(tooltipItem.value, self.defaultCurrency)
}
}
},

View File

@ -228,6 +228,10 @@ export default {
save_estimate: 'Save Estimate',
confirm_conversion: 'You want to convert this Estimate into Invoice?',
conversion_message: 'Conversion successful',
confirm_send_estimate: 'This estimate will be sent via email to the customer',
confirm_mark_as_sent: 'This estimate will be marked as sent',
confirm_mark_as_accepted: 'This estimate will be marked as Accepted',
confirm_mark_as_rejected: 'This estimate will be marked as Rejected',
no_matching_estimates: 'There are no matching estimates!',
errors: {
required: 'Field is required'
@ -551,6 +555,16 @@ export default {
action: 'Action',
add_currency: 'Add Currency'
},
mail: {
host: 'Mail Host',
port: 'Mail Port',
driver: 'Mail Driver',
password: 'Mail Password',
username: 'Mail Username',
mail_config: 'Mail Configuration',
encryption: 'Mail Encryption',
mail_config_desc: 'Below details will be used to update the mail environment. Also you can change the details anytime after logging in.'
},
pdf: {
title: 'PDF Setting',
footer_text: 'Footer Text',
@ -616,6 +630,7 @@ export default {
created_message: 'Sales tax created successfully',
updated_message: 'Sales tax updated successfully',
deleted_message: 'Sales tax deleted successfully',
confirm_delete: 'You will not be able to recover this Tax Type',
already_in_use: 'Tax is already in use'
},
expense_category: {
@ -628,6 +643,7 @@ export default {
created_message: 'Category created successfully',
deleted_message: 'Expense category deleted successfully',
updated_message: 'Expense category updated successfully',
confirm_delete: 'You will not be able to recover this Expense Category',
already_in_use: 'Category is already in use'
},
preferences: {
@ -682,6 +698,7 @@ export default {
username: 'Username',
next: 'Next',
continue: 'Continue',
skip: 'Skip',
database: {
database: 'Site URL & Database',
connection: 'Database Connection',
@ -752,7 +769,9 @@ export default {
payment_greater_than_due_amount: 'Entered Payment is more than due amount of this invoice.',
quantity_maxlength: 'Quantity should not be greater than 10 digits.',
price_maxlength: 'Price should not be greater than 10 digits.',
price_minvalue: 'Price should be greater than 0 digits',
amount_maxlength: 'Amount should not be greater than 10 digits.',
amount_minvalue: 'Amount should be greater than 0 digits',
description_maxlength: 'Description should not be greater than 255 characters.',
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.',

View File

@ -224,15 +224,19 @@ export default {
action: 'Acción',
notes: 'Notas',
tax: 'Impuesto',
send_estimate: 'Enviar presupuesto',
estimate_template: 'Plantilla de estimación',
convert_to_invoice: 'Convertir a factura',
mark_as_sent: 'Marcar como enviado',
send_estimate: 'Enviar presupuesto',
record_payment: 'Registro de pago',
add_estimate: 'Agregar presupuesto',
save_estimate: 'Guardar estimación',
confirm_conversion: '¿Quiere convertir esta estimación en factura?',
conversion_message: 'Conversión exitosa',
confirm_send_estimate: 'Esta estimación se enviará por correo electrónico al cliente',
confirm_mark_as_sent: 'Esta estimación se marcará como enviada',
confirm_mark_as_accepted: 'Esta estimación se marcará como Aceptada',
confirm_mark_as_rejected: 'Esta estimación se marcará como Rechazada',
errors: {
required: 'Se requiere campo'
},
@ -550,7 +554,7 @@ export default {
right: 'Derecho',
left: 'Izquierda',
action: 'Acción',
add_currency: 'Agregar moneda',
add_currency: 'Agregar moneda'
},
pdf: {
title: 'Configuración de PDF',
@ -597,8 +601,6 @@ export default {
estimate_viewed_desc: 'Cuando su cliente ve la estimación enviada a través del panel de control del cráter.',
save: 'Salvar',
email_save_message: 'Correo electrónico guardado con éxito',
invoice_viewed_message: 'Factura vista',
estimate_viewed_message: 'Estimación vista',
please_enter_email: 'Por favor, introduzca su correo electrónico'
},
tax_types: {
@ -616,6 +618,7 @@ export default {
created_message: 'Impuesto sobre las ventas creado con éxito',
updated_message: 'Impuesto sobre ventas actualizado con éxito',
deleted_message: 'Impuesto sobre las ventas eliminado con éxito',
confirm_delete: 'No podrá recuperar este tipo de impuesto',
already_in_use: 'El impuesto ya está en uso.'
},
expense_category: {
@ -628,6 +631,7 @@ export default {
created_message: 'Categoría creada con éxito',
deleted_message: 'Categoría de gastos eliminada correctamente',
updated_message: 'Categoría de gastos actualizada con éxito',
confirm_delete: 'No podrá recuperar esta categoría de gastos',
already_in_use: 'La categoría ya está en uso.'
},
preferences: {
@ -643,7 +647,6 @@ export default {
preference: 'Preferencia | Preferencias',
general_settings: 'Preferencias predeterminadas para el sistema.',
updated_message: 'Preferencias actualizadas exitosamente',
set_discount_per_item_message: 'Descuento establecido por artículo',
select_language: 'seleccione el idioma',
select_time_zone: 'selecciona la zona horaria',
select_date_formate: 'seleccione formato de fecha',
@ -669,7 +672,7 @@ export default {
state: 'Estado',
city: 'Ciudad',
address: 'Habla a',
street: 'Calle1 '| 'Calle # 2',
street: 'Calle1 ' | 'Calle # 2',
phone: 'Teléfono',
zip_code: 'Código postal',
go_back: 'Regresa',
@ -677,7 +680,52 @@ export default {
language: 'Idioma',
time_zone: 'Zona horaria',
fiscal_year: 'Año financiero',
date_format: 'Formato de fecha'
date_format: 'Formato de fecha',
from_address: 'De la Dirección',
username: 'Nombre de usuario',
next: 'Próximo',
continue: 'Hacer continuación',
database: {
database: 'URL del sitio y base de datose',
connection: 'Conexión de base de datos',
host: 'Database Host',
port: 'Host de base de datos',
password: 'Contraseña de base de datos',
app_url: 'URL de la aplicación',
username: 'Nombre de usuario de la base de datos',
db_name: 'Nombre de la base de datos',
desc: 'Cree una base de datos en su servidor y establezca las credenciales utilizando el siguiente formulario.'
},
permissions: {
permissions: 'Permisos',
permission_desc: 'A continuación se muestra la lista de permisos de carpeta necesarios para que la aplicación funcione. Si la verificación de permisos falla, asegúrese de actualizar los permisos de su carpeta.'
},
mail: {
host: 'Host de correo',
port: 'Puerto de correo',
driver: 'Conductor de correo',
password: 'Contraseña de correo',
username: 'Nombre de usuario de correo',
mail_config: 'Configuración de correo',
encryption: 'Cifrado de correo',
mail_config_desc: 'Los detalles a continuación se utilizarán para actualizar el entorno de correo. También puede cambiar los detalles en cualquier momento después de iniciar sesión.'
},
req: {
system_req: 'Requisitos del sistema',
php_req_version: 'Php (versión {version} necesario)',
check_req: 'Consultar requisitos',
system_req_desc: 'Crater tiene algunos requisitos de servidor. Asegúrese de que su servidor tenga la versión de php requerida y todas las extensiones mencionadas a continuación.'
},
errors: {
migrate_failed: 'La migración falló',
database_variables_save_error: 'No se puede conectar a la base de datos con los valores proporcionados.',
mail_variables_save_error: 'La configuración del correo electrónico ha fallado.',
connection_failed: 'Conexión de base de datos fallida'
},
success: {
mail_variables_save_successfully: 'Correo electrónico configurado correctamente',
database_variables_save_successfully: 'Base de datos configurada con éxito.'
}
},
layout_login: {
copyright_crater: 'Copyright @ Crater - 2019',
@ -708,7 +756,9 @@ export default {
payment_greater_than_due_amount: 'El pago ingresado es mayor al monto adeudado de esta factura.',
quantity_maxlength: 'La cantidad no debe ser mayor de 10 dígitos.',
price_maxlength: 'El precio no debe ser mayor de 10 dígitos.',
price_minvalue: 'El precio debe ser mayor que 0 dígitos',
amount_maxlength: 'La cantidad no debe ser mayor de 10 dígitos.',
amount_minvalue: 'La cantidad debe ser mayor que 0 dígitos',
description_maxlength: 'La descripción no debe tener más de 255 caracteres.',
maximum_options_error: 'Máximo de {max} opciones seleccionadas. Primero elimine una opción seleccionada para seleccionar otra.',
notes_maxlength: 'Las notas no deben tener más de 255 caracteres.',

View File

@ -233,6 +233,10 @@ export default {
save_estimate: 'Sauvegarder lestimation',
confirm_conversion: 'Vous souhaitez convertir cette estimation en facture?',
conversion_message: 'Conversion réussie',
confirm_send_estimate: 'Cette estimation sera envoyée par courrier électronique au client.',
confirm_mark_as_sent: 'Cette estimation sera marquée comme envoyé',
confirm_mark_as_accepted: 'Cette estimation sera marquée comme acceptée',
confirm_mark_as_rejected: 'Cette estimation sera marquée comme Rejetée',
errors: {
required: 'Champ requis'
},
@ -616,6 +620,7 @@ export default {
created_message: 'La taxe de vente créée avec succès',
updated_message: 'La taxe de vente a été mise à jour avec succès',
deleted_message: 'La taxe de vente a été supprimée avec succès',
confirm_delete: 'Vous ne pourrez pas récupérer ce type de taxe',
already_in_use: 'La taxe est déjà utilisée'
},
expense_category: {
@ -628,6 +633,7 @@ export default {
created_message: 'Catégorie créée avec succès',
deleted_message: 'La catégorie de dépenses a été supprimée avec succès',
updated_message: 'Catégorie de dépenses mise à jour avec succès',
confirm_delete: 'Vous ne pourrez pas récupérer cette catégorie de dépenses',
already_in_use: 'La catégorie est déjà utilisée'
},
preferences: {
@ -669,7 +675,7 @@ export default {
state: 'Etat',
city: 'Ville',
address: 'Adresse',
street: 'Street1 '| 'Rue # 2',
street: 'Street1 ' | 'Rue # 2',
phone: 'Téléphone',
zip_code: 'Code postal',
go_back: 'Retourner',
@ -677,7 +683,52 @@ export default {
language: 'La langue',
time_zone: 'Fuseau horaire',
fiscal_year: 'Année financière',
date_format: 'Format de date'
date_format: 'Format de date',
from_address: "De l'adresse",
username: "Nom d'utilisateur",
next: 'Suivant',
continue: 'Continuer',
database: {
database: 'URL du site et base de données',
connection: 'Connexion à la base de données',
host: 'Hôte de base de données',
port: 'Port de base de données',
password: 'Mot de passe de base de données',
app_url: 'Application URL',
username: "Nom d'utilisateur de la base de données",
db_name: 'Nom de la base de données',
desc: "Créez une base de données sur votre serveur et définissez les informations d'identification à l'aide du formulaire ci-dessous."
},
permissions: {
permissions: 'Les permissions',
permission_desc: "Vous trouverez ci-dessous la liste des autorisations de dossier requises pour le fonctionnement de l'application. Si la vérification des autorisations échoue, veillez à mettre à jour vos autorisations de dossier."
},
mail: {
host: 'Mail Host',
port: 'Port mail',
driver: 'Pilote de courrier',
password: 'Mot de passe mail',
username: "Mail Nom d'utilisateur",
mail_config: 'Configuration du courrier',
encryption: 'Chiffrement du courrier',
mail_config_desc: "Les détails ci-dessous seront utilisés pour mettre à jour l'environnement de messagerie. Aussi, vous pouvez modifier les détails à tout moment après la connexion."
},
req: {
system_req: 'Configuration requise',
php_req_version: 'Php (version {version} nécessaire)',
check_req: 'Vérifier les exigences',
system_req_desc: 'Crater a quelques exigences de serveur. Assurez-vous que votre serveur dispose de la version PHP requise et de toutes les extensions mentionnées ci-dessous.'
},
errors: {
migrate_failed: 'Migration impossible',
database_variables_save_error: 'Impossible de se connecter à la base de données avec les valeurs fournies.',
mail_variables_save_error: 'La configuration du courrier électronique a échoué.',
connection_failed: 'La connexion à la base de données a échoué'
},
success: {
mail_variables_save_successfully: 'Email configuré avec succès',
database_variables_save_successfully: 'Base de données configurée avec succès.'
}
},
layout_login: {
copyright_crater: 'Copyright @ Crater - 2019',
@ -708,7 +759,9 @@ export default {
payment_greater_than_due_amount: 'Le paiement entré est plus que le montant dû de cette facture.',
quantity_maxlength: 'La quantité ne doit pas dépasser 10 chiffres.',
price_maxlength: 'Le prix ne doit pas dépasser 10 chiffres.',
price_minvalue: 'Le prix doit être supérieur à 0 chiffre',
amount_maxlength: 'Le montant ne doit pas dépasser 10 chiffres.',
amount_minvalue: 'Le montant doit être supérieur à 0 chiffre',
description_maxlength: 'La description ne doit pas dépasser 255 caractères.',
maximum_options_error: 'Maximum de {max} options sélectionnées. Commencez par supprimer une option sélectionnée pour en sélectionner une autre.',
notes_maxlength: 'Les notes ne doivent pas dépasser 255 caractères.',

View File

@ -71,6 +71,7 @@ import Preferences from './views/settings/Preferences.vue'
import UserProfile from './views/settings/UserProfile.vue'
import TaxTypes from './views/settings/TaxTypes.vue'
import ExpenseCategory from './views/settings/ExpenseCategory.vue'
import MailConfig from './views/settings/MailConfig.vue'
import Wizard from './views/wizard/Index.vue'
@ -327,6 +328,11 @@ const routes = [
name: 'expense.category',
component: ExpenseCategory
},
{
path: 'mail-configuration',
name: 'mailconfig',
component: MailConfig
},
{
path: 'notifications',
name: 'notifications',

View File

@ -130,7 +130,7 @@ export const markAsRejected = ({ commit, dispatch, state }, data) => {
export const markAsSent = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post(`/api/estimates/sent`, data).then((response) => {
window.axios.post(`/api/estimates/mark-as-sent`, data).then((response) => {
// commit(types.UPDATE_INVOICE, response.data)
resolve(response)
}).catch((err) => {

View File

@ -343,7 +343,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('customers.confirm_delete'),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -363,7 +363,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('customers.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {

View File

@ -96,6 +96,7 @@
<line-chart
v-if="isLoaded"
:format-money="$utils.formatMoney"
:format-graph-money="$utils.formatGraphMoney"
:invoices="getChartInvoices"
:expenses="getChartExpenses"
:receipts="getReceiptTotals"

View File

@ -688,7 +688,7 @@ export default {
isValid = false
}
})
if (this.$v.newEstimate.$invalid === false && isValid === true) {
if (!this.$v.selectedCustomer.$invalid && this.$v.newEstimate.$invalid === false && isValid === true) {
return true
}
return false

View File

@ -249,25 +249,25 @@
{{ $t('estimates.convert_to_invoice') }}
</a>
</v-dropdown-item>
<v-dropdown-item>
<v-dropdown-item v-if="row.status !== 'SENT'">
<a class="dropdown-item" href="#" @click.self="onMarkAsSent(row.id)">
<font-awesome-icon icon="check-circle" class="dropdown-item-icon" />
{{ $t('estimates.mark_as_sent') }}
</a>
</v-dropdown-item>
<v-dropdown-item>
<v-dropdown-item v-if="row.status !== 'SENT'">
<a class="dropdown-item" href="#" @click.self="sendEstimate(row.id)">
<font-awesome-icon icon="paper-plane" class="dropdown-item-icon" />
{{ $t('estimates.send_estimate') }}
</a>
</v-dropdown-item>
<v-dropdown-item v-if="row.status === 'DRAFT'">
<v-dropdown-item v-if="row.status === 'DRAFT' || row.status === 'REJECTED'">
<a class="dropdown-item" href="#" @click.self="onMarkAsAccepted(row.id)">
<font-awesome-icon icon="check-circle" class="dropdown-item-icon" />
{{ $t('estimates.mark_as_accepted') }}
</a>
</v-dropdown-item>
<v-dropdown-item v-if="row.status === 'DRAFT'">
<v-dropdown-item v-if="row.status === 'ACCEPTED' || row.status === 'DRAFT'">
<a class="dropdown-item" href="#" @click.self="onMarkAsRejected(row.id)">
<font-awesome-icon icon="times-circle" class="dropdown-item-icon" />
{{ $t('estimates.mark_as_rejected') }}
@ -409,28 +409,48 @@ export default {
}
},
async onMarkAsAccepted (id) {
const data = {
id: id
}
let response = await this.markAsAccepted(data)
this.refreshTable()
if (response.data) {
this.filters.status = 'ACCEPTED'
this.$refs.table.refresh()
window.toastr['success'](this.$tc('estimates.marked_as_rejected_message'))
}
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('estimates.confirm_mark_as_accepted'),
icon: '/assets/icon/check-circle-solid.svg',
buttons: true,
dangerMode: true
}).then(async (markedAsRejected) => {
if (markedAsRejected) {
const data = {
id: id
}
let response = await this.markAsAccepted(data)
this.refreshTable()
if (response.data) {
this.filters.status = 'ACCEPTED'
this.$refs.table.refresh()
window.toastr['success'](this.$tc('estimates.marked_as_rejected_message'))
}
}
})
},
async onMarkAsRejected (id) {
const data = {
id: id
}
let response = await this.markAsRejected(data)
this.refreshTable()
if (response.data) {
this.filters.status = 'REJECTED'
this.$refs.table.refresh()
window.toastr['success'](this.$tc('estimates.marked_as_rejected_message'))
}
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('estimates.confirm_mark_as_rejected'),
icon: '/assets/icon/times-circle-solid.svg',
buttons: true,
dangerMode: true
}).then(async (markedAsRejected) => {
if (markedAsRejected) {
const data = {
id: id
}
let response = await this.markAsRejected(data)
this.refreshTable()
if (response.data) {
this.filters.status = 'REJECTED'
this.$refs.table.refresh()
window.toastr['success'](this.$tc('estimates.marked_as_rejected_message'))
}
}
})
},
setFilters () {
this.filtersApplied = true
@ -470,7 +490,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('estimates.confirm_delete', 1),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -491,7 +511,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('estimates.confirm_conversion'),
icon: 'error',
icon: '/assets/icon/envelope-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -510,7 +530,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('estimates.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -536,24 +556,44 @@ export default {
this.refreshTable()
},
async onMarkAsSent (id) {
const data = {
id: id
}
let response = await this.markAsSent(data)
this.refreshTable()
if (response.data) {
window.toastr['success'](this.$tc('estimates.mark_as_sent'))
}
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('estimates.confirm_mark_as_sent'),
icon: '/assets/icon/check-circle-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
if (willDelete) {
const data = {
id: id
}
let response = await this.markAsSent(data)
this.refreshTable()
if (response.data) {
window.toastr['success'](this.$tc('estimates.mark_as_sent'))
}
}
})
},
async sendEstimate (id) {
const data = {
id: id
}
let response = await this.sendEmail(data)
this.refreshTable()
if (response.data) {
window.toastr['success'](this.$tc('estimates.mark_as_sent'))
}
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('estimates.confirm_send_estimate'),
icon: '/assets/icon/paper-plane-solid.svg',
buttons: true,
dangerMode: true
}).then(async (sendEstimate) => {
if (sendEstimate) {
const data = {
id: id
}
let response = await this.sendEmail(data)
this.refreshTable()
if (response.data) {
window.toastr['success'](this.$tc('estimates.mark_as_sent'))
}
}
})
}
}
}

View File

@ -28,6 +28,7 @@
@select="onSelectItem"
@deselect="deselectItem"
@onDesriptionInput="$v.item.description.$touch()"
@onSelectItem="isSelected = true"
/>
</div>
</td>
@ -193,13 +194,17 @@ export default {
prefix: '$ ',
precision: 2,
masked: false
}
},
isSelected: false
}
},
computed: {
...mapGetters('item', [
'items'
]),
...mapGetters('modal', [
'modalActive'
]),
...mapGetters('currency', [
'defaultCurrencyForInput'
]),
@ -284,6 +289,11 @@ export default {
if (this.item.discount_type === 'percentage') {
this.item.discount_val = (this.item.discount * newValue) / 100
}
},
modalActive (val) {
if (!val) {
this.isSelected = false
}
}
},
validations () {
@ -313,7 +323,11 @@ export default {
},
created () {
window.hub.$on('checkItems', this.validateItem)
window.hub.$on('newItem', this.onSelectItem)
window.hub.$on('newItem', (val) => {
if (!this.item.item_id && this.modalActive && this.isSelected) {
this.onSelectItem(val)
}
})
},
methods: {
updateTax (data) {

View File

@ -126,6 +126,7 @@ export default {
this.$emit('search', val)
},
openItemModal () {
this.$emit('onSelectItem')
this.openModal({
'title': 'Add Item',
'componentName': 'ItemModal'

View File

@ -92,13 +92,14 @@
<money
v-model="amount"
v-bind="defaultCurrencyForInput"
:class="{'invalid' : $v.formData.amount.$error}"
class="input-field"
@input="$v.formData.amount.$touch()"
/>
</div>
<div v-if="$v.formData.amount.$error">
<span v-if="!$v.formData.amount.required" class="text-danger">{{ $t('validation.required') }}</span>
<span v-if="!$v.formData.amount.maxLength" class="text-danger">{{ $t('validation.amount_maxlength') }}</span>
<span v-if="!$v.formData.amount.maxValue" class="text-danger">{{ $t('validation.amount_minvalue') }}</span>
</div>
</div>
<div class="form-group col-sm-6">

View File

@ -358,7 +358,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('expenses.confirm_delete'),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -378,7 +378,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('expenses.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {

View File

@ -519,14 +519,6 @@ export default {
...mapActions('item', [
'fetchItems'
]),
isEmpty (obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
return false
}
}
return true
},
selectFixed () {
if (this.newInvoice.discount_type === 'fixed') {
return

View File

@ -496,7 +496,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('invoices.confirm_delete'),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -526,7 +526,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('invoices.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {

View File

@ -28,6 +28,7 @@
@select="onSelectItem"
@deselect="deselectItem"
@onDesriptionInput="$v.item.description.$touch()"
@onSelectItem="isSelected = true"
/>
</div>
</td>
@ -194,13 +195,17 @@ export default {
prefix: '$ ',
precision: 2,
masked: false
}
},
isSelected: false
}
},
computed: {
...mapGetters('item', [
'items'
]),
...mapGetters('modal', [
'modalActive'
]),
...mapGetters('currency', [
'defaultCurrencyForInput'
]),
@ -285,6 +290,11 @@ export default {
if (this.item.discount_type === 'percentage') {
this.item.discount_val = (this.item.discount * newValue) / 100
}
},
modalActive (val) {
if (!val) {
this.isSelected = false
}
}
},
validations () {
@ -314,7 +324,11 @@ export default {
},
created () {
window.hub.$on('checkItems', this.validateItem)
window.hub.$on('newItem', this.onSelectItem)
window.hub.$on('newItem', (val) => {
if (!this.item.item_id && this.modalActive && this.isSelected) {
this.onSelectItem(val)
}
})
},
methods: {
updateTax (data) {

View File

@ -115,6 +115,7 @@ export default {
this.$emit('search', val)
},
openItemModal () {
this.$emit('onSelectItem')
this.openModal({
'title': 'Add Item',
'componentName': 'ItemModal'

View File

@ -34,16 +34,16 @@
<label>{{ $t('items.price') }}</label><span class="text-danger"> *</span>
<div class="base-input">
<money
:invalid="$v.formData.price.$error"
:class="{'invalid' : $v.formData.price.$error}"
v-model="price"
v-bind="defaultCurrencyForInput"
class="input-field"
@input="$v.formData.price.$touch()"
/>
</div>
<div v-if="$v.formData.price.$error">
<span v-if="!$v.formData.price.required" class="text-danger">{{ $t('validation.required') }} </span>
<span v-if="!$v.formData.price.maxLength" class="text-danger">{{ $t('validation.price_maxlength') }}</span>
<span v-if="!$v.formData.price.minValue" class="text-danger">{{ $t('validation.price_minvalue') }}</span>
</div>
</div>
<div class="form-group">
@ -92,7 +92,8 @@
<script>
import { validationMixin } from 'vuelidate'
import { mapActions, mapGetters } from 'vuex'
const { required, minLength, numeric, alpha, minValue, maxLength} = require('vuelidate/lib/validators')
const { required, minLength, numeric, alpha, minValue, maxLength } = require('vuelidate/lib/validators')
export default {
mixins: {
validationMixin

View File

@ -362,7 +362,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('items.confirm_delete'),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -388,7 +388,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('items.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {

View File

@ -101,7 +101,6 @@
</div>
<div v-if="$v.formData.amount.$error">
<span v-if="!$v.formData.amount.required" class="text-danger">{{ $t('validation.required') }}</span>
<span v-if="!$v.formData.amount.numeric" class="text-danger">{{ $t('validation.numbers_only') }}</span>
<span v-if="!$v.formData.amount.between && $v.formData.amount.numeric && amount <= 0" class="text-danger">{{ $t('validation.payment_greater_than_zero') }}</span>
<span v-if="!$v.formData.amount.between && amount > 0" class="text-danger">{{ $t('validation.payment_greater_than_due_amount') }}</span>
</div>
@ -156,7 +155,7 @@ import { mapActions, mapGetters } from 'vuex'
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import moment from 'moment'
const { required, numeric, between, maxLength } = require('vuelidate/lib/validators')
const { required, between, maxLength } = require('vuelidate/lib/validators')
export default {
components: { MultiSelect },
@ -201,7 +200,6 @@ export default {
},
amount: {
required,
numeric,
between: between(1, this.maxPayableAmount + 1)
},
notes: {

View File

@ -368,7 +368,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('payments.confirm_delete'),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
@ -388,7 +388,7 @@ export default {
swal({
title: this.$t('general.are_you_sure'),
text: this.$tc('payments.confirm_delete', 2),
icon: 'error',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {

View File

@ -48,7 +48,7 @@
</div>
<div class="col-sm-8 reports-tab-container">
<iframe :src="getReportUrl" class="reports-frame-style"/>
<a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank">
<a class="base-button btn btn-primary btn-lg report-view-button" @click="viewReportsPDF">
<font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span>
</a>
</div>
@ -179,6 +179,11 @@ export default {
setRangeToCustom () {
this.selectedRange = 'Custom'
},
async viewReportsPDF () {
let data = await this.getReports()
window.open(this.getReportUrl, '_blank')
return data
},
async getReports (isDownload = false) {
this.$v.range.$touch()
this.$v.formData.$touch()

View File

@ -48,7 +48,7 @@
</div>
<div class="col-sm-8 reports-tab-container">
<iframe :src="getReportUrl" class="reports-frame-style"/>
<a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank">
<a class="base-button btn btn-primary btn-lg report-view-button" @click="viewReportsPDF">
<font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span>
</a>
</div>
@ -184,6 +184,11 @@ export default {
setRangeToCustom () {
this.selectedRange = 'Custom'
},
async viewReportsPDF () {
let data = await this.getReports()
window.open(this.getReportUrl, '_blank')
return data
},
async getReports (isDownload = false) {
this.$v.range.$touch()
this.$v.formData.$touch()

View File

@ -70,7 +70,7 @@
</div>
<div class="col-sm-8 reports-tab-container">
<iframe :src="getReportUrl" class="reports-frame-style"/>
<a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank">
<a class="base-button btn btn-primary btn-lg report-view-button" @click="viewReportsPDF">
<font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span>
</a>
</div>
@ -217,6 +217,11 @@ export default {
this.url = `${this.itemsSiteURL}?from_date=${moment(this.formData.from_date).format('DD/MM/YYYY')}&to_date=${moment(this.formData.to_date).format('DD/MM/YYYY')}`
return true
},
async viewReportsPDF () {
let data = await this.getReports()
window.open(this.getReportUrl, '_blank')
return data
},
async getReports (isDownload = false) {
this.$v.range.$touch()
this.$v.formData.$touch()

View File

@ -48,7 +48,7 @@
</div>
<div class="col-sm-8 reports-tab-container">
<iframe :src="getReportUrl" class="reports-frame-style"/>
<a :href="getReportUrl" class="base-button btn btn-primary btn-lg report-view-button" target="_blank">
<a class="base-button btn btn-primary btn-lg report-view-button" @click="viewReportsPDF">
<font-awesome-icon icon="file-pdf" class="vue-icon icon-left svg-inline--fa fa-download fa-w-16 mr-2" /> <span>{{ $t('reports.view_pdf') }}</span>
</a>
</div>
@ -179,6 +179,11 @@ export default {
setRangeToCustom () {
this.selectedRange = 'Custom'
},
async viewReportsPDF () {
let data = await this.getReports()
window.open(this.getReportUrl, '_blank')
return data
},
async getReports (isDownload = false) {
this.$v.range.$touch()
this.$v.formData.$touch()

View File

@ -101,13 +101,23 @@ export default {
'deleteCategory'
]),
async removeExpenseCategory (id, index) {
let response = await this.deleteCategory(id)
if (response.data.success) {
window.toastr['success'](this.$tc('settings.expense_category.deleted_message'))
this.id = null
this.$refs.table.refresh()
return true
} window.toastr['success'](this.$t('settings.expense_category.already_in_use'))
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('settings.expense_category.confirm_delete'),
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
if (willDelete) {
let response = await this.deleteCategory(id)
if (response.data.success) {
window.toastr['success'](this.$tc('settings.expense_category.deleted_message'))
this.id = null
this.$refs.table.refresh()
return true
} window.toastr['error'](this.$t('settings.expense_category.already_in_use'))
}
})
},
openCategoryModal () {
this.openModal({

View File

@ -0,0 +1,213 @@
<template>
<div class="setting-main-container">
<div class="card setting-card">
<div class="page-header">
<h3 class="page-title">{{ $t('settings.mail.mail_config') }}</h3>
<p class="page-sub-title">
{{ $t('settings.mail.mail_config_desc') }}
</p>
</div>
<form action="" @submit.prevent="saveEmailConfig()">
<div class="row my-2 mt-5">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.driver') }}</label>
<span class="text-danger"> *</span>
<base-select
v-model="mailConfigData.mail_driver"
:invalid="$v.mailConfigData.mail_driver.$error"
:options="mail_drivers"
:searchable="true"
:show-labels="false"
@change="$v.mailConfigData.mail_driver.$touch()"
/>
<div v-if="$v.mailConfigData.mail_driver.$error">
<span v-if="!$v.mailConfigData.mail_driver.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.host') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_host.$error"
v-model.trim="mailConfigData.mail_host"
type="text"
name="mail_host"
@input="$v.mailConfigData.mail_host.$touch()"
/>
<div v-if="$v.mailConfigData.mail_host.$error">
<span v-if="!$v.mailConfigData.mail_host.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.username') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_username.$error"
v-model.trim="mailConfigData.mail_username"
type="text"
name="db_name"
@input="$v.mailConfigData.mail_username.$touch()"
/>
<div v-if="$v.mailConfigData.mail_username.$error">
<span v-if="!$v.mailConfigData.mail_username.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.password') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_password.$error"
v-model.trim="mailConfigData.mail_password"
type="mail_password"
name="name"
@input="$v.mailConfigData.mail_password.$touch()"
/>
<div v-if="$v.mailConfigData.mail_password.$error">
<span v-if="!$v.mailConfigData.mail_password.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<div class="row my-2">
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.port') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_port.$error"
v-model.trim="mailConfigData.mail_port"
type="text"
name="mail_port"
@input="$v.mailConfigData.mail_port.$touch()"
/>
<div v-if="$v.mailConfigData.mail_port.$error">
<span v-if="!$v.mailConfigData.mail_port.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
<span v-if="!$v.mailConfigData.mail_port.numeric" class="text-danger">
{{ $tc('validation.numbers_only') }}
</span>
</div>
</div>
<div class="col-md-6 my-2">
<label class="form-label">{{ $t('settings.mail.encryption') }}</label>
<span class="text-danger"> *</span>
<base-input
:invalid="$v.mailConfigData.mail_encryption.$error"
v-model.trim="mailConfigData.mail_encryption"
type="text"
name="name"
@input="$v.mailConfigData.mail_encryption.$touch()"
/>
<div v-if="$v.mailConfigData.mail_encryption.$error">
<span v-if="!$v.mailConfigData.mail_encryption.required" class="text-danger">
{{ $tc('validation.required') }}
</span>
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-5"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
</form>
</div>
</div>
</template>
<script>
import MultiSelect from 'vue-multiselect'
import { validationMixin } from 'vuelidate'
import Ls from '../../services/ls'
const { required, email, numeric } = require('vuelidate/lib/validators')
export default {
components: {
MultiSelect
},
mixins: [validationMixin],
data () {
return {
mailConfigData: {
mail_driver: 'smtp',
mail_host: 'mailtrap.io',
mail_port: 2525,
mail_username: 'cc3c64516febd4',
mail_password: 'e6a0176301f587',
mail_encryption: 'tls'
},
loading: false,
mail_drivers: []
}
},
validations: {
mailConfigData: {
mail_driver: {
required
},
mail_host: {
required
},
mail_port: {
required,
numeric
},
mail_username: {
required
},
mail_password: {
required
},
mail_encryption: {
required
}
}
},
mounted () {
// this.getMailDrivers()
},
methods: {
async getMailDrivers () {
this.loading = true
let response = await window.axios.get('/api/admin/onboarding/environment/mail')
if (response.data) {
this.mail_drivers = response.data
this.loading = false
}
},
async saveEmailConfig () {
this.$v.mailConfigData.$touch()
if (this.$v.mailConfigData.$invalid) {
return true
}
this.loading = true
try {
let response = await window.axios.post('/api/admin/onboarding/environment/mail', this.mailConfigData)
if (response.data.success) {
window.toastr['success'](this.$t('wizard.success.' + response.data.success))
} else {
window.toastr['error'](this.$t('wizard.errors.' + response.data.error))
}
this.loading = false
return true
} catch (e) {
window.toastr['error']('Something went wrong')
}
}
}
}
</script>

View File

@ -113,7 +113,11 @@
<h3 class="page-title">{{ $t('settings.preferences.discount_setting') }}</h3>
<div class="flex-box">
<div class="left">
<base-switch v-model="discount_per_item" class="btn-switch" @change="setDiscount" />
<base-switch
v-model="discount_per_item"
class="btn-switch"
@change="setDiscount"
/>
</div>
<div class="right ml-15">
<p class="box-title"> {{ $t('settings.preferences.discount_per_item') }} </p>

View File

@ -34,7 +34,7 @@
>
<template slot-scope="row">
<span>{{ $t('settings.tax_types.tax_name') }}</span>
<span class="tax-name">
<span class="tax-name mt-3">
{{ row.name }}
</span>
</template>
@ -159,13 +159,23 @@ export default {
}
},
async removeTax (id, index) {
let response = await this.deleteTaxType(id)
if (response.data.success) {
window.toastr['success'](this.$t('settings.tax_types.deleted_message'))
this.id = null
this.$refs.table.refresh()
return true
}window.toastr['success'](this.$t('settings.tax_types.already_in_use'))
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('settings.tax_types.confirm_delete'),
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (willDelete) => {
if (willDelete) {
let response = await this.deleteTaxType(id)
if (response.data.success) {
window.toastr['success'](this.$t('settings.tax_types.deleted_message'))
this.id = null
this.$refs.table.refresh()
return true
}window.toastr['error'](this.$t('settings.tax_types.already_in_use'))
}
})
},
openTaxModal () {
this.openModal({

View File

@ -63,6 +63,12 @@ export default {
icon: 'list-alt',
iconType: 'far'
},
{
link: '/admin/settings/mail-configuration',
title: 'settings.mail.mail_config',
icon: 'envelope',
iconType: 'fa'
},
{
link: '/admin/settings/notifications',
title: 'settings.menu_title.notifications',

View File

@ -109,15 +109,26 @@
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-5"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
<div class="row mt-5">
<base-button
:loading="loading"
class="pull-right"
icon="save"
color="theme"
type="submit"
>
{{ $t('wizard.save_cont') }}
</base-button>
<base-button
:loading="loading"
class="pull-right ml-4"
outline
color="theme"
@click="$emit('next')"
>
{{ $t('wizard.skip') }}
</base-button>
</div>
</form>
</div>
</template>
@ -137,10 +148,10 @@ export default {
mailConfigData: {
mail_driver: 'smtp',
mail_host: 'mailtrap.io',
mail_port: 2525,
mail_username: 'cc3c64516febd4',
mail_password: 'e6a0176301f587',
mail_encryption: 'tls'
mail_port: null,
mail_username: null,
mail_password: null,
mail_encryption: null
},
loading: false,
mail_drivers: []