Fix Invoice/Estimate template issues and Add Payment Receipt, Custom Payment Modes and Item units

This commit is contained in:
Jay Makwana
2020-01-05 07:22:36 +00:00
committed by Mohit Panjwani
parent 56a955befd
commit 4c33a5d88c
112 changed files with 5050 additions and 331 deletions

View File

@ -12,6 +12,7 @@ import CustomerModal from './components/base/modal/CustomerModal.vue'
import TaxTypeModal from './components/base/modal/TaxTypeModal.vue'
import CategoryModal from './components/base/modal/CategoryModal.vue'
import money from 'v-money'
import VTooltip from 'v-tooltip'
/**
* Global css plugins
@ -107,6 +108,7 @@ window.toastr = require('toastr')
Vue.use(VueRouter)
Vue.use(Vuex)
Vue.use(VTooltip)
// register directive v-money and component <money>
Vue.use(money, {precision: 2})

View File

@ -21,6 +21,9 @@ import EstimateTemplate from './EstimateTemplate'
import InvoiceTemplate from './InvoiceTemplate'
import CustomerModal from './CustomerModal'
import CategoryModal from './CategoryModal'
import PaymentMode from './PaymentModeModal'
import ItemUnit from './ItemUnitModal'
import MailTestModal from './MailTestModal'
export default {
components: {
@ -29,7 +32,10 @@ export default {
EstimateTemplate,
InvoiceTemplate,
CustomerModal,
CategoryModal
CategoryModal,
PaymentMode,
ItemUnit,
MailTestModal
},
data () {
return {

View File

@ -341,6 +341,7 @@ export default {
mixins: [validationMixin],
data () {
return {
isEdit: false,
isLoading: false,
countryList: [],
billingCountry: null,
@ -399,9 +400,22 @@ export default {
...mapGetters('currency', [
'defaultCurrency',
'currencies'
]),
...mapGetters('modal', [
'modalDataID',
'modalData',
'modalActive'
])
},
watch: {
'modalDataID' (val) {
if (val) {
this.isEdit = true
this.setData()
} else {
this.isEdit = false
}
},
billingCountry () {
if (this.billingCountry) {
this.billing.country_id = this.billingCountry.id
@ -419,6 +433,9 @@ export default {
this.$refs.name.focus = true
this.currency = this.defaultCurrency
this.fetchCountry()
if (this.modalDataID) {
this.setData()
}
},
methods: {
...mapActions('invoice', {
@ -493,6 +510,24 @@ export default {
this.formData.addresses = [{...this.shipping, type: 'shipping'}]
return true
},
async setData () {
this.formData.id = this.modalData.id
this.formData.name = this.modalData.name
this.formData.email = this.modalData.email
this.formData.contact_name = this.modalData.contact_name
this.formData.phone = this.modalData.phone
this.formData.website = this.modalData.website
this.currency = this.modalData.currency
if (this.modalData.billing_address) {
this.billing = this.modalData.billing_address
this.billingCountry = this.modalData.billing_address.country
}
if (this.modalData.shipping_address) {
this.shipping = this.modalData.shipping_address
this.shippingCountry = this.modalData.shipping_address.country
}
},
async submitCustomerData () {
this.$v.formData.$touch()
@ -510,14 +545,23 @@ export default {
this.formData.currency_id = this.defaultCurrency.id
}
try {
let response = await this.addCustomer(this.formData)
let response = null
if (this.modalDataID) {
response = await this.updateCustomer(this.formData)
} else {
response = await this.addCustomer(this.formData)
}
if (response.data) {
window.toastr['success'](this.$tc('customers.created_message'))
if (this.modalDataID) {
window.toastr['success'](this.$tc('customers.updated_message'))
} else {
window.toastr['success'](this.$tc('customers.created_message'))
}
this.isLoading = false
if (this.$route.name === 'invoices.create') {
if (this.$route.name === 'invoices.create' || this.$route.name === 'invoices.edit') {
this.setInvoiceCustomer(response.data.customer.id)
}
if (this.$route.name === 'estimates.create') {
if (this.$route.name === 'estimates.create' || this.$route.name === 'estimates.edit') {
this.setEstimateCustomer(response.data.customer.id)
}
this.resetData()

View File

@ -45,14 +45,34 @@
<div class="col-sm-7">
<base-select
v-model="formData.unit"
:options="units"
:options="itemUnits"
:searchable="true"
:show-labels="false"
label="name"
>
<div slot="afterList">
<button type="button" class="list-add-button" @click="addItemUnit">
<font-awesome-icon class="icon" icon="cart-plus" />
<label>{{ $t('settings.customization.items.add_item_unit') }}</label>
</button>
</div>
</base-select>
</div>
</div>
<div v-if="isTexPerItem" class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('items.taxes') }}</label>
<div class="col-sm-7">
<base-select
v-model="formData.taxes"
:options="getTaxTypes"
:searchable="true"
:show-labels="false"
:allow-empty="true"
:multiple="true"
label="tax_name"
/>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('items.description') }}</label>
<div class="col-sm-7">
@ -124,11 +144,13 @@ export default {
{ name: 'mg', value: 'mg' },
{ name: 'pc', value: 'pc' }
],
taxes: [],
formData: {
name: null,
price: null,
description: null,
unit: null
unit: null,
taxes: []
}
}
},
@ -161,12 +183,28 @@ export default {
this.formData.price = newValue * 100
}
},
// itemUnits () {
// return this.units
// },
...mapGetters('modal', [
'modalDataID'
'modalDataID',
'modalData'
]),
...mapGetters('item', [
'getItemById'
])
'getItemById',
'itemUnits'
]),
...mapGetters('taxType', [
'taxTypes'
]),
isTexPerItem () {
return this.modalData.taxPerItem === 'YES'
},
getTaxTypes () {
return this.taxTypes.map(tax => {
return {...tax, tax_name: tax.name + ' (' + tax.percent + '%)'}
})
}
},
watch: {
modalDataID () {
@ -179,12 +217,17 @@ export default {
this.isEdit = true
this.fetchEditData()
}
if (this.isEdit) {
this.loadEditData()
}
},
mounted () {
this.$refs.name.focus = true
},
methods: {
...mapActions('modal', [
'openModal',
'closeModal',
'resetModalData'
]),
@ -203,7 +246,6 @@ export default {
unit: null,
id: null
}
this.$v.$reset()
},
fetchEditData () {
@ -230,9 +272,20 @@ export default {
if (this.isEdit) {
response = await this.updateItem(this.formData)
} else {
response = await this.addItem(this.formData)
let data = {
...this.formData,
taxes: this.formData.taxes.map(tax => {
return {
tax_type_id: tax.id,
amount: ((this.formData.price * tax.percent) / 100),
percent: tax.percent,
name: tax.name,
collective_tax: 0
}
})
}
response = await this.addItem(data)
}
if (response.data) {
window.toastr['success'](this.$tc('items.created_message'))
this.setItem(response.data.item)
@ -245,6 +298,12 @@ export default {
}
window.toastr['error'](response.data.error)
},
async addItemUnit () {
this.openModal({
'title': 'Add Item Unit',
'componentName': 'ItemUnit'
})
},
closeItemModal () {
this.resetFormData()
this.closeModal()

View File

@ -0,0 +1,148 @@
<template>
<div class="item-unit-modal">
<form action="" @submit.prevent="submitItemUnit">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('settings.customization.items.unit_name') }} <span class="required"> *</span></label>
<div class="col-sm-7">
<base-input
ref="name"
:invalid="$v.formData.name.$error"
v-model="formData.name"
type="text"
@input="$v.formData.name.$touch()"
/>
<div v-if="$v.formData.name.$error">
<span v-if="!$v.formData.name.required" class="form-group__message text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
</div>
</div>
<div class="card-footer">
<base-button
:outline="true"
class="mr-3"
color="theme"
type="button"
@click="closePaymentModeModal"
>
{{ $t('general.cancel') }}
</base-button>
<base-button
:loading="isLoading"
color="theme"
icon="save"
type="submit"
>
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</base-button>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { validationMixin } from 'vuelidate'
const { required, minLength } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
isEdit: false,
isLoading: false,
formData: {
id: null,
name: null
}
}
},
computed: {
...mapGetters('modal', [
'modalDataID',
'modalData',
'modalActive'
])
},
validations: {
formData: {
name: {
required,
minLength: minLength(2)
}
}
},
async mounted () {
this.$refs.name.focus = true
if (this.modalDataID) {
this.isEdit = true
this.setData()
}
},
methods: {
...mapActions('modal', [
'closeModal',
'resetModalData'
]),
...mapActions('item', [
'addItemUnit',
'updateItemUnit',
'fatchItemUnit'
]),
resetFormData () {
this.formData = {
id: null,
name: null
}
this.$v.formData.$reset()
},
async submitItemUnit () {
this.$v.formData.$touch()
if (this.$v.$invalid) {
return true
}
this.isLoading = true
let response
if (this.isEdit) {
response = await this.updateItemUnit(this.formData)
if (response.data) {
window.toastr['success'](this.$t('settings.customization.items.item_unit_updated'))
this.closePaymentModeModal()
return true
}
window.toastr['error'](response.data.error)
} else {
try {
response = await this.addItemUnit(this.formData)
if (response.data) {
this.isLoading = false
window.toastr['success'](this.$t('settings.customization.items.item_unit_added'))
this.closePaymentModeModal()
return true
} window.toastr['error'](response.data.error)
} catch (err) {
if (err.response.data.errors.name) {
this.isLoading = true
window.toastr['error'](this.$t('validation.item_unit_already_taken'))
}
}
}
},
async setData () {
this.formData = {
id: this.modalData.id,
name: this.modalData.name
}
},
closePaymentModeModal () {
this.resetModalData()
this.resetFormData()
this.closeModal()
}
}
}
</script>

View File

@ -0,0 +1,166 @@
<template>
<div class="mail-test-modal">
<form action="" @submit.prevent="onTestMailSend">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('general.to') }} <span class="required"> *</span></label>
<div class="col-sm-7">
<base-input
ref="to"
:invalid="$v.formData.to.$error"
v-model="formData.to"
type="text"
@input="$v.formData.to.$touch()"
/>
<div v-if="$v.formData.to.$error">
<span v-if="!$v.formData.to.required" class="form-group__message text-danger">{{ $tc('validation.required') }}</span>
<span v-if="!$v.formData.to.email" class="form-group__message text-danger"> {{ $t('validation.email_incorrect') }} </span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('general.subject') }} <span class="required"> *</span></label>
<div class="col-sm-7">
<div class="base-input">
<base-input
:invalid="$v.formData.subject.$error"
v-model="formData.subject"
type="text"
@input="$v.formData.subject.$touch()"
/>
</div>
<div v-if="$v.formData.subject.$error">
<span v-if="!$v.formData.subject.required" class="text-danger">{{ $t('validation.required') }}</span>
<span v-if="!$v.formData.subject.maxLength" class="text-danger">{{ $t('validation.subject_maxlength') }}</span>
</div>
</div>
</div>
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('general.message') }}<span class="required"> *</span></label>
<div class="col-sm-7">
<base-text-area
v-model="formData.message"
:invalid="$v.formData.message.$error"
rows="4"
cols="50"
@input="$v.formData.message.$touch()"
/>
<div v-if="$v.formData.message.$error">
<span v-if="!$v.formData.message.required" class="text-danger">{{ $t('validation.required') }}</span>
<span v-if="!$v.formData.message.maxLength" class="text-danger">{{ $t('validation.message_maxlength') }}</span>
</div>
</div>
</div>
</div>
<div class="card-footer">
<base-button
:outline="true"
class="mr-3"
color="theme"
type="button"
@click="closeTaxModal"
>
{{ $t('general.cancel') }}
</base-button>
<base-button
:loading="isLoading"
color="theme"
icon="save"
type="submit"
>
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</base-button>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { validationMixin } from 'vuelidate'
const { required, minLength, email, maxLength } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
isEdit: false,
isLoading: false,
formData: {
to: null,
subject: null,
message: null
}
}
},
computed: {
...mapGetters('modal', [
'modalDataID',
'modalData',
'modalActive'
])
},
validations: {
formData: {
to: {
required,
email
},
subject: {
required,
maxLength: maxLength(100)
},
message: {
required,
maxLength: maxLength(255)
}
}
},
async mounted () {
this.$refs.to.focus = true
},
methods: {
...mapActions('modal', [
'closeModal',
'resetModalData'
]),
resetFormData () {
this.formData = {
to: null,
subject: null,
message: null
}
this.$v.formData.$reset()
},
async onTestMailSend () {
this.$v.formData.$touch()
if (this.$v.$invalid) {
return true
}
this.isLoading = true
let response = await axios.post('/api/settings/test/mail', this.formData)
if (response.data) {
if (response.data.success) {
window.toastr['success'](this.$tc('general.send_mail_successfully'))
this.closeTaxModal()
this.isLoading = false
return true
}
window.toastr['error'](this.$tc('validation.something_went_wrong'))
this.closeTaxModal()
this.isLoading = false
return true
}
window.toastr['error'](response.data.error)
},
closeTaxModal () {
this.resetModalData()
this.resetFormData()
this.closeModal()
}
}
}
</script>

View File

@ -0,0 +1,143 @@
<template>
<div class="payment-modes-modal">
<form action="" @submit.prevent="submitPaymentMode">
<div class="card-body">
<div class="form-group row">
<label class="col-sm-4 col-form-label input-label">{{ $t('settings.customization.payments.mode_name') }} <span class="required"> *</span></label>
<div class="col-sm-7">
<base-input
ref="name"
:invalid="$v.formData.name.$error"
v-model="formData.name"
type="text"
@input="$v.formData.name.$touch()"
/>
<div v-if="$v.formData.name.$error">
<span v-if="!$v.formData.name.required" class="form-group__message text-danger">{{ $tc('validation.required') }}</span>
</div>
</div>
</div>
</div>
<div class="card-footer">
<base-button
:outline="true"
class="mr-3"
color="theme"
type="button"
@click="closePaymentModeModal"
>
{{ $t('general.cancel') }}
</base-button>
<base-button
:loading="isLoading"
color="theme"
icon="save"
type="submit"
>
{{ !isEdit ? $t('general.save') : $t('general.update') }}
</base-button>
</div>
</form>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
import { validationMixin } from 'vuelidate'
const { required, minLength } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
data () {
return {
isEdit: false,
isLoading: false,
formData: {
id: null,
name: null
}
}
},
computed: {
...mapGetters('modal', [
'modalDataID',
'modalData',
'modalActive'
])
},
validations: {
formData: {
name: {
required,
minLength: minLength(2)
}
}
},
async mounted () {
this.$refs.name.focus = true
if (this.modalDataID) {
this.isEdit = true
this.setData()
}
},
methods: {
...mapActions('modal', [
'closeModal',
'resetModalData'
]),
...mapActions('payment', [
'addPaymentMode',
'updatePaymentMode'
]),
resetFormData () {
this.formData = {
id: null,
name: null
}
this.$v.formData.$reset()
},
async submitPaymentMode () {
this.$v.formData.$touch()
if (this.$v.$invalid) {
return true
}
this.isLoading = true
let response
if (this.isEdit) {
response = await this.updatePaymentMode(this.formData)
if (response.data) {
window.toastr['success'](this.$t('settings.customization.payments.payment_mode_updated'))
this.closePaymentModeModal()
return true
} window.toastr['error'](response.data.error)
} else {
try {
response = await this.addPaymentMode(this.formData)
if (response.data) {
this.isLoading = false
window.toastr['success'](this.$t('settings.customization.payments.payment_mode_added'))
this.closePaymentModeModal()
return true
} window.toastr['error'](response.data.error)
} catch (err) {
if (err.response.data.errors.name) {
this.isLoading = true
window.toastr['error'](this.$t('validation.payment_mode_already_taken'))
}
}
}
},
async setData () {
this.formData = {
id: this.modalData.id,
name: this.modalData.name
}
},
closePaymentModeModal () {
this.resetModalData()
this.resetFormData()
this.closeModal()
}
}
}
</script>

View File

@ -852,6 +852,8 @@
"email_incorrect": "بريد الكتروني غير صحيح.",
"email_already_taken": "هذا البريد الالكتروني مستخدم مسبقاً",
"email_does_not_exist": "لا يوجد كستخدم بهذا البريد الالكتروني",
"item_unit_already_taken": "وحدة البند قد اتخذت بالفعل",
"payment_mode_already_taken": "لقد تم بالفعل أخذ طريقة الدفع",
"send_reset_link": "أرسال رابط استعادة كلمة المرور",
"not_yet": "ليس بعد؟ أعد الإرسال الآن..",
"password_min_length": "كلمة المرور يجب أن تتكون من {count} أحرف على الأقل",

View File

@ -0,0 +1,877 @@
{
"credit_notes": {
"action": "Aktion",
"amount": "Summe",
"confirm_delete": "Wollen Sie diese Gutschrift löschen?",
"contact": "Kontakt",
"credit_notes": "Gutschriften",
"credit_notes_list": "Gutschriften-Liste",
"credit_number": "Kreditkarten-Nummer",
"date": "Datum",
"item": {
"description": "Beschreibung",
"discount": "Rabatt",
"price": "Preis",
"quantity": "Menge",
"sub_total": "Zwischensumme",
"tax": "Steuer",
"title": "Titel",
"total": "Gesamt",
"total_discount": "Rabatt Gesamt"
},
"notes": "Hinweise",
"title": "Gutschriften"
},
"customers": {
"action": "Aktion",
"add_customer": "Kunde hinzufügen",
"add_new_customer": "Neuen Kunden hinzufügen",
"added_on": "Hinzugefügt am",
"address": "Adresse",
"amount_due": "Offener Betrag",
"basic_info": "Basisinformation",
"billing_address": "Rechnungsadresse",
"city": "Stadt",
"confirm_delete": "Sie können diesen Kunden nicht wiederherstellen | Sie können diese Kunden nicht wiederherstellen",
"contact_name": "Kontakt Name",
"contacts_list": "Kunden-Liste",
"copy_billing_address": "Rechnungsadresse kopieren",
"country": "Land",
"created_message": "Kunde erfolgreich erstellt",
"customer": "Kunde | Kunden",
"deleted_message": "Kunden erfolgreich gelöscht | Kunden erfolgreich gelöscht",
"display_name": "Anzeige Name",
"edit_customer": "Kunde bearbeiten",
"email": "E-Mail",
"list_of_customers": "Dieser Abschnitt enthält die Liste der Kunden.",
"name": "Name",
"new_customer": "Neuer Kunde",
"no_customers": "Noch keine Kunden!",
"no_customers_found": "Keine Kunden gefunden!",
"password": "Passwort",
"phone": "Telefon",
"primary_contact_name": "Ansprechpartner",
"primary_currency": "Primäre Währung",
"primary_display_name": "Primärer Anzeige Name",
"save_customer": "Kunde speichern",
"select_a_customer": "Wählen Sie einen Kunden",
"select_city": "Stadt wählen",
"select_country": "Land wählen",
"select_currency": "Währung wählen",
"select_state": "Bundesland wählen",
"shipping_address": "Versand-Adresse",
"state": "Bundesland",
"street_1": "Strasse",
"street_2": "Adresszusatz",
"street_number": "Hausnummer",
"title": "Kunden",
"type_or_click": "Eingeben oder anklicken zum auswählen",
"update_customer": "Kunden ändern",
"updated_message": "Kunde erfolgreich aktualisiert",
"website": "Webseite",
"zip_code": "PLZ"
},
"dashboard": {
"cards": {
"customers": "Kunden",
"due_amount": "Offene Beträge",
"estimates": "Kostenvoranschläge",
"invoices": "Rechnungen"
},
"chart_info": {
"net_income": "Einnahmen Netto",
"total_expense": "Gesamtausgaben",
"total_receipts": "Eingänge gesamt",
"total_sales": "Verkäufe gesamt",
"year": "Jahr"
},
"monthly_chart": {
"title": "Umsatz & Kosten"
},
"recent_estimate_card": {
"actions": "Aktionen",
"amount_due": "Betrag",
"customer": "Kunden",
"date": "Datum",
"title": "Aktuelle Kostenvoranschläge",
"view_all": "Alle Anzeigen"
},
"recent_invoices_card": {
"actions": "Aktionen",
"amount_due": "Offener Betrag",
"customer": "Kunden",
"due_on": "Fällig am",
"title": "Fällige Rechnungen",
"view_all": "Alle Anzeigen"
},
"select_year": "Jahr wählen",
"weekly_invoices": {
"title": "Wöchentliche Rechnungen"
}
},
"estimates": {
"Estimate": "Kostenvoranschlag | Kostenvoranschläge",
"accepted": "Angenommen",
"action": "Aktion",
"add_estimate": "Kostenvoranschlag hinzufügen",
"add_item": "Fügen Sie ein Artikel hinzu",
"add_new_estimate": "Neuen Kostenvoranschlag hinzufügen",
"add_new_tax": "neuen Steuersatz hinzufügen",
"add_tax": "Steuer hinzufügen",
"all": "Alle",
"amount": "Summe",
"amount_due": "OFFENER BETRAG",
"confirm_conversion": "Sie möchten, konvertieren Sie diese Schätzung in die Rechnung?",
"confirm_delete": "Der Kostenvoranschlag kann nicht wiederhergestellt werden | Die Kostenvoranschläge können nicht wiederhergestellt werden",
"confirm_mark_as_accepted": "Dieser Kostenvoranschlag wird als angenommen markiert",
"confirm_mark_as_rejected": "Dieser Kostenvoranschlag wird als abgelehnt markiert",
"confirm_mark_as_sent": "Dieser Kostenvoranschlag wird als gesendet markiert",
"confirm_send_estimate": "Der Kostenvoranschlag wird per E-Mail an den Kunden gesendet",
"contact": "Kontakt",
"conversion_message": "Rechnung erfolgreich erstellt",
"convert_to_invoice": "Konvertieren in Rechnung",
"created_message": "Kostenvoranschlag erfolgreich erstellt",
"customer": "KUNDEN",
"date": "Datum",
"days": "{days} Tage",
"declined": "Abgelehnt",
"deleted_message": "Kostenvoranschlag erfolgreich gelöscht | Kostenvoranschläge erfolgreich gelöscht",
"discount": "Rabatt",
"draft": "Entwurf",
"due_date": "Fälligkeit",
"edit_estimate": "Kostenvoranschlag ändern",
"errors": {
"required": "Feld ist erforderlich"
},
"estimate": "Kostenvoranschlag | Kostenvoranschläge",
"estimate_number": "Kostenvoran. Nummer",
"estimate_template": "Vorlage",
"estimates_list": "Liste Kostenvoranschläge",
"expiry_date": "Zahlungsziel",
"item": {
"amount": "Summe",
"description": "Beschreibung",
"discount": "Rabatt",
"price": "Preis",
"quantity": "Menge",
"select_an_item": "Wählen Sie einen Artikel",
"sub_total": "Zwischensumme",
"tax": "Steuer",
"title": "Titel",
"total": "Gesamt",
"total_discount": "Rabatt Gesamt",
"type_item_description": "Artikel Beschreibung (optional)"
},
"items": "Artikel",
"list_of_estimates": "Dieser Abschnitt enthält die Liste der Kostenvoranschläge.",
"mark_as_accepted": "Markiert als angenommen",
"mark_as_rejected": "Markiert als abgelehnt",
"mark_as_sent": "Als gesendet markieren",
"mark_as_sent_successfully": "Kostenvoranschlag als gesendet markiert.",
"marked_as_accepted_message": "Kostenvoranschlag als angenommen markiert",
"marked_as_rejected_message": "Kostenvoranschlag als abgelehnt markiert",
"months": "{months} Monat",
"new_estimate": "Neuer Kostenvoranschlag",
"no_estimates": "Keine Kostenvoranschläge vorhanden!",
"no_matching_estimates": "Es gibt keine übereinstimmenden Kostenvoranschläge!",
"notes": "Hinweise",
"number": "ANZAHL",
"paid": "Bezahlt",
"partially_paid": "Teilweise bezahlt",
"record_payment": "Zahlung erfassen",
"ref_no": "REF. - NR.",
"ref_number": "Ref-Nummer",
"save_estimate": "Kostenvoranschlag speichern",
"send_estimate": "Kostenvoranschlag senden",
"send_estimate_successfully": "Kostenvoranschlag erfolgreich gesendet",
"sent": "Gesendet",
"something_went_wrong": "Da ging etwas schief",
"status": "Status",
"sub_total": "Zwischensumme",
"tax": "Steuer",
"title": "Kostenvoranschläge",
"total": "Gesamt",
"unpaid": "Unbezahlte",
"update_Estimate": "Kostenvoranschlag aktualisieren",
"updated_message": "Kostenvoranschlag erfolgreich aktualisiert",
"user_email_does_not_exist": "Benutzer-E-Mail nicht vorhanden",
"years": "{years} Jahre"
},
"expenses": {
"action": "Aktion",
"add_expense": "Aufwendung hinzufügen",
"add_new_expense": "Neue Aufwendung hinzufügen",
"amount": "Summe",
"categories": {
"actions": "Aktionen",
"add_category": "Kategorie hinzufügen",
"amount": "Summe",
"categories_list": "Liste der Kategorien",
"category": "Kategorie | Kategorien",
"description": "Beschreibung",
"name": "Name",
"new_category": "Neue Kategorie",
"select_a_category": "Wählen Sie eine Kategorie",
"title": "Titel"
},
"category": "Kategorie",
"category_id": "Kategorie-Id",
"confirm_delete": "Sie können diese Ausgabe nicht wiederherstellen. | Sie können diese Ausgaben nicht wiederherstellen.",
"contact": "Kontakt",
"created_message": "Aufwand erfolgreich erstellt",
"date": "Aufwandsdatum",
"deleted_message": "Aufwand erfolgreich gelöscht | Aufwand erfolgreich gelöscht",
"description": "Beschreibung",
"download_receipt": "Quittung herunterladen",
"edit_expense": "Aufwendung ändern",
"expense": "Aufwendung | Aufwendungen",
"expense_date": "Datum",
"expense_title": "Titel",
"expenses_list": "Liste der Ausgaben",
"from_date": "Von Datum",
"list_of_expenses": "Dieser Abschnitt enthält die Liste der Ausgaben.",
"new_expense": "Neue Aufwendung",
"no_expenses": "Noch keine Ausgaben!",
"note": "Hinweis",
"receipt": "Eingang",
"save_expense": "Aufwendung speichern",
"title": "Aufwendungen/Ausgaben",
"to_date": "bis Datum",
"update_expense": "Aufwendung aktualisieren",
"updated_message": "Aufwand erfolgreich aktualisiert"
},
"general": {
"action_failed": "Aktion fehlgeschlagen",
"actions": "Aktionen",
"add_new_item": "Artikel hinzufügen",
"all": "Alle",
"are_you_sure": "Sind Sie sicher?",
"back_to_login": "Zurück zum Login?",
"bill_to": "Rechnungsempfänger",
"bytefury": "Bytefury",
"cancel": "Abrechen",
"choose": "Wählen",
"choose_file": "Klicken Sie hier, um eine Datei auszuwählen",
"choose_template": "Wählen Sie eine Vorlage",
"clear_all": "Alle entfernen",
"delete": "Löschen",
"discount": "RABATT",
"download": "Download",
"download_pdf": "Download PDF",
"draft": "Entwurf",
"due": "Fällig",
"edit": "Ändern",
"filter": "Filter",
"fixed": "Behoben",
"four_zero_four": "Vier hundert vier",
"from": "Von",
"from_date": "Von Datum",
"go_back": "zurück",
"go_home": "Geh zurück",
"home": "Startseite",
"list_is_empty": "Liste ist leer.",
"no_tax_found": "Kein Steuersatz gefunden!",
"of": "von",
"percentage": "Prozentsatz",
"powered_by": "Powered by",
"remove": "Entfernen",
"save": "Speichern",
"search": "Suchen",
"select_a_status": "Status wählen",
"select_a_tax": "Steuersatz wählen",
"select_all": "Alle auswählen",
"select_city": "Stadt wählen",
"select_country": "Land wählen",
"select_state": "Bundesland wählen",
"sent": "Gesendet",
"setting_updated": "Einstellungen erfolgreich aktualisiert",
"ship_to": "Versand ein",
"showing": "Anzeigen",
"street_1": "Straße",
"street_2": "Zusatz Strasse",
"subtotal": "ZWISCHENSUMME",
"tax": "Steuer",
"to": "bis",
"to_date": "bis Datum",
"total_amount": "GESAMTSUMME",
"update": "Update",
"view": "Anzeigen",
"view_pdf": "PDF anzeigen",
"you_got_lost": "Hoppla! Du hast dich verirrt!"
},
"invoices": {
"action": "Aktion",
"add_item": "Fügen Sie ein Artikel hinzu",
"add_new_invoice": "Neue Rechnung hinzufügen",
"add_new_tax": "Neuen Steuersatz hinzufügen",
"add_tax": "Steuersatz hinzufügen",
"all": "Alle",
"amount": "Summe",
"amount_due": "OFFENER BETRAG",
"confirm_delete": "Sie können diese Rechnung nicht wiederherstellen. | Sie können diese Rechnungen nicht wiederherstellen.",
"confirm_send": "Diese Rechnung wird per E-Mail an den Kunden gesendet",
"confirm_send_invoice": "Diese Rechnung wird per E-Mail an den Kunden gesendet",
"contact": "Kontakt",
"created_message": "Rechnung erfolgreich erstellt",
"customer": "KUNDEN",
"date": "Datum",
"days": "{days} Tage",
"deleted_message": "Rechnung erfolgreich gelöscht | Rechnungen erfolgreich gelöscht",
"discount": "Rabatt",
"due_date": "Fälligkeit",
"edit_invoice": "Rechnung bearbeiten",
"invalid_due_amount_message": "Der Gesamtrechnungsbetrag darf nicht kleiner sein als der für diese Rechnung bezahlte Gesamtbetrag. Bitte aktualisieren Sie die Rechnung oder löschen Sie die zugehörigen Zahlungen um fortzufahren.",
"invoice": "Rechnung | Rechnungen",
"invoice_date": "Rechnungsdatum",
"invoice_mark_as_sent": "Diese Rechnung wird als gesendet markiert",
"invoice_number": "Rechnungsnummer",
"invoice_template": "Rechnungs-Vorlage",
"invoices_list": "Liste der Rechnungen",
"item": {
"amount": "Summe",
"description": "Beschreibung",
"discount": "Rabatt",
"price": "Preis",
"quantity": "Menge",
"select_an_item": "Geben Sie oder wählen Sie ein Artikel",
"sub_total": "Zwischensumme",
"tax": "Steuer",
"title": "Titel",
"total": "Gesamt",
"total_discount": "Rabatt Gesamt",
"type_item_description": "Artikel Beschreibung (optional)"
},
"list_of_invoices": "Dieser Abschnitt enthält die Liste der Rechnungen.",
"mark_as_sent": "Als gesendet markieren",
"mark_as_sent_successfully": "Rechnung gekennzeichnet als erfolgreich gesendet",
"marked_as_sent_message": "Rechnung als erfolgreich gesendet markiert",
"months": "{months} Monat",
"new_invoice": "Neue Rechnung",
"no_invoices": "Keine Rechnungen vorhanden!",
"no_matching_invoices": "Es gibt keine entsprechenden Rechnungen!",
"notes": "Hinweise",
"number": "ANZAHL",
"paid": "Bezahlt",
"paid_status": "BEZAHLT-STATUS",
"partially_paid": "Teilzahlung",
"payment_attached_message": "Einer der ausgewählten Rechnungen ist bereits eine Zahlung zugeordnet. Stellen Sie sicher, dass Sie zuerst die angehängten Zahlungen löschen, um mit dem Entfernen fortzufahren",
"record_payment": "Zahlung erfassen",
"ref_no": "REF. - NR.",
"ref_number": "Ref-Nummer",
"save_invoice": "Rechnung speichern",
"select_invoice": "Wählen Sie eine Rechnung",
"send_invoice": "Rechnung senden",
"send_invoice_successfully": "Rechnung erfolgreich versendet",
"something_went_wrong": "Da ist etwas schief gelaufen",
"status": "Status",
"sub_total": "Zwischensumme",
"template": "Vorlage",
"title": "Rechnungen",
"total": "Gesamt",
"unpaid": "Unbezahlte",
"update_expense": "Kosten aktualisieren",
"update_invoice": "Rechnung ändern",
"updated_message": "Rechnung erfolgreich aktualisiert",
"user_email_does_not_exist": "Benutzer-E-Mail existiert nicht",
"view": "Anzeigen",
"years": "{years} Jahre"
},
"items": {
"action": "Aktion",
"add_item": "Artikel hinzufügen",
"add_new_item": "Neuen Artikel hinzufügen",
"added_on": "Hinzugefügt am",
"confirm_delete": "Sie können diesen Artikel nicht wiederherstellen | Sie können diese Artikel nicht wiederherstellen",
"created_message": "Artikel erfolgreich erstellt",
"date_of_creation": "Erstellt am",
"deleted_message": "Artikel erfolgreich gelöscht | Artikel erfolgreich gelöscht",
"description": "Beschreibung",
"edit_item": "Artikel bearbeiten",
"item": "Artikel | Artikel",
"item_attached_message": "Ein Artikel der bereits verwendet wird kann nicht gelöscht werden",
"items_list": "Artikel-Liste",
"list_of_items": "Dieser Abschnitt enthält die Liste der Artikel.",
"name": "Name",
"new_item": "Neuer Artikel",
"no_items": "Keine Artikel vorhanden!",
"price": "Preis",
"save_item": "Artikel speichern",
"select_a_unit": "wählen Sie die Einheit",
"title": "Artikel",
"unit": "Einheit",
"update_item": "Artikel ändern",
"updated_message": "Artikel erfolgreich aktualisiert"
},
"layout_login": {
"copyright_crater": "Copyright @ Crater - 2019",
"crater_help": "Crater helps you track expenses, record payments & generate beautiful",
"for_freelancer": "for Freelancers &",
"invoices_and_estimates": "invoices & estimates with ability to choose multiple templates.",
"small_businesses": "Small Businesses ",
"super_simple_invoicing": "Super Simple Invoicing"
},
"login": {
"email": "E-Mail",
"enter_email": "Geben Sie Ihre E-Mail ein",
"enter_password": "Geben Sie das Passwort ein",
"forgot_password": "Passwort vergessen?",
"login": "Anmelden",
"login_placeholder": "mail@example.com",
"or_signIn_with": "oder Anmelden mit",
"password": "Passwort",
"password_reset_successfully": "Passwort erfolgreich zurückgesetzt",
"register": "Registrieren",
"reset_password": "Passwort zurücksetzen",
"retype_password": "Passwort bestätigen"
},
"navigation": {
"customers": "Kunden",
"dashboard": "Übersicht",
"estimates": "Kostenvoranschläge",
"expenses": "Kosten",
"invoices": "Rechnungen",
"items": "Artikel",
"logout": "Abmelden",
"payments": "Zahlungen",
"reports": "Berichte",
"settings": "Einstellungen"
},
"payments": {
"action": "Aktion",
"add_new_payment": "Neue Zahlung hinzufügen",
"add_payment": "Zahlung hinzufügen",
"amount": "Summe",
"confirm_delete": "Sie können diese Zahlung nicht wiederherstellen. | Sie können diese Zahlungen nicht wiederherstellen.",
"created_message": "Zahlung erfolgreich erstellt",
"customer": "Kunden",
"date": "Datum",
"deleted_message": "Zahlung erfolgreich gelöscht | Zahlungen erfolgreich gelöscht",
"edit_payment": "Zahlung bearbeiten",
"invalid_amount_message": "Zahlungsbetrag ist ungültig",
"invoice": "Rechnung",
"list_of_payments": "Dieser Abschnitt enthält die Liste der Zahlungen.",
"new_payment": "Neue Zahlung",
"no_payments": "Keine Zahlungen vorhanden!",
"note": "Hinweis",
"payment": "Zahlung | Zahlungen",
"payment_mode": "Zahlungsart",
"payment_number": "Zahlungsnummer",
"payments_list": "Liste der Zahlungen",
"record_payment": "Zahlung eintragen",
"save_payment": "Zahlung speichern",
"select_payment_mode": "Wählen Sie den Zahlungsmodus",
"title": "Zahlungen",
"update_payment": "Zahlung ändern",
"updated_message": "Zahlung erfolgreich aktualisiert",
"view_payment": "Zahlung anzeigen"
},
"reports": {
"download_pdf": "Download PDF",
"errors": {
"required": "Feld ist erforderlich"
},
"estimates": {
"amount": "Summe",
"contact_name": "Ansprechpartner",
"due_date": "Fälligkeit",
"estimate": "Kostenvoranschlag",
"estimate_date": "Datum Kostenvoranschlag",
"estimate_number": "Kostenvoranschlag-Nr.",
"ref_number": "Ref-Nummer",
"status": "Status"
},
"expenses": {
"amount": "Summe",
"category": "Kategorie",
"date": "Datum",
"date_range": "Datumsbereich auswählen",
"expenses": "Aufwendungen",
"from_date": "Ab Datum",
"to_date": "bis Datum"
},
"from_date": "Ab Datum",
"invoices": {
"amount": "Summe",
"contact_name": "Ansprechpartner",
"due_date": "Fälligkeit",
"invoice": "Rechnung",
"invoice_date": "Rechnungsdatum",
"status": "Status"
},
"paid": "Bezahlt",
"profit_loss": {
"date_range": "Datumsbereich auswählen",
"from_date": "Ab Datum",
"profit_loss": "Gewinn & Verlust",
"to_date": "bis Datum"
},
"report": "Bericht | Berichte",
"sales": {
"date_range": "Datumsbereich auswählen",
"from_date": "Ab Datum",
"report_type": "Berichtstyp",
"sales": "Vertrieb",
"to_date": "bis Datum"
},
"status": "Status",
"taxes": {
"date_range": "Datumsbereich auswählen",
"from_date": "Ab Datum",
"taxes": "Steuern",
"to_date": "bis Datum"
},
"title": "Bericht",
"to_date": "bis Datum",
"unpaid": "Unbezahlt",
"update_report": "Bericht aktualisieren",
"view_pdf": "PDF anzeigen"
},
"settings": {
"account_settings": {
"account_settings": "Konto-Einstellungen",
"confirm_password": "Kennwort Bestätigen",
"email": "E-Mail",
"name": "Name",
"password": "Passwort",
"profile_picture": "Profil Bild",
"save": "Speichern",
"section_description": "Sie können Ihren Namen, Ihre E-Mail-Adresse und Ihr Passwort mit dem folgenden Formular aktualisieren.",
"updated_message": "Kontoeinstellungen erfolgreich aktualisiert"
},
"company_info": {
"address": "Adresse",
"city": "Stadt",
"company_info": "Firmeninfo",
"company_logo": "Firmenlogo",
"company_name": "Name des Unternehmens",
"country": "Land",
"phone": "Telefon",
"save": "Speichern",
"section_description": "Informationen zu Ihrem Unternehmen, die auf Rechnungen, Kostenvoranschlägen und anderen von Crater erstellten Dokumenten angezeigt werden.",
"state": "Bundesland",
"updated_message": "Unternehmensinformationen wurden erfolgreich aktualisiert",
"zip": "PLZ"
},
"currencies": {
"action": "Aktion",
"add_currency": "Währung einfügen",
"code": "Code",
"currencies_list": "Währungen Liste",
"currency": "Währung | Währungen",
"decimal_separator": "Decimal-Separator",
"left": "Links",
"name": "Name",
"position": "Position",
"position_of_symbol": "Position des Währungssymbol",
"precision": "Präzision",
"right": "Rechts",
"select_currency": "Währung wählen",
"symbol": "Symbol",
"thousand_separator": "Tausendertrennzeichen",
"title": "Währungen"
},
"customization": {
"addresses": {
"address": "Adresse",
"address_setting_updated": "Adresse-Einstellung erfolgreich aktualisiert",
"address_street_1": "Strasse",
"address_street_2": "Zusatz Strasse",
"city": "Stadt",
"company_address": "Firma Adresse",
"company_name": "Name des Unternehmens",
"contact": "Kontakt",
"country": "Land",
"customer_billing_address": "Rechnungsadresse des Kunden",
"customer_shipping_address": "Versand-Adresse des Kunden",
"display_name": "Anzeigename",
"email": "E-Mail",
"insert_fields": "Felder einfügen",
"name": "Name",
"phone": "Telefon",
"primary_contact_name": "Ansprechpartner",
"section_description": "Sie können die Rechnungsadresse und das Versandadressenformat des Kunden festlegen (nur in PDF angezeigt). ",
"state": "Bundesland",
"title": "Adressen",
"website": "Website",
"zip_code": "PLZ"
},
"customization": "Anpassung",
"estimates": {
"autogenerate_estimate_number": "Kostenvoranschlagsnummer automatisch generieren",
"enter_estimate_prefix": "Geben Sie das Kostenvoranschlag Präfix ein",
"estimate_prefix": "Kostenvoranschlag Präfix",
"estimate_setting_description": "Deaktivieren Sie diese Option, wenn Sie nicht jedes Mal, wenn Sie einen neue Kostenvoranschlag erstellen, automatisch eine Schätzung generieren möchten.",
"estimate_setting_updated": "Einstellungen Kostenvoranschläge erfolgreich aktualisiert",
"estimate_settings": "Einstellungen Kostenvoranschlag",
"title": "Kostenvoranschläge"
},
"invoices": {
"autogenerate_invoice_number": "Rechnungsnummer automatisch generieren",
"enter_invoice_prefix": "Rechnungspräfix eingeben",
"invoice_prefix": "Rechnung Präfix",
"invoice_setting_description": "Deaktivieren Sie diese Option, wenn Sie Rechnungsnummern nicht jedes Mal automatisch generieren möchten, wenn Sie eine neue Rechnung erstellen.",
"invoice_setting_updated": "Rechnungseinstellung erfolgreich aktualisiert",
"invoice_settings": "Rechnungseinstellungen",
"notes": "Hinweise",
"terms_and_conditions": "Allgemeine Geschäftsbedingungen",
"title": "Rechnungen"
},
"payments": {
"autogenerate_payment_number": "Zahlungsnummer automatisch generieren",
"enter_payment_prefix": "Zahlungspräfix eingeben",
"payment_prefix": "Zahlung Präfix",
"payment_setting_description": "Deaktivieren Sie diese Option, wenn Sie nicht jedes Mal, wenn Sie eine neue Zahlung erstellen, automatisch Zahlungsnummern generieren möchten.",
"payment_setting_updated": "Zahlungseinstellung erfolgreich aktualisiert",
"payment_settings": "Zahlung Einstellungen",
"title": "Zahlungen"
},
"save": "Speichern",
"updated_message": "Unternehmensinformationen wurden erfolgreich aktualisiert"
},
"date_format": "Datum-Format",
"expense_category": {
"action": "Aktion",
"add_new_category": "Neue Kategorie hinzufügen",
"already_in_use": "Kategorie wird bereits verwendet",
"category_description": "Beschreibung",
"category_name": "Kategorie Name",
"confirm_delete": "Sie können diese Ausgabenkategorie nicht wiederherstellen",
"created_message": "Ausgabenkategorie erfolgreich erstellt",
"deleted_message": "Ausgabenkategorie erfolgreich gelöscht",
"description": "Für das Hinzufügen von Ausgabeneinträgen sind Kategorien erforderlich. Sie können diese Kategorien nach Ihren Wünschen hinzufügen oder entfernen.",
"title": "Kategorien Kosten",
"updated_message": "Ausgabenkategorie erfolgreich aktualisiert"
},
"general": "Allgemeine",
"language": "Sprache",
"mail": {
"driver": "E-Mail Treiber",
"encryption": "E-Mail-Verschlüsselung",
"from_mail": "Von E-Mail-Adresse",
"from_name": "Von E-Mail-Namen",
"host": "E-Mail Mailserver",
"mail_config": "E-Mail-Konfiguration",
"mail_config_desc": "Unten finden Sie das Formular zum Konfigurieren des E-Mail-Treibers zum Senden von E-Mails über die App. Sie können auch Drittanbieter wie Sendgrid, SES usw. konfigurieren.",
"mailgun_domain": "Domain",
"mailgun_endpoint": "Mailgun-Endpunkt",
"mailgun_secret": "Mailgun Verschlüsselung",
"password": "E-Mail-Kennwort",
"port": "E-Mail Port",
"secret": "Verschlüsselung",
"ses_key": "SES-Taste",
"ses_secret": "SES Verschlüsselung",
"username": "E-Mail-Benutzername"
},
"menu_title": {
"account_settings": "Konto-Einstellungen",
"company_information": "Informationen zum Unternehmen",
"customization": "Anpassung",
"expense_category": "Ausgabenkategorien",
"notifications": "Benachrichtigungen",
"preferences": "Einstellungen",
"tax_types": "Steuersätze",
"update_app": "Update App"
},
"notification": {
"description": "Welche E-Mail-Benachrichtigungen möchten Sie erhalten wenn sich etwas ändert?",
"email": "Benachrichtigungen senden an",
"email_save_message": "Email erfolgreich gespeichert",
"estimate_viewed": "Kostenvoranschlag angesehen",
"estimate_viewed_desc": "Wenn Ihr Kunde den gesendeten Kostenvoranschlag anzeigt bekommt.",
"invoice_viewed": "Rechnung angezeigt",
"invoice_viewed_desc": "Wenn Ihr Kunde die gesendete Rechnung anzeigt bekommt.",
"please_enter_email": "Bitte E-Mail eingeben",
"save": "Speichern",
"title": "Benachrichtigung"
},
"pdf": {
"footer_text": "Fußzeile Text",
"pdf_layout": "PDF-Layout",
"title": "PDF-Einstellung"
},
"preferences": {
"currency": "Währung",
"date_format": "Datum-Format",
"discount_per_item": "Rabatt pro Artikel ",
"discount_setting": "Einstellung Rabatt",
"discount_setting_description": "Aktivieren Sie diese Option, wenn Sie einzelnen Rechnungspositionen einen Rabatt hinzufügen möchten. Standardmäßig wird der Rabatt direkt zur Rechnung hinzugefügt.",
"fiscal_year": "Geschäftsjahr",
"general_settings": "Standardeinstellungen für das System.",
"language": "Sprache",
"preference": "Präferenz | Präferenzen",
"save": "Speichern",
"select_date_formate": "select Date Formate",
"select_financial_year": "Geschäftsjahr auswählen",
"select_language": "Sprache auswählen",
"select_time_zone": "Zeitzone auswählen",
"time_zone": "Zeitzone",
"updated_message": "Einstellungen erfolgreich aktualisiert"
},
"primary_currency": "Primäre Währung",
"setting": "Einstellung | Einstellungen",
"tax_types": {
"action": "Aktion",
"add_new_tax": "Neuen Steuersatz hinzufügen",
"add_tax": "Steuersätze hinzufügen",
"already_in_use": "Steuersatz wird bereits verwendet",
"compound_tax": "Compound Tax",
"confirm_delete": "Sie können diesen Steuersatz nicht wiederherstellen",
"created_message": "Steuersatz erfolgreich erstellt",
"deleted_message": "Steuersatz erfolgreich gelöscht",
"description": "Sie können Steuern nach Belieben hinzufügen oder entfernen. Crater unterstützt Steuern auf einzelne Artikel sowie auf die Rechnung.",
"percent": "Prozent",
"tax_name": "Name des Steuersatzes",
"tax_per_item": "Steuersatz pro Artikel",
"tax_setting_description": "Aktivieren Sie diese Option, wenn Sie den Steuersatz zu einzelnen Rechnungspositionen hinzufügen möchten. Standardmäßig wird der Steuersatz direkt zur Rechnung hinzugefügt.",
"tax_settings": "Einstellungen Steuersatz",
"title": "Steuersätze",
"updated_message": "Steuersatz erfolgreich aktualisiert"
},
"timezone": "Zeitzone",
"title": "Einstellungen",
"update_app": {
"avail_update": "Neues Update verfügbar",
"check_update": "Nach Updates suchen",
"current_version": "Aktuelle Version",
"description": "Sie können Crater ganz einfach aktualisieren, indem Sie auf die Schaltfläche unten klicken, um nach einem neuen Update zu suchen.",
"latest_message": "Kein Update verfügbar! Du bist auf der neuesten Version.",
"next_version": "Nächste Version",
"progress_text": "Es dauert nur ein paar Minuten. Bitte aktualisieren Sie den Bildschirm nicht und schließen Sie das Fenster nicht, bevor das Update abgeschlossen ist.",
"title": "Update App",
"update": "Jetzt aktualisieren",
"update_progress": "Update läuft ...",
"update_success": "App wurde aktualisiert! Bitte warten Sie, während Ihr Browserfenster automatisch neu geladen wird."
},
"user_profile": {
"confirm_password": "Kennwort bestätigen",
"email": "E-Mail",
"name": "Name",
"password": "Passwort"
}
},
"tax_types": {
"compound_tax": "zusammengesetzte Steuer",
"description": "Beschreibung",
"name": "Name",
"percent": "Prozent"
},
"validation": {
"address_maxlength": "Die Adresse sollte nicht länger als 255 Zeichen sein.",
"amount_maxlength": "Der Betrag sollte nicht größer als 20 Ziffern sein.",
"amount_minvalue": "Betrag sollte größer als 0 sein.",
"characters_only": "Nur Zeichen.",
"description_maxlength": "Die Beschreibung sollte nicht länger als 255 Zeichen sein.",
"email_already_taken": "Die E-Mail ist bereits vergeben.",
"email_does_not_exist": "Benutzer mit der angegebenen E-Mail existiert nicht",
"email_incorrect": "Falsche E-Mail.",
"item_unit_already_taken": "Die Artikeleinheit wurde bereits vergeben",
"payment_mode_already_taken": "Der Zahlungsmodus wurde bereits verwendet",
"enter_valid_tax_rate": "Geben Sie einen gültige Steuersatz ein",
"invalid_url": "Ungültige URL (Bsp.: http://www.crater.com)",
"maximum_options_error": "Maximal {max} Optionen ausgewählt. Entfernen Sie zuerst eine ausgewählte Option, um eine andere auszuwählen.",
"name_min_length": "Name muss mindestens {count} Zeichen enthalten.",
"not_yet": "Noch erhalten? Erneut senden",
"notes_maxlength": "Notizen sollten nicht länger als 255 Zeichen sein.",
"numbers_only": "Nur Zahlen.",
"password_incorrect": "Passwörter müssen identisch sein",
"password_length": "Passwort muss {count} Zeichen lang sein.",
"password_min_length": "Password muß {count} Zeichen enthalten",
"payment_greater_than_due_amount": "Die eingegebene Zahlung ist mehr als der fällige Betrag dieser Rechnung.",
"payment_greater_than_zero": "Die Zahlung muss größer als 0 sein.",
"prefix_maxlength": "Das Präfix sollte nicht länger als 5 Zeichen sein.",
"price_greater_than_zero": "Preis muss größer als 0 sein.",
"price_maxlength": "Der Preis sollte nicht größer als 20 Ziffern sein.",
"price_minvalue": "Der Preis sollte größer als 0 sein.",
"qty_must_greater_than_zero": "Die Menge muss größer als 0 sein.",
"quantity_maxlength": "Die Menge sollte nicht größer als 20 Ziffern sein.",
"ref_number_maxlength": "Ref Number sollte nicht länger als 255 Zeichen sein.",
"required": "Feld ist erforderlich",
"send_reset_link": "Link zum Zurücksetzen senden"
},
"wizard": {
"account_info": "Account-Informationen",
"account_info_desc": "Die folgenden Details werden zum Erstellen des Hauptadministratorkontos verwendet. Sie können die Details auch jederzeit nach dem Anmelden ändern.",
"address": "Adresse",
"city": "Stadt",
"company_info": "Unternehmensinformationen",
"company_info_desc": "Diese Informationen werden auf Rechnungen angezeigt. Beachten Sie, dass Sie diese später auf der Einstellungsseite bearbeiten können.",
"company_logo": "Firmenlogo",
"company_name": "Firmenname",
"confirm_password": "Passwort bestätigen",
"continue": "Weiter",
"country": "Land",
"currency": "Currency",
"database": {
"app_url": "App-URL",
"connection": "Datenbank Verbindung",
"database": "URL der Seite & Datenbank",
"db_name": "Datenbank Name",
"desc": "Erstellen Sie eine Datenbank auf Ihrem Server und legen Sie die Anmeldeinformationen mithilfe des folgenden Formulars fest.",
"host": "Datenbank Host",
"password": "Datenbank Passwort",
"port": "Datenbank Port",
"username": "Datenbank Benutzername"
},
"date_format": "Datumsformat",
"email": "Email",
"errors": {
"connection_failed": "Datenbankverbindung fehlgeschlagen",
"database_should_be_empty": "Datenbank sollte leer sein",
"database_variables_save_error": "Konfiguration kann nicht in EN.env-Datei geschrieben werden. Bitte überprüfen Sie die Dateiberechtigungen.",
"mail_variables_save_error": "E-Mail-Konfiguration fehlgeschlagen.",
"migrate_failed": "Migration ist Fehlgeschlagen"
},
"fiscal_year": "Geschäftsjahr",
"from_address": "From Address",
"go_back": "Zurück",
"language": "Sprache",
"logo_preview": "Vorschau Logo",
"mail": {
"driver": "E-Mail-Treiber",
"encryption": "E-Mail-Verschlüsselung",
"from_mail": "Von E-Mail-Absenderadresse",
"from_name": "Von E-Mail-Absendername",
"host": "E-Mail-Host",
"mail_config": "E-Mail-Konfiguration",
"mail_config_desc": "Unten finden Sie das Formular zum Konfigurieren des E-Mail-Treibers zum Senden von E-Mails über die App. Sie können auch Drittanbieter wie Sendgrid, SES usw. konfigurieren.",
"mailgun_domain": "Domain",
"mailgun_endpoint": "Mailgun-Endpunkt",
"mailgun_secret": "Mailgun Verschlüsselung",
"password": "E-Mail-Passwort",
"port": "E-Mail-Port",
"secret": "Verschlüsselung",
"ses_key": "SES-Taste",
"ses_secret": "SES Verschlüsselung",
"username": "E-Mail-Benutzername"
},
"name": "Name",
"next": "Next",
"password": "Passwort",
"permissions": {
"permission_confirm_desc": "Prüfung der Berechtigung der Ordner fehlgeschlagen.",
"permission_confirm_title": "Sind Sie sicher, dass Sie fortfahren möchten?",
"permission_desc": "Unten finden Sie eine Liste der Ordnerberechtigungen, die erforderlich sind, damit die App funktioniert. Wenn die Berechtigungsprüfung fehlschlägt, müssen Sie Ihre Ordnerberechtigungen aktualisieren.",
"permissions": "Berechtigungen"
},
"phone": "Telefon",
"preferences": "Einstellungen",
"preferences_desc": "Standardeinstellungen für das System.",
"req": {
"check_req": "Anforderungen prüfen",
"php_req_version": "Php (version {version} erforderlich)",
"system_req": "System Anforderungen",
"system_req_desc": "Crater hat einige Serveranforderungen. Stellen Sie sicher, dass Ihr Server die erforderliche PHP-Version und alle unten genannten Erweiterungen hat."
},
"save_cont": "Speichern und weiter",
"skip": "Überspringen",
"state": "Bundesland",
"street": "Straße1 | Straße2",
"success": {
"database_variables_save_successfully": "Datenbank erfolgreich konfiguriert.",
"mail_variables_save_successfully": "E-Mail erfolgreich konfiguriert"
},
"time_zone": "Zeitzone",
"username": "Benutzername",
"zip_code": "Postleitzahl"
}
}

View File

@ -17,11 +17,17 @@
"save": "Save",
"cancel": "Cancel",
"update": "Update",
"deselect": "Deselect",
"download": "Download",
"from_date": "From Date",
"to_date": "To Date",
"from": "From",
"to": "To",
"sort_by": "Sort By",
"ascending": "Ascending",
"descending": "Descending",
"subject": "Subject",
"message": "Message",
"go_back": "Go Back",
"back_to_login": "Back to Login?",
"home": "Home",
@ -62,6 +68,8 @@
"four_zero_four": "404",
"you_got_lost": "Whoops! You got Lost!",
"go_home": "Go Home",
"test_mail_conf": "Test Mail Configuration",
"send_mail_successfully": "Mail sent successfully",
"setting_updated": "Setting updated successfully",
"select_state": "Select state",
@ -180,7 +188,7 @@
"no_items": "No items yet!",
"list_of_items": "This section will contain the list of items.",
"select_a_unit": "select unit",
"taxes": "Taxes",
"item_attached_message": "Cannot delete an item which is already in use",
"confirm_delete": "You will not be able to recover this Item | You will not be able to recover these Items",
"created_message": "Item created successfully",
@ -225,7 +233,7 @@
"record_payment": "Record Payment",
"add_estimate": "Add Estimate",
"save_estimate": "Save Estimate",
"confirm_conversion": "You want to convert this Estimate into Invoice?",
"confirm_conversion": "This estimate will be used to create a new Invoice.",
"conversion_message": "Invoice created 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",
@ -329,6 +337,9 @@
"no_matching_invoices": "There are no matching invoices!",
"mark_as_sent_successfully": "Invoice marked as sent successfully",
"send_invoice_successfully": "Invoice sent successfully",
"cloned_successfully": "Invoice cloned successfully",
"clone_invoice": "Clone Invoice",
"confirm_clone": "This invoice will be cloned into a new Invoice",
"item": {
"title": "Item Title",
"description": "Description",
@ -394,12 +405,17 @@
"edit_payment": "Edit Payment",
"view_payment": "View Payment",
"add_new_payment": "Add New Payment",
"send_payment_receipt": "Send Payment Receipt",
"save_payment": "Save Payment",
"update_payment": "Update Payment",
"payment": "Payment | Payments",
"no_payments": "No payments yet!",
"list_of_payments": "This section will contain the list of payments.",
"select_payment_mode": "Select payment mode",
"confirm_send_payment": "This payment will be sent via email to the customer",
"send_payment_successfully": "Payment sent successfully",
"user_email_does_not_exist": "User email does not exist",
"something_went_wrong": "something went wrong",
"confirm_delete": "You will not be able to recover this Payment | You will not be able to recover these Payments",
"created_message": "Payment created successfully",
@ -633,7 +649,7 @@
"notes": "Notes",
"invoice_prefix": "Invoice Prefix",
"invoice_settings": "Invoice Settings",
"autogenerate_invoice_number": "Autogenerate Invoice Number",
"autogenerate_invoice_number": "Auto-generate 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",
@ -644,7 +660,7 @@
"title": "Estimates",
"estimate_prefix": "Estimate Prefix",
"estimate_settings": "Estimate Settings",
"autogenerate_estimate_number": "Autogenerate Estimate Number",
"autogenerate_estimate_number": "Auto-generate 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"
@ -654,10 +670,30 @@
"title": "Payments",
"payment_prefix": "Payment Prefix",
"payment_settings": "Payment Settings",
"autogenerate_payment_number": "Autogenerate Payment Number",
"autogenerate_payment_number": "Auto-generate 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"
"payment_setting_updated": "Payment Setting updated successfully",
"payment_mode": "Payment Mode",
"add_payment_mode": "Add Payment Mode",
"mode_name": "Mode Name",
"payment_mode_added": "Payment Mode Added",
"payment_mode_updated": "Payment Mode Updated",
"payment_mode_confirm_delete":"You will not be able to recover this Payment Mode",
"already_in_use": "Payment Mode is already in use",
"deleted_message": "Payment Mode deleted successfully"
},
"items": {
"title": "Items",
"units": "units",
"add_item_unit": "Add Item Unit",
"unit_name": "Unit Name",
"item_unit_added": "Item Unit Added",
"item_unit_updated": "Item Unit Updated",
"item_unit_confirm_delete":"You will not be able to recover this Item unit",
"already_in_use": "Item Unit is already in use",
"deleted_message": "Item Unit deleted successfully"
}
},
"account_settings": {
@ -852,6 +888,8 @@
"email_incorrect": "Incorrect Email.",
"email_already_taken": "The email has already been taken.",
"email_does_not_exist": "User with given email doesn't exist",
"item_unit_already_taken": "This item unit name has already been taken",
"payment_mode_already_taken": "This payment mode name has already been taken",
"send_reset_link": "Send Reset Link",
"not_yet": "Not yet? Send it again",
"password_min_length": "Password must contain {count} characters",
@ -871,10 +909,13 @@
"amount_maxlength": "Amount should not be greater than 20 digits.",
"amount_minvalue": "Amount should be greater than 0.",
"description_maxlength": "Description should not be greater than 255 characters.",
"subject_maxlength": "Subject should not be greater than 100 characters.",
"message_maxlength": "Message 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.",
"address_maxlength": "Address 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."
"prefix_maxlength": "Prefix should not be greater than 5 characters.",
"something_went_wrong": "something went wrong"
}
}

View File

@ -784,6 +784,8 @@
"required": "Se requiere campo",
"email_incorrect": "Email incorrecto.",
"email_does_not_exist": " Usuario con correo electrónico dado no existe",
"item_unit_already_taken": "La unidad del artículo ya ha sido tomada",
"payment_mode_already_taken": "El modo de pago ya ha sido tomado",
"send_reset_link": "Enviar restablecer enlace",
"not_yet": "¿Aún no? Envialo de nuevo",
"password_min_length": "La contraseña debe contener {count} caracteres",

View File

@ -787,6 +787,8 @@
"required": "Champ requis",
"email_incorrect": "Adresse Email incorrecte.",
"email_does_not_exist": "L'utilisateur avec un email donné n'existe pas",
"item_unit_already_taken": "L'unité d'article a déjà été prise",
"payment_mode_already_taken": "Le mode de paiement a déjà été pris",
"send_reset_link": "Envoyer le lien de réinitialisation",
"not_yet": "Pas encore? Envoyer à nouveau",
"password_min_length": "Le mot de passe doit contenir {nombre} caractères",

View File

@ -4,6 +4,7 @@ import en from './en.json'
import fr from './fr.json'
import es from './es.json'
import ar from './ar.json'
import de from './de.json'
Vue.use(VueI18n)
@ -13,7 +14,8 @@ const i18n = new VueI18n({
en,
fr,
es,
ar
ar,
de
}
})

View File

@ -46,6 +46,7 @@ import InvoiceView from './views/invoices/View.vue'
// Payments
import PaymentsIndex from './views/payments/Index.vue'
import PaymentCreate from './views/payments/Create.vue'
import PaymentView from './views/payments/View.vue'
// Estimates
import EstimateIndex from './views/estimates/Index.vue'
@ -259,6 +260,11 @@ const routes = [
name: 'payments.edit',
component: PaymentCreate
},
{
path: 'payments/:id/view',
name: 'payments.view',
component: PaymentView
},
// Expenses
{

View File

@ -4,6 +4,8 @@ import * as userTypes from './modules/user/mutation-types'
import * as companyTypes from './modules/company/mutation-types'
import * as preferencesTypes from './modules/settings/preferences/mutation-types'
import * as taxTypeTypes from './modules/tax-type/mutation-types'
import * as itemTypes from './modules/item/mutation-types'
import * as paymentModes from './modules/payment/mutation-types'
export default {
bootstrap ({ commit, dispatch, state }) {
@ -17,6 +19,8 @@ export default {
commit('taxType/' + taxTypeTypes.BOOTSTRAP_TAX_TYPES, response.data.taxTypes)
commit('preferences/' + preferencesTypes.SET_MOMENT_DATE_FORMAT, response.data.moment_date_format)
commit('preferences/' + preferencesTypes.SET_LANGUAGE_FORMAT, response.data.default_language)
commit('item/' + itemTypes.SET_ITEM_UNITS, response.data.units)
commit('payment/' + paymentModes.SET_PAYMENT_MODES, response.data.paymentMethods)
commit(types.UPDATE_APP_LOADING_STATUS, true)
resolve(response)
}).catch((err) => {

View File

@ -135,6 +135,16 @@ export const markAsSent = ({ commit, dispatch, state }, data) => {
})
}
export const cloneInvoice = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post(`/api/invoices/clone`, data).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const searchInvoice = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/invoices?${data}`).then((response) => {

View File

@ -26,7 +26,6 @@ export const addItem = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post('/api/items', data).then((response) => {
commit(types.ADD_ITEM, response.data)
resolve(response)
}).catch((err) => {
reject(err)
@ -90,3 +89,57 @@ export const selectItem = ({ commit, dispatch, state }, data) => {
commit(types.SET_SELECT_ALL_STATE, false)
}
}
export const addItemUnit = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post(`/api/units`, data).then((response) => {
commit(types.ADD_ITEM_UNIT, response.data)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const updateItemUnit = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.put(`/api/units/${data.id}`, data).then((response) => {
commit(types.UPDATE_ITEM_UNIT, response.data)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const fetchItemUnits = ({ commit, dispatch, state }) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/units`).then((response) => {
commit(types.SET_ITEM_UNITS, response.data.units)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const fatchItemUnit = ({ commit, dispatch, state }, id) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/units/${id}`).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const deleteItemUnit = ({ commit, dispatch, state }, id) => {
return new Promise((resolve, reject) => {
window.axios.delete(`/api/units/${id}`).then((response) => {
commit(types.DELETE_ITEM_UNIT, id)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}

View File

@ -2,3 +2,4 @@ export const items = (state) => state.items
export const selectAllField = (state) => state.selectAllField
export const selectedItems = (state) => state.selectedItems
export const totalItems = (state) => state.totalItems
export const itemUnits = (state) => state.itemUnits

View File

@ -6,7 +6,8 @@ const initialState = {
items: [],
totalItems: 0,
selectAllField: false,
selectedItems: []
selectedItems: [],
itemUnits: []
}
export default {

View File

@ -6,3 +6,7 @@ export const DELETE_MULTIPLE_ITEMS = 'DELETE_MULTIPLE_ITEMS'
export const SET_SELECTED_ITEMS = 'SET_SELECTED_ITEMS'
export const SET_TOTAL_ITEMS = 'SET_TOTAL_ITEMS'
export const SET_SELECT_ALL_STATE = 'SET_SELECT_ALL_STATE'
export const ADD_ITEM_UNIT = 'ADD_ITEM_UNIT'
export const SET_ITEM_UNITS = 'SET_ITEM_UNITS'
export const UPDATE_ITEM_UNIT = 'UPDATE_ITEM_UNIT'
export const DELETE_ITEM_UNIT = 'DELETE_ITEM_UNIT'

View File

@ -39,6 +39,25 @@ export default {
[types.SET_SELECT_ALL_STATE] (state, data) {
state.selectAllField = data
}
},
[types.ADD_ITEM_UNIT] (state, data) {
state.itemUnits.push(data.unit)
state.itemUnits = [data.unit, ...state.itemUnits]
},
[types.SET_ITEM_UNITS] (state, data) {
state.itemUnits = data
},
[types.DELETE_ITEM_UNIT] (state, id) {
let index = state.itemUnits.findIndex(unit => unit.id === id)
state.itemUnits.splice(index, 1)
},
[types.UPDATE_ITEM_UNIT] (state, data) {
let pos = state.itemUnits.findIndex(unit => unit.id === data.unit.id)
state.itemUnits.splice(pos, 1)
state.itemUnits = [data.unit, ...state.itemUnits]
}
}

View File

@ -12,9 +12,9 @@ export const fetchPayments = ({ commit, dispatch, state }, params) => {
})
}
export const fetchCreatePayment = ({ commit, dispatch }, page) => {
export const fetchPayment = ({ commit, dispatch }, id) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payments/create`).then((response) => {
window.axios.get(`/api/payments/${id}`).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
@ -22,7 +22,7 @@ export const fetchCreatePayment = ({ commit, dispatch }, page) => {
})
}
export const fetchPayment = ({ commit, dispatch }, id) => {
export const fetchEditPaymentData = ({ commit, dispatch }, id) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payments/${id}/edit`).then((response) => {
resolve(response)
@ -32,6 +32,16 @@ export const fetchPayment = ({ commit, dispatch }, id) => {
})
}
export const fetchCreatePayment = ({ commit, dispatch }, page) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payments/create`).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const addPayment = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post('/api/payments', data).then((response) => {
@ -97,3 +107,78 @@ export const selectAllPayments = ({ commit, dispatch, state }) => {
commit(types.SET_SELECT_ALL_STATE, true)
}
}
export const addPaymentMode = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post(`/api/payment-methods`, data).then((response) => {
commit(types.ADD_PAYMENT_MODE, response.data)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const updatePaymentMode = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.put(`/api/payment-methods/${data.id}`, data).then((response) => {
commit(types.UPDATE_PAYMENT_MODE, response.data)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const fetchPaymentModes = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payment-methods`, data).then((response) => {
commit(types.SET_PAYMENT_MODES, response.data.paymentMethods)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const fetchPaymentMode = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payment-methods/${data.id}`, data).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const deletePaymentMode = ({ commit, dispatch, state }, id) => {
return new Promise((resolve, reject) => {
window.axios.delete(`/api/payment-methods/${id}`).then((response) => {
commit(types.DELETE_PAYMENT_MODE, id)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const sendEmail = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.post(`/api/payments/send`, data).then((response) => {
resolve(response)
}).catch((err) => {
reject(err)
})
})
}
export const searchPayment = ({ commit, dispatch, state }, data) => {
return new Promise((resolve, reject) => {
window.axios.get(`/api/payments?${data}`).then((response) => {
// commit(types.UPDATE_INVOICE, response.data)
resolve(response)
}).catch((err) => {
reject(err)
})
})
}

View File

@ -2,3 +2,4 @@ export const payments = (state) => state.payments
export const selectedPayments = (state) => state.selectedPayments
export const selectAllField = (state) => state.selectAllField
export const totalPayments = (state) => state.totalPayments
export const paymentModes = (state) => state.paymentModes

View File

@ -6,7 +6,8 @@ const initialState = {
payments: [],
totalPayments: 0,
selectAllField: false,
selectedPayments: []
selectedPayments: [],
paymentModes: []
}
export default {

View File

@ -6,3 +6,7 @@ export const DELETE_MULTIPLE_PAYMENTS = 'DELETE_MULTIPLE_PAYMENTS'
export const SET_SELECTED_PAYMENTS = 'SET_SELECTED_PAYMENTS'
export const SET_TOTAL_PAYMENTS = 'SET_TOTAL_PAYMENTS'
export const SET_SELECT_ALL_STATE = 'SET_SELECT_ALL_STATE'
export const ADD_PAYMENT_MODE = 'ADD_PAYMENT_MODE'
export const DELETE_PAYMENT_MODE = 'DELETE_PAYMENT_MODE'
export const SET_PAYMENT_MODES = 'SET_PAYMENT_MODES'
export const UPDATE_PAYMENT_MODE = 'UPDATE_PAYMENT_MODE'

View File

@ -33,5 +33,25 @@ export default {
[types.SET_SELECT_ALL_STATE] (state, data) {
state.selectAllField = data
},
[types.SET_PAYMENT_MODES] (state, data) {
state.paymentModes = data
},
[types.ADD_PAYMENT_MODE] (state, data) {
state.paymentModes.push(data.paymentMethod)
state.paymentModes = [data.paymentMethod, ...state.paymentModes]
},
[types.DELETE_PAYMENT_MODE] (state, id) {
let index = state.paymentModes.findIndex(paymentMethod => paymentMethod.id === id)
state.paymentModes.splice(index, 1)
},
[types.UPDATE_PAYMENT_MODE] (state, data) {
let pos = state.paymentModes.findIndex(paymentMethod => paymentMethod.id === data.paymentMethod.id)
state.paymentModes.splice(pos, 1)
state.paymentModes = [data.paymentMethod, ...state.paymentModes]
}
}

View File

@ -85,7 +85,8 @@
</div>
<div class="customer-content mb-1">
<label class="email">{{ selectedCustomer.name }}</label>
<label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label>
<label class="action" @click="editCustomer">{{ $t('general.edit') }}</label>
<label class="action" @click="removeCustomer">{{ $t('general.deselect') }}</label>
</div>
</div>
@ -195,6 +196,7 @@
:index="index"
:item-data="item"
:currency="currency"
:estimate-items="newEstimate.items"
:tax-per-item="taxPerItem"
:discount-per-item="discountPerItem"
@remove="removeItem"
@ -589,6 +591,14 @@ export default {
removeCustomer () {
this.resetSelectedCustomer()
},
editCustomer () {
this.openModal({
'title': this.$t('customers.edit_customer'),
'componentName': 'CustomerModal',
'id': this.selectedCustomer.id,
'data': this.selectedCustomer
})
},
openTemplateModal () {
this.openModal({
'title': this.$t('general.choose_template'),

View File

@ -24,6 +24,8 @@
:invalid="$v.item.name.$error"
:invalid-description="$v.item.description.$error"
:item="item"
:tax-per-item="taxPerItem"
:taxes="item.taxes"
@search="searchVal"
@select="onSelectItem"
@deselect="deselectItem"
@ -108,7 +110,7 @@
<div class="remove-icon-wrapper">
<font-awesome-icon
v-if="index > 0"
v-if="isShowRemoveItemIcon"
class="remove-icon"
icon="trash-alt"
@click="removeItem"
@ -180,6 +182,10 @@ export default {
discountPerItem: {
type: String,
default: ''
},
estimateItems: {
type: Array,
required: true
}
},
data () {
@ -221,6 +227,12 @@ export default {
return this.defaultCurrencyForInput
}
},
isShowRemoveItemIcon () {
if (this.estimateItems.length == 1) {
return false
}
return true
},
subtotal () {
return this.item.price * this.item.quantity
},
@ -324,6 +336,9 @@ export default {
created () {
window.hub.$on('checkItems', this.validateItem)
window.hub.$on('newItem', (val) => {
if (this.taxPerItem === 'YES') {
this.item.taxes = val.taxes
}
if (!this.item.item_id && this.modalActive && this.isSelected) {
this.onSelectItem(val)
}
@ -363,7 +378,13 @@ export default {
this.item.price = item.price
this.item.item_id = item.id
this.item.description = item.description
if (this.taxPerItem === 'YES' && item.taxes) {
let index = 0
item.taxes.forEach(tax => {
this.updateTax({index, item: { ...tax }})
index++
})
}
// if (this.item.taxes.length) {
// this.item.taxes = {...item.taxes}
// }

View File

@ -68,6 +68,14 @@ export default {
type: Boolean,
required: false,
default: false
},
taxPerItem: {
type: String,
default: ''
},
taxes: {
type: Array,
default: null
}
},
data () {
@ -129,7 +137,8 @@ export default {
this.$emit('onSelectItem')
this.openModal({
'title': 'Add Item',
'componentName': 'ItemModal'
'componentName': 'ItemModal',
'data': {taxPerItem: this.taxPerItem, taxes: this.taxes}
})
},
deselectItem () {

View File

@ -69,7 +69,9 @@
<font-awesome-icon icon="filter" />
</base-button>
</a>
<div class="filter-title">
{{ $t('general.sort_by') }}
</div>
<div class="filter-items">
<input
id="filter_estimate_date"
@ -107,7 +109,7 @@
<label class="inv-label" for="filter_estimate_number">{{ $t('estimates.estimate_number') }}</label>
</div>
</v-dropdown>
<base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData">
<base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData">
<font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" />
<font-awesome-icon v-else icon="sort-amount-down" />
</base-button>
@ -172,7 +174,12 @@ export default {
}
return false
},
getOrderName () {
if (this.getOrderBy) {
return this.$t('general.ascending')
}
return this.$t('general.descending')
},
shareableLink () {
return `/estimates/pdf/${this.estimate.unique_hash}`
}

View File

@ -83,7 +83,8 @@
</div>
<div class="customer-content mb-1">
<label class="email">{{ selectedCustomer.name }}</label>
<label class="action" @click="removeCustomer">{{ $t('general.remove') }}</label>
<label class="action" @click="editCustomer">{{ $t('general.edit') }}</label>
<label class="action" @click="removeCustomer">{{ $t('general.deselect') }}</label>
</div>
</div>
@ -193,6 +194,7 @@
:key="item.id"
:index="index"
:item-data="item"
:invoice-items="newInvoice.items"
:currency="currency"
:tax-per-item="taxPerItem"
:discount-per-item="discountPerItem"
@ -589,6 +591,14 @@ export default {
removeCustomer () {
this.resetSelectedCustomer()
},
editCustomer () {
this.openModal({
'title': this.$t('customers.edit_customer'),
'componentName': 'CustomerModal',
'id': this.selectedCustomer.id,
'data': this.selectedCustomer
})
},
openTemplateModal () {
this.openModal({
'title': this.$t('general.choose_template'),

View File

@ -259,6 +259,18 @@
{{ $t('invoices.mark_as_sent') }}
</a>
</v-dropdown-item>
<v-dropdown-item v-if="row.status === 'SENT' || row.status === 'VIEWED' || row.status === 'OVERDUE'">
<router-link :to="`/admin/payments/${row.id}/create`" class="dropdown-item">
<font-awesome-icon :icon="['fas', 'credit-card']" class="dropdown-item-icon"/>
{{ $t('payments.record_payment') }}
</router-link>
</v-dropdown-item>
<v-dropdown-item>
<a class="dropdown-item" href="#/" @click="onCloneInvoice(row.id)">
<font-awesome-icon icon="copy" class="dropdown-item-icon" />
{{ $t('invoices.clone_invoice') }}
</a>
</v-dropdown-item>
<v-dropdown-item>
<div class="dropdown-item" @click="removeInvoice(row.id)">
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
@ -378,7 +390,8 @@ export default {
'deleteMultipleInvoices',
'sendEmail',
'markAsSent',
'setSelectAllState'
'setSelectAllState',
'cloneInvoice'
]),
...mapActions('customer', [
'fetchCustomers'
@ -429,6 +442,27 @@ export default {
}
})
},
async onCloneInvoice (id) {
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('invoices.confirm_clone'),
icon: '/assets/icon/check-circle-solid.svg',
buttons: true,
dangerMode: true
}).then(async (value) => {
if (value) {
const data = {
id: id
}
let response = await this.cloneInvoice(data)
this.refreshTable()
if (response.data) {
window.toastr['success'](this.$tc('invoices.cloned_successfully'))
this.$router.push(`/admin/invoices/${response.data.invoice.id}/edit`)
}
}
})
},
getStatus (val) {
this.filters.status = {
name: val,

View File

@ -24,6 +24,8 @@
:invalid="$v.item.name.$error"
:invalid-description="$v.item.description.$error"
:item="item"
:tax-per-item="taxPerItem"
:taxes="item.taxes"
@search="searchVal"
@select="onSelectItem"
@deselect="deselectItem"
@ -109,7 +111,7 @@
<div class="remove-icon-wrapper">
<font-awesome-icon
v-if="index > 0"
v-if="showRemoveItemIcon"
class="remove-icon"
icon="trash-alt"
@click="removeItem"
@ -181,6 +183,10 @@ export default {
discountPerItem: {
type: String,
default: ''
},
invoiceItems: {
type: Array,
default: null
}
},
data () {
@ -222,6 +228,12 @@ export default {
return this.defaultCurrenctForInput
}
},
showRemoveItemIcon () {
if (this.invoiceItems.length == 1) {
return false
}
return true
},
subtotal () {
return this.item.price * this.item.quantity
},
@ -325,6 +337,9 @@ export default {
created () {
window.hub.$on('checkItems', this.validateItem)
window.hub.$on('newItem', (val) => {
if (this.taxPerItem === 'YES') {
this.item.taxes = val.taxes
}
if (!this.item.item_id && this.modalActive && this.isSelected) {
this.onSelectItem(val)
}
@ -364,7 +379,13 @@ export default {
this.item.price = item.price
this.item.item_id = item.id
this.item.description = item.description
if (this.taxPerItem === 'YES' && item.taxes) {
let index = 0
item.taxes.forEach(tax => {
this.updateTax({index, item: { ...tax }})
index++
})
}
// if (this.item.taxes.length) {
// this.item.taxes = {...item.taxes}
// }

View File

@ -66,6 +66,14 @@ export default {
type: Boolean,
required: false,
default: false
},
taxPerItem: {
type: String,
default: ''
},
taxes: {
type: Array,
default: null
}
},
data () {
@ -118,7 +126,8 @@ export default {
this.$emit('onSelectItem')
this.openModal({
'title': 'Add Item',
'componentName': 'ItemModal'
'componentName': 'ItemModal',
'data': {taxPerItem: this.taxPerItem, taxes: this.taxes}
})
},
deselectItem () {

View File

@ -73,7 +73,9 @@
<font-awesome-icon icon="filter" />
</base-button>
</a>
<div class="filter-title">
{{ $t('general.sort_by') }}
</div>
<div class="filter-items">
<input
id="filter_invoice_date"
@ -111,7 +113,7 @@
<label class="inv-label" for="filter_invoice_number">{{ $t('invoices.invoice_number') }}</label>
</div>
</v-dropdown>
<base-button class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData">
<base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData">
<font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" />
<font-awesome-icon v-else icon="sort-amount-down" />
</base-button>
@ -168,13 +170,18 @@ export default {
}
},
computed: {
getOrderBy () {
if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) {
return true
}
return false
},
getOrderName () {
if (this.getOrderBy) {
return this.$t('general.ascending')
}
return this.$t('general.descending')
},
shareableLink () {
return `/invoices/pdf/${this.invoice.unique_hash}`
}

View File

@ -50,11 +50,31 @@
<label>{{ $t('items.unit') }}</label>
<base-select
v-model="formData.unit"
:options="units"
:options="itemUnits"
:searchable="true"
:show-labels="false"
:placeholder="$t('items.select_a_unit')"
label="name"
>
<div slot="afterList">
<button type="button" class="list-add-button" @click="addItemUnit">
<font-awesome-icon class="icon" icon="cart-plus" />
<label>{{ $t('settings.customization.items.add_item_unit') }}</label>
</button>
</div>
</base-select>
</div>
<div v-if="isTaxPerItem" class="form-group">
<label>{{ $t('items.taxes') }}</label>
<base-select
v-model="formData.taxes"
:options="getTaxTypes"
:searchable="true"
:show-labels="false"
:allow-empty="true"
:multiple="true"
track-by="tax_type_id"
label="tax_name"
/>
</div>
<div class="form-group">
@ -66,7 +86,9 @@
@input="$v.formData.description.$touch()"
/>
<div v-if="$v.formData.description.$error">
<span v-if="!$v.formData.description.maxLength" class="text-danger">{{ $t('validation.description_maxlength') }}</span>
<span v-if="!$v.formData.description.maxLength" class="text-danger">
{{ $t('validation.description_maxlength') }}
</span>
</div>
</div>
<div class="form-group">
@ -102,24 +124,17 @@ export default {
return {
isLoading: false,
title: 'Add Item',
units: [
{ name: 'box', value: 'box' },
{ name: 'cm', value: 'cm' },
{ name: 'dz', value: 'dz' },
{ name: 'ft', value: 'ft' },
{ name: 'g', value: 'g' },
{ name: 'in', value: 'in' },
{ name: 'kg', value: 'kg' },
{ name: 'km', value: 'km' },
{ name: 'lb', value: 'lb' },
{ name: 'mg', value: 'mg' },
{ name: 'pc', value: 'pc' }
],
units: [],
taxes: [],
taxPerItem: '',
formData: {
name: '',
description: '',
price: '',
unit: null
unit_id: null,
unit: null,
taxes: [],
tax_per_item: false
},
money: {
decimal: '.',
@ -134,6 +149,9 @@ export default {
...mapGetters('currency', [
'defaultCurrencyForInput'
]),
...mapGetters('item', [
'itemUnits'
]),
price: {
get: function () {
return this.formData.price / 100
@ -142,14 +160,26 @@ export default {
this.formData.price = newValue * 100
}
},
...mapGetters('taxType', [
'taxTypes'
]),
isEdit () {
if (this.$route.name === 'items.edit') {
return true
}
return false
},
isTaxPerItem () {
return this.taxPerItem === 'YES' ? 1 : 0
},
getTaxTypes () {
return this.taxTypes.map(tax => {
return {...tax, tax_type_id: tax.id, tax_name: tax.name + ' (' + tax.percent + '%)'}
})
}
},
created () {
this.setTaxPerItem()
if (this.isEdit) {
this.loadEditData()
}
@ -177,10 +207,26 @@ export default {
'fetchItem',
'updateItem'
]),
...mapActions('modal', [
'openModal'
]),
async setTaxPerItem () {
let res = await axios.get('/api/settings/get-setting?key=tax_per_item')
if (res.data && res.data.tax_per_item === 'YES') {
this.taxPerItem = 'YES'
} else {
this.taxPerItem = 'FALSE'
}
},
async loadEditData () {
let response = await this.fetchItem(this.$route.params.id)
this.formData = response.data.item
this.formData.unit = this.units.find(_unit => response.data.item.unit === _unit.name)
this.formData = {...response.data.item, unit: null}
this.formData.taxes = response.data.item.taxes.map(tax => {
return {...tax, tax_name: tax.name + ' (' + tax.percent + '%)'}
})
this.formData.unit = this.itemUnits.find(_unit => response.data.item.unit_id === _unit.id)
this.fractional_price = response.data.item.price
},
async submitItem () {
@ -189,30 +235,40 @@ export default {
return false
}
if (this.formData.unit) {
this.formData.unit = this.formData.unit.name
this.formData.unit_id = this.formData.unit.id
}
let response
if (this.isEdit) {
this.isLoading = true
let response = await this.updateItem(this.formData)
if (response.data) {
this.isLoading = false
window.toastr['success'](this.$tc('items.updated_message'))
this.$router.push('/admin/items')
return true
}
window.toastr['error'](response.data.error)
response = await this.updateItem(this.formData)
} else {
this.isLoading = true
let response = await this.addItem(this.formData)
if (response.data) {
window.toastr['success'](this.$tc('items.created_message'))
this.$router.push('/admin/items')
this.isLoading = false
return true
let data = {
...this.formData,
taxes: this.formData.taxes.map(tax => {
return {
tax_type_id: tax.id,
amount: ((this.formData.price * tax.percent) / 100),
percent: tax.percent,
name: tax.name,
collective_tax: 0
}
})
}
window.toastr['success'](response.data.success)
response = await this.addItem(data)
}
if (response.data) {
this.isLoading = false
window.toastr['success'](this.$tc('items.updated_message'))
this.$router.push('/admin/items')
return true
}
window.toastr['error'](response.data.error)
},
async addItemUnit () {
this.openModal({
'title': 'Add Item Unit',
'componentName': 'ItemUnit'
})
}
}
}

View File

@ -64,7 +64,7 @@
<label class="form-label"> {{ $tc('items.unit') }} </label>
<base-select
v-model="filters.unit"
:options="units"
:options="itemUnits"
:searchable="true"
:show-labels="false"
:placeholder="$t('items.select_a_unit')"
@ -169,7 +169,7 @@
/>
<table-column
:label="$t('items.unit')"
show="unit"
show="unit_name"
/>
<table-column
:label="$t('items.price')"
@ -235,19 +235,6 @@ export default {
id: null,
showFilters: false,
sortedBy: 'created_at',
units: [
{ name: 'box', value: 'box' },
{ name: 'cm', value: 'cm' },
{ name: 'dz', value: 'dz' },
{ name: 'ft', value: 'ft' },
{ name: 'g', value: 'g' },
{ name: 'in', value: 'in' },
{ name: 'kg', value: 'kg' },
{ name: 'km', value: 'km' },
{ name: 'lb', value: 'lb' },
{ name: 'mg', value: 'mg' },
{ name: 'pc', value: 'pc' }
],
isRequestOngoing: true,
filtersApplied: false,
filters: {
@ -262,7 +249,8 @@ export default {
'items',
'selectedItems',
'totalItems',
'selectAllField'
'selectAllField',
'itemUnits'
]),
...mapGetters('currency', [
'defaultCurrency'
@ -296,6 +284,7 @@ export default {
deep: true
}
},
destroyed () {
if (this.selectAllField) {
this.selectAllItems()
@ -316,7 +305,7 @@ export default {
async fetchData ({ page, filter, sort }) {
let data = {
search: this.filters.name !== null ? this.filters.name : '',
unit: this.filters.unit !== null ? this.filters.unit.name : '',
unit_id: this.filters.unit !== null ? this.filters.unit.id : '',
price: this.filters.price * 100,
orderByField: sort.fieldName || 'created_at',
orderBy: sort.order || 'desc',
@ -395,7 +384,7 @@ export default {
}).then(async (willDelete) => {
if (willDelete) {
let res = await this.deleteMultipleItems()
if (res.data.success) {
if (res.data.success || res.data.items) {
window.toastr['success'](this.$tc('items.deleted_message', 2))
this.$refs.table.refresh()
} else if (res.data.error) {

View File

@ -109,12 +109,20 @@
<div class="form-group">
<label class="form-label">{{ $t('payments.payment_mode') }}</label>
<base-select
v-model="formData.payment_mode"
:options="getPaymentMode"
v-model="formData.payment_method"
:options="paymentModes"
:searchable="true"
:show-labels="false"
:placeholder="$t('payments.select_payment_mode')"
/>
label="name"
>
<div slot="afterList">
<button type="button" class="list-add-button" @click="addPaymentMode">
<font-awesome-icon class="icon" icon="cart-plus" />
<label>{{ $t('settings.customization.payments.add_payment_mode') }}</label>
</button>
</div>
</base-select>
</div>
</div>
<div class="col-sm-12 ">
@ -166,9 +174,10 @@ export default {
payment_number: null,
payment_date: null,
amount: 0,
payment_mode: null,
payment_method: null,
invoice_id: null,
notes: null
notes: null,
payment_method_id: null
},
money: {
decimal: '.',
@ -215,9 +224,9 @@ export default {
...mapGetters('currency', [
'defaultCurrencyForInput'
]),
getPaymentMode () {
return ['Cash', 'Check', 'Credit Card', 'Bank Transfer']
},
...mapGetters('payment', [
'paymentModes'
]),
amount: {
get: function () {
return this.formData.amount / 100
@ -286,14 +295,23 @@ export default {
'fetchCreatePayment',
'addPayment',
'updatePayment',
'fetchPayment'
'fetchEditPaymentData'
]),
...mapActions('modal', [
'openModal'
]),
invoiceWithAmount ({ invoice_number, due_amount }) {
return `${invoice_number} (${this.$utils.formatGraphMoney(due_amount, this.customer.currency)})`
},
async addPaymentMode () {
this.openModal({
'title': 'Add Payment Mode',
'componentName': 'PaymentMode'
})
},
async loadData () {
if (this.isEdit) {
let response = await this.fetchPayment(this.$route.params.id)
let response = await this.fetchEditPaymentData(this.$route.params.id)
this.customerList = response.data.customers
this.formData = { ...response.data.payment }
this.customer = response.data.payment.user
@ -301,6 +319,7 @@ export default {
this.formData.amount = parseFloat(response.data.payment.amount)
this.paymentPrefix = response.data.payment_prefix
this.paymentNumAttribute = response.data.nextPaymentNumber
this.formData.payment_method = response.data.payment.payment_method
if (response.data.payment.invoice !== null) {
this.maxPayableAmount = parseInt(response.data.payment.amount) + parseInt(response.data.payment.invoice.due_amount)
this.invoice = response.data.payment.invoice
@ -344,6 +363,7 @@ export default {
let data = {
editData: {
...this.formData,
payment_method_id: this.formData.payment_method.id,
payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY')
},
id: this.$route.params.id
@ -371,6 +391,7 @@ export default {
} else {
let data = {
...this.formData,
payment_method_id: this.formData.payment_method.id,
payment_date: moment(this.formData.payment_date).format('DD/MM/YYYY')
}
this.isLoading = true

View File

@ -67,10 +67,11 @@
<label class="form-label">{{ $t('payments.payment_mode') }}</label>
<base-select
v-model="filters.payment_mode"
:options="payment_mode"
:options="paymentModes"
:searchable="true"
:show-labels="false"
:placeholder="$t('payments.payment_mode')"
label="name"
/>
</div>
</div>
@ -203,6 +204,14 @@
{{ $t('general.edit') }}
</router-link>
</v-dropdown-item>
<v-dropdown-item>
<router-link :to="{path: `payments/${row.id}/view`}" class="dropdown-item">
<font-awesome-icon icon="eye" class="dropdown-item-icon" />
{{ $t('general.view') }}
</router-link>
</v-dropdown-item>
<v-dropdown-item>
<div class="dropdown-item" @click="removePayment(row.id)">
@ -237,7 +246,6 @@ export default {
sortedBy: 'created_at',
filtersApplied: false,
isRequestOngoing: true,
payment_mode: ['Cash', 'Check', 'Credit Card', 'Bank Transfer'],
filters: {
customer: null,
payment_mode: '',
@ -259,7 +267,8 @@ export default {
'selectedPayments',
'totalPayments',
'payments',
'selectAllField'
'selectAllField',
'paymentModes'
]),
selectField: {
get: function () {
@ -308,7 +317,7 @@ export default {
let data = {
customer_id: this.filters.customer !== null ? this.filters.customer.id : '',
payment_number: this.filters.payment_number,
payment_mode: this.filters.payment_mode ? this.filters.payment_mode : '',
payment_method_id: this.filters.payment_mode ? this.filters.payment_mode.id : '',
orderByField: sort.fieldName || 'created_at',
orderBy: sort.order || 'desc',
page

View File

@ -0,0 +1,286 @@
<template>
<div v-if="payment" class="main-content payment-view-page">
<div class="page-header">
<h3 class="page-title"> {{ payment.payment_number }}</h3>
<div class="page-actions row">
<base-button
:loading="isSendingEmail"
:disabled="isSendingEmail"
:outline="true"
color="theme"
@click="onPaymentSend"
>
{{ $t('payments.send_payment_receipt') }}
</base-button>
<v-dropdown :close-on-select="false" align="left" class="filter-container">
<a slot="activator" href="#">
<base-button color="theme">
<font-awesome-icon icon="ellipsis-h" />
</base-button>
</a>
<v-dropdown-item>
<router-link :to="{path: `/admin/payments/${$route.params.id}/edit`}" class="dropdown-item">
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon"/>
{{ $t('general.edit') }}
</router-link>
<div class="dropdown-item" @click="removePayment($route.params.id)">
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
{{ $t('general.delete') }}
</div>
</v-dropdown-item>
</v-dropdown>
</div>
</div>
<div class="payment-sidebar">
<div class="side-header">
<base-input
v-model="searchData.searchText"
:placeholder="$t('general.search')"
input-class="inv-search"
icon="search"
type="text"
align-icon="right"
@input="onSearch"
/>
<div
class="btn-group ml-3"
role="group"
aria-label="First group"
>
<v-dropdown :close-on-select="false" align="left" class="filter-container">
<a slot="activator" href="#">
<base-button class="inv-button inv-filter-fields-btn" color="default" size="medium">
<font-awesome-icon icon="filter" />
</base-button>
</a>
<div class="filter-title">
{{ $t('general.sort_by') }}
</div>
<div class="filter-items">
<input
id="filter_invoice_number"
v-model="searchData.orderByField"
type="radio"
name="filter"
class="inv-radio"
value="invoice_number"
@change="onSearch"
>
<label class="inv-label" for="filter_invoice_number">{{ $t('invoices.title') }}</label>
</div>
<div class="filter-items">
<input
id="filter_payment_date"
v-model="searchData.orderByField"
type="radio"
name="filter"
class="inv-radio"
value="payment_date"
@change="onSearch"
>
<label class="inv-label" for="filter_payment_date">{{ $t('payments.date') }}</label>
</div>
<div class="filter-items">
<input
id="filter_payment_number"
v-model="searchData.orderByField"
type="radio"
name="filter"
class="inv-radio"
value="payment_number"
@change="onSearch"
>
<label class="inv-label" for="filter_payment_number">{{ $t('payments.payment_number') }}</label>
</div>
</v-dropdown>
<base-button v-tooltip.top-center="{ content: getOrderName }" class="inv-button inv-filter-sorting-btn" color="default" size="medium" @click="sortData">
<font-awesome-icon v-if="getOrderBy" icon="sort-amount-up" />
<font-awesome-icon v-else icon="sort-amount-down" />
</base-button>
</div>
</div>
<base-loader v-if="isSearching" />
<div v-else class="side-content">
<router-link
v-for="(payment,index) in payments"
:to="`/admin/payments/${payment.id}/view`"
:key="index"
class="side-payment"
>
<div class="left">
<div class="inv-name">{{ payment.user.name }}</div>
<div class="inv-number">{{ payment.payment_number }}</div>
<div class="inv-number">{{ payment.invoice_number }}</div>
</div>
<div class="right">
<div class="inv-amount" v-html="$utils.formatMoney(payment.amount, payment.user.currency)" />
<div class="inv-date">{{ payment.formattedPaymentDate }}</div>
<!-- <div class="inv-number">{{ payment.payment_method.name }}</div> -->
</div>
</router-link>
<p v-if="!payments.length" class="no-result">
{{ $t('payments.no_matching_invoices') }}
</p>
</div>
</div>
<div class="payment-view-page-container" >
<iframe :src="`${shareableLink}`" class="frame-style"/>
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex'
const _ = require('lodash')
export default {
data () {
return {
id: null,
count: null,
payments: [],
payment: null,
currency: null,
searchData: {
orderBy: null,
orderByField: null,
searchText: null
},
isRequestOnGoing: false,
isSearching: false,
isSendingEmail: false,
isMarkingAsSent: false
}
},
computed: {
getOrderBy () {
if (this.searchData.orderBy === 'asc' || this.searchData.orderBy == null) {
return true
}
return false
},
getOrderName () {
if (this.getOrderBy) {
return this.$t('general.ascending')
}
return this.$t('general.descending')
},
shareableLink () {
return `/payments/pdf/${this.payment.unique_hash}`
}
},
watch: {
$route (to, from) {
this.loadPayment()
}
},
created () {
this.loadPayments()
this.loadPayment()
this.onSearch = _.debounce(this.onSearch, 500)
},
methods: {
// ...mapActions('invoice', [
// 'fetchInvoices',
// 'getRecord',
// 'searchInvoice',
// 'markAsSent',
// 'sendEmail',
// 'deleteInvoice',
// 'fetchViewInvoice'
// ]),
...mapActions('payment', [
'fetchPayments',
'fetchPayment',
'sendEmail',
'deletePayment',
'searchPayment'
]),
async loadPayments () {
let response = await this.fetchPayments()
if (response.data) {
this.payments = response.data.payments.data
}
},
async loadPayment () {
let response = await this.fetchPayment(this.$route.params.id)
if (response.data) {
this.payment = response.data.payment
}
},
async onSearch () {
let data = ''
if (this.searchData.searchText !== '' && this.searchData.searchText !== null && this.searchData.searchText !== undefined) {
data += `search=${this.searchData.searchText}&`
}
if (this.searchData.orderBy !== null && this.searchData.orderBy !== undefined) {
data += `orderBy=${this.searchData.orderBy}&`
}
if (this.searchData.orderByField !== null && this.searchData.orderByField !== undefined) {
data += `orderByField=${this.searchData.orderByField}`
}
this.isSearching = true
let response = await this.searchPayment(data)
this.isSearching = false
if (response.data) {
this.payments = response.data.payments.data
}
},
sortData () {
if (this.searchData.orderBy === 'asc') {
this.searchData.orderBy = 'desc'
this.onSearch()
return true
}
this.searchData.orderBy = 'asc'
this.onSearch()
return true
},
async onPaymentSend () {
window.swal({
title: this.$tc('general.are_you_sure'),
text: this.$tc('payments.confirm_send_payment'),
icon: '/assets/icon/paper-plane-solid.svg',
buttons: true,
dangerMode: true
}).then(async (value) => {
if (value) {
this.isSendingEmail = true
let response = await this.sendEmail({id: this.payment.id})
this.isSendingEmail = false
if (response.data.success) {
window.toastr['success'](this.$tc('payments.send_payment_successfully'))
return true
}
if (response.data.error === 'user_email_does_not_exist') {
window.toastr['error'](this.$tc('payments.user_email_does_not_exist'))
return false
}
window.toastr['error'](this.$tc('payments.something_went_wrong'))
}
})
},
async removePayment (id) {
this.id = id
window.swal({
title: 'Deleted',
text: 'you will not be able to recover this payment!',
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (value) => {
if (value) {
let request = await this.deletePayment(this.id)
if (request.data.success) {
window.toastr['success'](this.$tc('payments.deleted_message', 1))
this.$router.push('/admin/payments')
} else if (request.data.error) {
window.toastr['error'](request.data.message)
}
}
})
}
}
}
</script>

View File

@ -11,12 +11,15 @@
<li class="tab" @click="setActiveTab('PAYMENTS')">
<a :class="['tab-link', {'a-active': activeTab === 'PAYMENTS'}]" href="#">{{ $t('settings.customization.payments.title') }}</a>
</li>
<li class="tab" @click="setActiveTab('ITEMS')">
<a :class="['tab-link', {'a-active': activeTab === 'ITEMS'}]" href="#">{{ $t('settings.customization.items.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">
<form action="" class="mt-3" @submit.prevent="updateInvoiceSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.invoices.invoice_prefix') }}</label>
@ -32,7 +35,7 @@
<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="row pb-3">
<div class="col-md-12">
<base-button
icon="save"
@ -43,25 +46,23 @@
</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>
<hr>
<div class="page-header pt-3">
<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>
@ -71,7 +72,7 @@
<!-- Estimates Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'ESTIMATES'" class="estimate-tab">
<form action="" class="form-section" @submit.prevent="updateEstimateSetting">
<form action="" class="mt-3" @submit.prevent="updateEstimateSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.estimates.estimate_prefix') }}</label>
@ -87,7 +88,7 @@
<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="row pb-3">
<div class="col-md-12">
<base-button
icon="save"
@ -100,23 +101,21 @@
</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 class="page-header pt-3">
<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>
@ -126,7 +125,66 @@
<!-- Payments Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'PAYMENTS'" class="payment-tab">
<form action="" class="form-section" @submit.prevent="updatePaymentSetting">
<div class="page-header">
<div class="row">
<div class="col-md-8">
<!-- <h3 class="page-title">
{{ $t('settings.customization.payments.payment_mode') }}
</h3> -->
</div>
<div class="col-md-4 d-flex flex-row-reverse">
<base-button
outline
class="add-new-tax"
color="theme"
@click="addPaymentMode"
>
{{ $t('settings.customization.payments.add_payment_mode') }}
</base-button>
</div>
</div>
</div>
<table-component
ref="table"
:show-filter="false"
:data="paymentModes"
table-class="table tax-table"
class="mb-3"
>
<table-column
:sortable="true"
:label="$t('settings.customization.payments.payment_mode')"
show="name"
/>
<table-column
:sortable="false"
:filterable="false"
cell-class="action-dropdown"
>
<template slot-scope="row">
<span>{{ $t('settings.tax_types.action') }}</span>
<v-dropdown>
<a slot="activator" href="#">
<dot-icon />
</a>
<v-dropdown-item>
<div class="dropdown-item" @click="editPaymentMode(row)">
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
{{ $t('general.edit') }}
</div>
</v-dropdown-item>
<v-dropdown-item>
<div class="dropdown-item" @click="removePaymentMode(row.id)">
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
{{ $t('general.delete') }}
</div>
</v-dropdown-item>
</v-dropdown>
</template>
</table-column>
</table-component>
<hr>
<form action="" class="pt-3" @submit.prevent="updatePaymentSetting">
<div class="row">
<div class="col-md-12 mb-4">
<label class="input-label">{{ $t('settings.customization.payments.payment_prefix') }}</label>
@ -142,7 +200,7 @@
<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="row pb-3">
<div class="col-md-12">
<base-button
icon="save"
@ -155,33 +213,97 @@
</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 class="page-header pt-3">
<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>
</transition>
<!-- Items Tab -->
<transition name="fade-customize">
<div v-if="activeTab === 'ITEMS'" class="item-tab">
<div class="page-header">
<div class="row">
<div class="col-md-8">
<!-- <h3 class="page-title">
{{ $t('settings.customization.items.title') }}
</h3> -->
</div>
<div class="col-md-4 d-flex flex-row-reverse">
<base-button
outline
class="add-new-tax"
color="theme"
@click="addItemUnit"
>
{{ $t('settings.customization.items.add_item_unit') }}
</base-button>
</div>
</div>
</div>
<table-component
ref="itemTable"
:show-filter="false"
:data="itemUnits"
table-class="table tax-table"
class="mb-3"
>
<table-column
:sortable="true"
:label="$t('settings.customization.items.units')"
show="name"
/>
<table-column
:sortable="false"
:filterable="false"
cell-class="action-dropdown"
>
<template slot-scope="row">
<span>{{ $t('settings.tax_types.action') }}</span>
<v-dropdown>
<a slot="activator" href="#">
<dot-icon />
</a>
<v-dropdown-item>
<div class="dropdown-item" @click="editItemUnit(row)">
<font-awesome-icon :icon="['fas', 'pencil-alt']" class="dropdown-item-icon" />
{{ $t('general.edit') }}
</div>
</v-dropdown-item>
<v-dropdown-item>
<div class="dropdown-item" @click="removeItemUnit(row.id)">
<font-awesome-icon :icon="['fas', 'trash']" class="dropdown-item-icon" />
{{ $t('general.delete') }}
</div>
</v-dropdown-item>
</v-dropdown>
</template>
</table-column>
</table-component>
</div>
</transition>
</div>
</div>
</template>
<script>
import { validationMixin } from 'vuelidate'
import { mapActions, mapGetters } from 'vuex'
const { required, maxLength, alpha } = require('vuelidate/lib/validators')
export default {
mixins: [validationMixin],
@ -204,9 +326,20 @@ export default {
payments: {
payment_prefix: null
},
items: {
units: []
},
currentData: null
}
},
computed: {
...mapGetters('item', [
'itemUnits'
]),
...mapGetters('payment', [
'paymentModes'
])
},
watch: {
activeTab () {
this.loadData()
@ -239,6 +372,15 @@ export default {
this.loadData()
},
methods: {
...mapActions('modal', [
'openModal'
]),
...mapActions('payment', [
'deletePaymentMode'
]),
...mapActions('item', [
'deleteItemUnit'
]),
async setInvoiceSetting () {
let data = {
key: 'invoice_auto_generate',
@ -259,6 +401,78 @@ export default {
window.toastr['success'](this.$t('general.setting_updated'))
}
},
async addItemUnit () {
this.openModal({
'title': 'Add Item Unit',
'componentName': 'ItemUnit'
})
this.$refs.itemTable.refresh()
},
async editItemUnit (data) {
this.openModal({
'title': 'Edit Item Unit',
'componentName': 'ItemUnit',
'id': data.id,
'data': data
})
this.$refs.itemTable.refresh()
},
async removeItemUnit (id) {
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('settings.customization.items.item_unit_confirm_delete'),
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (value) => {
if (value) {
let response = await this.deleteItemUnit(id)
if (response.data.success) {
window.toastr['success'](this.$t('settings.customization.items.deleted_message'))
this.id = null
this.$refs.itemTable.refresh()
return true
}
window.toastr['error'](this.$t('settings.customization.items.already_in_use'))
}
})
},
async addPaymentMode () {
this.openModal({
'title': 'Add Payment Mode',
'componentName': 'PaymentMode'
})
this.$refs.table.refresh()
},
async editPaymentMode (data) {
this.openModal({
'title': 'Edit Payment Mode',
'componentName': 'PaymentMode',
'id': data.id,
'data': data
})
this.$refs.table.refresh()
},
removePaymentMode (id) {
swal({
title: this.$t('general.are_you_sure'),
text: this.$t('settings.customization.payments.payment_mode_confirm_delete'),
icon: '/assets/icon/trash-solid.svg',
buttons: true,
dangerMode: true
}).then(async (value) => {
if (value) {
let response = await this.deletePaymentMode(id)
if (response.data.success) {
window.toastr['success'](this.$t('settings.customization.payments.deleted_message'))
this.id = null
this.$refs.table.refresh()
return true
}
window.toastr['error'](this.$t('settings.customization.payments.already_in_use'))
}
})
},
changeToUppercase (currentTab) {
if (currentTab === 'INVOICES') {
this.invoices.invoice_prefix = this.invoices.invoice_prefix.toUpperCase()

View File

@ -15,7 +15,19 @@
:mail-drivers="mail_drivers"
@on-change-driver="(val) => mail_driver = mailConfigData.mail_driver = val"
@submit-data="saveEmailConfig"
/>
>
<base-button
:loading="loading"
outline
class="pull-right mt-4 ml-2"
icon="check"
color="theme"
type="button"
@click="openMailTestModal"
>
{{ $t('general.test_mail_conf') }}
</base-button>
</component>
</div>
</div>
</div>
@ -27,6 +39,7 @@ import Smtp from './mailDriver/Smtp'
import Mailgun from './mailDriver/Mailgun'
import Ses from './mailDriver/Ses'
import Basic from './mailDriver/Basic'
import { mapActions } from 'vuex'
export default {
components: {
@ -50,6 +63,9 @@ export default {
this.loadData()
},
methods: {
...mapActions('modal', [
'openModal'
]),
async loadData () {
this.loading = true
@ -79,6 +95,12 @@ export default {
} catch (e) {
window.toastr['error']('Something went wrong')
}
},
openMailTestModal () {
this.openModal({
'title': 'Test Mail Configuration',
'componentName': 'MailTestModal'
})
}
}
}

View File

@ -73,15 +73,18 @@
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<div class="d-flex">
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<slot/>
</div>
</form>
</template>
<script>

View File

@ -167,15 +167,18 @@
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<div class="d-flex">
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<slot/>
</div>
</form>
</template>
<script>

View File

@ -146,15 +146,18 @@
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<div class="d-flex">
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<slot/>
</div>
</form>
</template>
<script>

View File

@ -146,15 +146,18 @@
</div>
</div>
</div>
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<div class="d-flex">
<base-button
:loading="loading"
class="pull-right mt-4"
icon="save"
color="theme"
type="submit"
>
{{ $t('general.save') }}
</base-button>
<slot/>
</div>
</form>
</template>
<script>

View File

@ -54,7 +54,6 @@
:placeholder="$t('general.select_country')"
track-by="id"
label="name"
@input="fetchState()"
/>
<div v-if="$v.companyData.country_id.$error">
<span v-if="!$v.companyData.country_id.required" class="text-danger">{{ $tc('validation.required') }}</span>

View File

@ -0,0 +1,44 @@
.item-unit-modal {
.card-footer {
display: flex;
justify-content: flex-end;
padding: 20px 20px;
}
.input-label {
text-align: end;
padding-right: 0;
position: relative;
}
.required {
position: absolute;
// left: -10px;
color: $ls-color-red;
}
.compound-tax-toggle {
display: flex;
align-items: center;
margin-top: 9px;
}
}
@media(max-width: $x-small-breakpoint ) {
.base-modal {
.item-unit-modal {
width: 100vw;
.input-label {
text-align: left;
}
}
}
}

View File

@ -0,0 +1,44 @@
.mail-test-modal {
.card-footer {
display: flex;
justify-content: flex-end;
padding: 20px 20px;
}
.input-label {
text-align: end;
padding-right: 0;
position: relative;
}
.required {
position: absolute;
margin-left: 4px;
color: $ls-color-red;
}
.compound-tax-toggle {
display: flex;
align-items: center;
margin-top: 9px;
}
}
@media(max-width: $x-small-breakpoint ) {
.base-modal {
.mail-test-modal {
width: 100vw;
.input-label {
text-align: left;
}
}
}
}

View File

@ -0,0 +1,44 @@
.payment-modes-modal {
.card-footer {
display: flex;
justify-content: flex-end;
padding: 20px 20px;
}
.input-label {
text-align: end;
padding-right: 0;
position: relative;
}
.required {
position: absolute;
// left: -10px;
color: $ls-color-red;
}
.compound-tax-toggle {
display: flex;
align-items: center;
margin-top: 9px;
}
}
@media(max-width: $x-small-breakpoint ) {
.base-modal {
.payment-modes-modal-modal {
width: 100vw;
.input-label {
text-align: left;
}
}
}
}

View File

@ -0,0 +1,109 @@
.tooltip {
display: block !important;
z-index: 10000;
.tooltip-inner {
background: black;
color: white;
border-radius: 16px;
padding: 5px 10px 4px;
}
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: black;
z-index: 1;
}
&[x-placement^="top"] {
margin-bottom: 5px;
.tooltip-arrow {
border-width: 5px 5px 0 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
bottom: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="bottom"] {
margin-top: 5px;
.tooltip-arrow {
border-width: 0 5px 5px 5px;
border-left-color: transparent !important;
border-right-color: transparent !important;
border-top-color: transparent !important;
top: -5px;
left: calc(50% - 5px);
margin-top: 0;
margin-bottom: 0;
}
}
&[x-placement^="right"] {
margin-left: 5px;
.tooltip-arrow {
border-width: 5px 5px 5px 0;
border-left-color: transparent !important;
border-top-color: transparent !important;
border-bottom-color: transparent !important;
left: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&[x-placement^="left"] {
margin-right: 5px;
.tooltip-arrow {
border-width: 5px 0 5px 5px;
border-top-color: transparent !important;
border-right-color: transparent !important;
border-bottom-color: transparent !important;
right: -5px;
top: calc(50% - 5px);
margin-left: 0;
margin-right: 0;
}
}
&.popover {
$color: #f9f9f9;
.popover-inner {
background: $color;
color: black;
padding: 24px;
border-radius: 5px;
box-shadow: 0 5px 30px rgba(black, .1);
}
.popover-arrow {
border-color: $color;
}
}
&[aria-hidden='true'] {
visibility: hidden;
opacity: 0;
transition: opacity .15s, visibility .15s;
}
&[aria-hidden='false'] {
visibility: visible;
opacity: 1;
transition: opacity .15s;
}
}

View File

@ -49,6 +49,7 @@
@import "components/base/base-switch";
@import 'components/base/base-loader/index';
@import 'components/base/base-prefix-input';
@import 'components/v-tooltips.scss';
// Components
@ -72,6 +73,9 @@
@import "components/item-select";
@import "components/tax-select";
@import "components/avatar-cropper";
@import "components/payment-modes-modal";
@import "components/item-unit-modal.scss";
@import "components/mail-test-modal.scss";
// Modals
@ -107,5 +111,6 @@
@import 'pages/reports.scss';
@import 'pages/customers';
@import 'pages/payments.scss';
@import 'pages/payment-view.scss';
@import 'pages/items.scss';
@import 'pages/statuses.scss';

View File

@ -158,6 +158,7 @@
border: 1px solid #eaf1fb;
box-sizing: border-box;
color: $ls-color-gray--dark;
box-shadow: none !important;
}
}
@ -190,8 +191,17 @@
.filter-container {
margin-left: 12px;
.filter-title {
padding: 5px 10px;
border-bottom: 1px solid rgba(185, 193, 209, 0.41);
margin-bottom: 10px;
}
.filter-items {
display: flex;
padding: 4px 9px;
cursor: pointer;
&:first-child {
margin-top: auto;
@ -202,11 +212,12 @@
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 18px;
line-height: 12px;
text-transform: capitalize;
color: $ls-color-black;
margin-bottom: 6px;
margin-left: 10px;
cursor: pointer;
}
.base-input {
@ -214,7 +225,7 @@
}
.dropdown-container {
padding: 11px;
padding: 0px !important;
left: auto;
right: 0px;
width: 166px;

View File

@ -87,7 +87,7 @@
font-size: 14px;
font-weight: 500;
color: $ls-color-primary;
margin: 0 0 0 0;
margin: 0 9px 0 0;
position: relative;
}
}

View File

@ -113,6 +113,7 @@
border: 1px solid $ls-color-gray--light;
box-sizing: border-box;
color: $ls-color-gray;
box-shadow: none !important;
}
}
@ -148,9 +149,18 @@
.filter-container {
margin-left: 12px;
.filter-title {
padding: 5px 10px;
border-bottom: 1px solid rgba(185, 193, 209, 0.41);
margin-bottom: 10px;
}
.filter-items {
// margin-top: 10px;
display: flex;
padding: 4px 9px;
cursor: pointer;
&:first-child {
margin-top: auto;
@ -162,11 +172,12 @@
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 18px;
line-height: 12px;
text-transform: capitalize;
color: $ls-color-black;
margin-bottom: 6px;
margin-left: 10px;
cursor: pointer;
}
.base-input {
@ -174,7 +185,7 @@
}
.dropdown-container {
padding: 11px;
padding: 0px !important;
left: auto;
right: 0px;
width: 155px;

View File

@ -0,0 +1,238 @@
// Payments - View
// -------------------------
.payment-view-page {
padding-left: 570px !important;
.payment-sidebar {
width: 300px;
height: 100vh;
height: 100%;
left: 240px;
padding: 60px 0 10px;
position: fixed;
top: 0;
width: 300px;
z-index: 25;
background: #FFFFFF;
}
.inv-search {
background: $ls-color-gray--very-light !important;
}
.side-payment {
padding: 12px 16px;
display: flex;
justify-content: space-between;
border-bottom: 1px solid rgba(185, 193, 209, 0.41);
cursor: pointer;
&:last-child {
margin-bottom: 98px;
}
&:hover {
background-color: $ls-color-gray--very-light;
}
.left {
.inv-name {
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 21px;
text-transform: capitalize;
color: $ls-color-black;
margin-bottom: 6px;
}
.inv-number {
font-style: normal;
font-weight: 500;
font-size: 12px;
line-height: 18px;
color: $ls-color-gray--dark;
margin-bottom: 6px;
}
.inv-status {
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
padding: 2px 10px;
display: inline-block;
}
}
.right {
.inv-amount {
font-style: normal;
font-weight: 600;
font-size: 20px;
line-height: 30px;
text-align: right;
color: $ls-color-black--light;
}
.inv-date {
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 21px;
text-align: right;
color: $ls-color-gray--dark;
}
}
}
.no-result {
color: $ls-color-gray;
display: flex;
justify-content: center;
margin-top: 20px;
}
.side-header {
height: 100px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 30px 15px;
border-bottom: 1px solid rgba(185, 193, 209, 0.41);
.inv-button {
background: $ls-color-gray--very-light;
border: 1px solid $ls-color-gray--light;
box-sizing: border-box;
color: $ls-color-gray;
box-shadow: none !important;
}
}
.side-content {
overflow-y: scroll;
height: 100%;
}
.payment-view-page-container {
display: flex;
flex-direction: column;
height: 75vh;
min-height: 0;
overflow:hidden;
}
.frame-style {
flex: 1 1 auto;
border: 1px solid $ls-color-gray;
border-radius: 7px;
}
.inv-filter-fields-btn, .inv-filter-sorting-btn {
&:focus {
border-color: inherit;
box-shadow: none;
outline: none !important;
}
}
.filter-container {
margin-left: 12px;
.filter-title {
padding: 5px 10px;
border-bottom: 1px solid rgba(185, 193, 209, 0.41);
margin-bottom: 10px;
}
.filter-items {
// margin-top: 10px;
display: flex;
padding: 4px 9px;
cursor: pointer;
&:first-child {
margin-top: auto;
}
}
.inv-label {
font-style: normal;
font-weight: normal;
font-size: 14px;
line-height: 12px;
text-transform: capitalize;
color: $ls-color-black;
margin-bottom: 6px;
margin-left: 10px;
cursor: pointer;
}
.base-input {
width: 20%;
}
.dropdown-container {
padding: 0px !important;
left: auto;
right: 0px;
width: 167px;
}
}
.filter-payment-date {
.vdp-datepicker {
div {
.vdp-datepicker__clear-button {
margin-left: -21px;
margin-top: 2px;
font-size: 20px;
font-weight: 800;
}
}
}
}
.date-group {
display: flex
}
.to-text {
padding: 8px;
}
}
@media (max-width: $small-breakpoint) {
.payment-view-page {
padding-left: 310px !important;
}
.payment-sidebar {
transition: .2s all;
left: 0px !important;
}
}

View File

@ -104,9 +104,28 @@
}
}
.payment-tab {
.dropdown-container {
right: 0;
left: auto;
}
}
.item-tab {
.dropdown-container {
right: 0;
left: auto;
}
}
.add-new-tax {
height: 45px;
white-space: nowrap;
z-index: 1;
}
.flex-box {

View File

@ -2,7 +2,6 @@
<html>
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
@ -19,13 +18,18 @@
border-collapse: collapse;
}
hr {
.header-line {
color:rgba(0, 0, 0, 0.2);
position: absolute;
top: 90px;
left: 0px;
width: 100%;
}
hr {
margin: 0 30px 0 30px;
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.header-center {
@ -250,8 +254,7 @@
.table2 {
margin-top: 35px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
@ -265,6 +268,7 @@
text-align: center;
color: rgba(0, 0, 0, 0.85);
padding: 5px;
padding-bottom: 10px;
}
tr.main-table-header th {
@ -275,6 +279,10 @@
line-height: 18px;
}
.main-table-header {
margin-bottom: 10px;
}
tr.item-details td {
font-style: normal;
font-weight: normal;
@ -287,6 +295,7 @@
color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 5px;
padding-top: 10px;
}
.padd8 {
@ -379,7 +388,7 @@
</td>
</tr>
</table>
<hr class="header-line" style="border: 0.620315px solid #E8E8E8;" />
<hr class="header-line" />
</div>
<div class="wrapper">
<div class="address">

View File

@ -2,9 +2,7 @@
<html>
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: "DejaVu Sans";
@ -25,7 +23,6 @@
display:inline-block;
width:30%;
}
@page {
margin-top: 60px !important;
}
@ -80,7 +77,8 @@
}
.address {
display: inline-block;
display: block;
padding-top: 20px;
}
.company {
padding: 0 0 0 30px;
@ -110,8 +108,8 @@
/* -------------------------- */
/* billing style */
.bill-address-container {
display: inline;
position: absolute;
display: block;
/* position: absolute; */
float:right;
padding: 0 40px 0 0;
}
@ -159,8 +157,7 @@
/* -------------------------- */
/* shipping style */
.ship-address-container {
display: inline;
position: absolute;
display: block;
float:right;
padding: 0 30px 0 0;
}
@ -247,13 +244,18 @@
}
.table2 {
margin-top: 200px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
margin-top: 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
hr {
margin: 0 30px 0 30px;
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.table2 hr {
height:0.1px;
}
@ -285,6 +287,7 @@
color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 5px;
padding-top: 10px;
}
.note-header {
@ -317,6 +320,16 @@
page-break-after: auto;
}
.text-per-item-table3 {
border: 1px solid #EAF1FB;
border-top: none;
padding-right: 30px;
box-sizing: border-box;
width: 260px;
/* height: 100px; */
position: absolute;
right: -25;
}
.inv-item {
border-color: #d9d9d9;
@ -402,6 +415,7 @@
<h1 class="header-logo"> {{$estimate->user->company->name}} </h1>
@endif
@endif
</td>
<td width="40%" class="header-right company-details">
<h1>Estimate</h1>
<h4>{{$estimate->estimate_number}}</h4>
@ -426,6 +440,7 @@
@endif
@include('app.pdf.estimate.partials.billing-address')
</div>
<div style="clear: both;"></div>
</div>
@include('app.pdf.estimate.partials.table')
@include('app.pdf.estimate.partials.notes')

View File

@ -2,7 +2,6 @@
<html>
<head>
<title>Estimate</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
@ -18,7 +17,7 @@
border-collapse: collapse;
}
hr {
.header-line {
color:rgba(0, 0, 0, 0.2);
position: absolute;
top: 80px;
@ -27,6 +26,15 @@
width: 100%;
}
hr {
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.items-table-hr{
margin: 0 30px 0 30px;
}
.header-left {
padding-top: 45px;
padding-bottom: 45px;
@ -34,6 +42,7 @@
display:inline-block;
width:30%;
}
.header-table {
position: absolute;
width: 100%;
@ -87,7 +96,7 @@
.address {
display: inline-block;
padding-top: 20px
padding-top: 100px
}
.bill-add {
@ -201,8 +210,7 @@
.job-add {
display: inline;
position: absolute;
display: block;
float: right;
padding: 20px 30px 0 0;
}
@ -251,9 +259,7 @@
line-height: 18px;
}
.table2 {
margin-top: 188px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
@ -417,7 +423,9 @@
</tr>
</table>
</div>
<hr style="border: 0.620315px solid #E8E8E8;">
<hr class="header-line">
<div class="wrapper">
<div class="address">
<div class="bill-add">
@ -450,6 +458,7 @@
</tr>
</table>
</div>
<div style="clear: both;"></div>
</div>
@include('app.pdf.estimate.partials.table')
@include('app.pdf.estimate.partials.notes')

View File

@ -3,6 +3,6 @@
<div class="notes-label">
Notes
</div>
{{$estimate->notes}}
{!! nl2br(htmlspecialchars($estimate->notes)) !!}
</div>
@endif

View File

@ -1,17 +1,13 @@
<table width="100%" class="table2" cellspacing="0" border="0">
<tr class="main-table-header">
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">#</th>
@if($estimate->discount_per_item === 'NO')
<th width="80%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
@else
<th width="40%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
@endif
<th width="17%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Quantity</th>
<th width="18%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 40px">Price</th>
<th width="2%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">#</th>
<th width="40%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Quantity</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Price</th>
@if($estimate->discount_per_item === 'YES')
<th width="10%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-left: 10px">Discount</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-left: 10px">Discount</th>
@endif
<th width="15%" class="ItemTableHeader" style="text-align: right; color: #55547A;">Amount</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A;">Amount</th>
</tr>
@php
$index = 1
@ -32,7 +28,7 @@
<span
style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;"
>
{{ $item->description }}
{!! nl2br(htmlspecialchars($item->description)) !!}
</span>
</td>
<td
@ -43,7 +39,7 @@
</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 40px"
style="text-align: right; color: #040405; padding-right: 20px"
>
{!! format_money_pdf($item->price, $estimate->user->currency) !!}
</td>
@ -67,7 +63,9 @@
@endforeach
</table>
<table width="100%" cellspacing="0px" style="margin-left:420px" border="0" class="table3 @if(count($estimate->items) > 12) page-break @endif">
<hr class="items-table-hr">
<table width="100%" cellspacing="0px" style="margin-left:420px;margin-top: 10px" border="0" class="table3 @if(count($estimate->items) > 12) page-break @endif">
<tr>
<td class="no-borde" style="color: #55547A; padding-left:10px; font-size:12px;">Subtotal</td>
<td class="no-border items"

View File

@ -2,7 +2,6 @@
<html>
<head>
<title>Invoice</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
@ -20,13 +19,18 @@
border-collapse: collapse;
}
hr {
.header-line {
color:rgba(0, 0, 0, 0.2);
position: absolute;
top: 90px;
left: 0px;
width: 100%;
}
hr {
margin: 0 30px 0 30px;
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.header-center {
@ -251,8 +255,7 @@
.table2 {
margin-top: 35px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
@ -289,6 +292,7 @@
color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 5px;
padding-top: 10px;
}
.padd8 {

View File

@ -78,7 +78,8 @@
}
.address {
display: inline-block;
display: block;
padding-top: 20px;
}
.company {
padding: 0 0 0 30px;
@ -108,8 +109,8 @@
/* -------------------------- */
/* billing style */
.bill-address-container {
display: inline;
position: absolute;
display: block;
/* position: absolute; */
float:right;
padding: 0 40px 0 0;
}
@ -157,8 +158,7 @@
/* -------------------------- */
/* shipping style */
.ship-address-container {
display: inline;
position: absolute;
display: block;
float:right;
padding: 0 30px 0 0;
}
@ -245,13 +245,18 @@
}
.table2 {
margin-top: 200px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
margin-top: 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
hr {
margin: 0 30px 0 30px;
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.table2 hr {
height:0.1px;
}
@ -283,6 +288,7 @@
color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 5px;
padding-top: 10px;
}
.note-header {
@ -435,8 +441,9 @@
@endif
@include('app.pdf.invoice.partials.billing-address')
</div>
<div style="clear: both;"></div>
</div>
@include('app.pdf.invoice.partials.table')
@include('app.pdf.invoice.partials.table')
@include('app.pdf.invoice.partials.notes')
</div>
</body>

View File

@ -18,7 +18,7 @@
border-collapse: collapse;
}
hr {
.header-line {
color:rgba(0, 0, 0, 0.2);
position: absolute;
top: 80px;
@ -27,6 +27,16 @@
width: 100%;
}
hr {
color:rgba(0, 0, 0, 0.2);
border: 0.5px solid #EAF1FB;
}
.items-table-hr{
margin: 0 30px 0 30px;
}
.header-left {
padding-top: 45px;
padding-bottom: 45px;
@ -86,7 +96,7 @@
.address {
display: inline-block;
padding-top: 20px
padding-top: 100px;
}
.bill-add {
@ -200,8 +210,7 @@
.job-add {
display: inline;
position: absolute;
display: block;
float: right;
padding: 20px 30px 0 0;
}
@ -250,9 +259,7 @@
line-height: 18px;
}
.table2 {
margin-top: 188px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
padding: 0px 30px 10px 30px;
page-break-before: avoid;
page-break-after: auto;
}
@ -427,7 +434,9 @@
</tr>
</table>
</div>
<hr style="border: 0.620315px solid #E8E8E8;">
<hr class="header-line">
<div class="wrapper">
<div class="address">
<div class="bill-add">
@ -460,6 +469,7 @@
</tr>
</table>
</div>
<div style="clear: both;"></div>
</div>
@include('app.pdf.invoice.partials.table')
@include('app.pdf.invoice.partials.notes')

View File

@ -3,6 +3,6 @@
<div class="notes-label">
Notes
</div>
{!! $invoice->notes !!}
{!! nl2br(htmlspecialchars($invoice->notes)) !!}
</div>
@endif

View File

@ -1,17 +1,13 @@
<table width="100%" class="table2" cellspacing="0" border="0">
<tr class="main-table-header">
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">#</th>
@if($invoice->discount_per_item === 'NO')
<th width="80%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
@else
<th width="40%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
@endif
<th width="17%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Quantity</th>
<th width="18%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 40px">Price</th>
<th width="2%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">#</th>
<th width="40%" class="ItemTableHeader" style="text-align: left; color: #55547A; padding-left: 0px">Items</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Quantity</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-right: 20px">Price</th>
@if($invoice->discount_per_item === 'YES')
<th width="10%" class="ItemTableHeader" style="text-align: right; color: #55547A; padding-left: 10px">Discount</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A; padding-left: 10px">Discount</th>
@endif
<th width="15%" class="ItemTableHeader" style="text-align: right; color: #55547A;">Amount</th>
<th class="ItemTableHeader" style="text-align: right; color: #55547A;">Amount</th>
</tr>
@php
$index = 1
@ -29,7 +25,7 @@
style="text-align: left; color: #040405;padding-left: 0px"
>
<span>{{ $item->name }}</span><br>
<span style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;">{{ $item->description }}</span>
<span style="text-align: left; color: #595959; font-size: 9px; font-weight:300; line-height: 12px;">{!! nl2br(htmlspecialchars($item->description)) !!}</span>
</td>
<td
class="inv-item items"
@ -39,7 +35,7 @@
</td>
<td
class="inv-item items"
style="text-align: right; color: #040405; padding-right: 40px"
style="text-align: right; color: #040405; padding-right: 20px"
>
{!! format_money_pdf($item->price, $invoice->user->currency) !!}
</td>
@ -66,10 +62,12 @@
@endforeach
</table>
<table width="100%" cellspacing="0px" style="margin-left:420px" border="0" class="table3 @if(count($invoice->items) > 12) page-break @endif">
<hr class="items-table-hr">
<table width="100%" cellspacing="0px" style="margin-left:420px; margin-top: 10px" border="0" class="table3 @if(count($invoice->items) > 12) page-break @endif">
<tr>
<td class="no-borde" style="color: #55547A; padding-left:10px; font-size:12px;">Subtotal</td>
<td class="no-border items"
<td class="no-border" style="color: #55547A; padding-left:10px; font-size:12px;">Subtotal</td>
<td class="no-border items padd2"
style="padding-right:10px; text-align: right; font-size:12px; color: #040405; font-weight: 500;">{!! format_money_pdf($invoice->sub_total, $invoice->user->currency) !!}</td>
</tr>

View File

@ -0,0 +1,33 @@
@if($payment->user->billingaddress)
<p class="bill-to">Received From:</p>
@if($payment->user->billingaddress->name)
<p class="bill-user-name">
{{$payment->user->billingaddress->name}}
</p>
@endif
<p class="bill-user-address">
@if($payment->user->billingaddress->address_street_1)
{{$payment->user->billingaddress->address_street_1}}<br>
@endif
@if($payment->user->billingaddress->address_street_2)
{{$payment->user->billingaddress->address_street_2}}<br>
@endif
@if($payment->user->billingaddress->city && $payment->user->billingaddress->city)
{{$payment->user->billingaddress->city}},
@endif
@if($payment->user->billingaddress->state && $payment->user->billingaddress->state)
{{$payment->user->billingaddress->state}}.
@endif
@if($payment->user->billingaddress->zip)
{{$payment->user->billingaddress->zip}}<br>
@endif
@if($payment->user->billingaddress->country && $payment->user->billingaddress->country->name)
{{$payment->user->billingaddress->country->name}}<br>
@endif
@if($payment->user->billingaddress->phone)
<p class="bill-user-phone">
Phone :{{$payment->user->billingaddress->phone}}
</p>
@endif
</p>
@endif

View File

@ -0,0 +1,30 @@
@if($payment->user->company)
<h1 class="company-name"> {{$payment->user->company->name}} </h1>
@endif
@if($company_address)
<p class="company-add">
@if($company_address->addresses[0]['address_street_1'])
{{$company_address->addresses[0]['address_street_1']}} <br>
@endif
@if($company_address->addresses[0]['address_street_2'])
{{$company_address->addresses[0]['address_street_2']}} <br>
@endif
@if($company_address->addresses[0]['city'])
{{$company_address->addresses[0]['city']}}
@endif
@if($company_address->addresses[0]['state'])
{{$company_address->addresses[0]['state']}}
@endif
@if($company_address->addresses[0]['zip'])
{{$company_address->addresses[0]['zip']}} <br>
@endif
@if($company_address->addresses[0]['country'])
{{$company_address->addresses[0]['country']->name}} <br>
@endif
@if($company_address->addresses[0]['phone'])
{{$company_address->addresses[0]['phone']}} <br>
@endif
</p>
@endif

View File

@ -0,0 +1,40 @@
@if($payment->user->shippingaddress)
<p class="ship-to">Ship To,</p>
@if($payment->user->shippingaddress->name)
<p class="ship-user-name">
{{$payment->user->shippingaddress->name}}
</p>
@endif
<p class="ship-user-address">
@if($payment->user->shippingaddress->address_street_1)
{{$payment->user->shippingaddress->address_street_1}}<br>
@endif
@if($payment->user->shippingaddress->address_street_2)
{{$payment->user->shippingaddress->address_street_2}}<br>
@endif
@if($payment->user->shippingaddress->city && $payment->user->shippingaddress->city)
{{$payment->user->shippingaddress->city}},
@endif
@if($payment->user->shippingaddress->state && $payment->user->shippingaddress->state)
{{$payment->user->shippingaddress->state}}.
@endif
@if($payment->user->shippingaddress->zip)
{{$payment->user->shippingaddress->zip}}<br>
@endif
@if($payment->user->shippingaddress->country && $payment->user->shippingaddress->country->name)
{{$payment->user->shippingaddress->country->name}}<br>
@endif
@if($payment->user->phone)
<p class="ship-user-phone">
Phone :{{$payment->user->shippingaddress->phone}}
</p>
@endif
</p>
@endif

View File

@ -0,0 +1,524 @@
<!DOCTYPE html>
<html>
<head>
<title>Invoice</title>
{{-- <link href="https://fonts.googleapis.com/css?family=Poppins&display=swap" rel="stylesheet"> --}}
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<style type="text/css">
body {
font-family: "DejaVu Sans";
}
html {
margin: 0px;
padding: 0px;
}
table {
border-collapse: collapse;
}
hr {
color:rgba(0, 0, 0, 0.2);
position: absolute;
top: 80px;
left: 0px;
right: -70px;
width: 100%;
}
.header-left {
padding-top: 45px;
padding-bottom: 45px;
padding-left: 30px;
display:inline-block;
width:30%;
}
.header-table {
position: absolute;
width: 100%;
height: 150px;
left: 0px;
top: -60px;
}
.header-logo {
position: absolute;
height: 50px;
text-transform: capitalize;
color: #817AE3;
}
.header-right {
display:inline-block;
position: absolute;
right:0;
padding: 15px 30px 15px 0px;
float: right;
}
.inv-flex{
display:flex;
}
.inv-data{
text-align:right;
margin-right:120px;
}
.inv-value{
text-align:left;
margin-left:160px;
}
.header {
font-size: 20px;
color: rgba(0, 0, 0, 0.7);
}
.TextColor1 {
font-size: 16px;
color: rgba(0, 0, 0, 0.5);
}
@page {
margin-top: 60px !important;
}
.wrapper {
display: block;
height: 200px;
}
.address {
display: inline-block;
padding-top: 20px
}
.bill-add {
display: block;
float:left;
width:40%;
padding: 0 0 0 30px;
}
.company {
padding-left: 30px;
display: inline;
float:left;
width:30%;
}
.company h1 {
font-style: normal;
font-weight: bold;
font-size: 18px;
line-height: 22px;
letter-spacing: 0.05em;
}
.company-add {
text-align: left;
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
color: #595959;
margin: 0px;
}
/* -------------------------- */
/* shipping style */
.ship-to {
padding-top: 5px;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
margin-bottom: 0px;
}
.ship-user-name {
padding: 0px;
font-style: normal;
font-weight: normal;
font-size: 15px;
line-height: 22px;
margin: 0px;
}
.ship-user-address {
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
color: #595959;
margin: 0px;
width: 160px;
}
.ship-user-phone {
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
color: #595959;
margin: 0px;
}
/* -------------------------- */
/* billing style */
.bill-to {
padding-top: 5px;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
margin-bottom: 0px;
color: #55547A;
}
.bill-user-name {
padding: 0px;
font-style: normal;
font-weight: normal;
font-size: 15px;
line-height: 22px;
margin: 0px;
}
.bill-user-address {
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
color: #595959;
margin:0px;
width: 160px;
}
.bill-user-phone {
font-style: normal;
font-weight: normal;
font-size: 10px;
line-height: 15px;
color: #595959;
margin: 0px;
}
.job-add {
display: inline;
position: absolute;
float: right;
width: 40%;
height: 120px;
padding: 20px 30px 0 0;
}
.amount-due {
background-color: #f2f2f2;
}
.textRight {
text-align: right;
}
.textLeft {
text-align: left;
}
.textStyle1 {
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
}
.textStyle2 {
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
text-align: right;
}
.main-table-header td {
padding: 10px;
}
.main-table-header {
border-bottom: 1px solid red;
}
tr.main-table-header th {
font-style: normal;
font-weight: 600;
font-size: 12px;
line-height: 18px;
}
tr.item-details td {
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 18px;
}
.table2 {
margin-top: 188px;
border-bottom: 1px solid #EAF1FB;
padding: 0px 30px 0 30px;
page-break-before: avoid;
page-break-after: auto;
}
.table2 hr {
height:0.1px;
}
.ItemTableHeader {
font-size: 13.5;
text-align: center;
color: rgba(0, 0, 0, 0.85);
padding: 5px;
}
.items {
font-size: 13;
color: rgba(0, 0, 0, 0.6);
text-align: center;
padding: 5px;
}
.note-header {
font-size: 13;
color: rgba(0, 0, 0, 0.6);
}
.note-text {
font-size: 10;
color: rgba(0, 0, 0, 0.6);
}
.padd8 {
padding-top: 8px;
padding-bottom: 8px;
}
.padd2 {
padding-top: 2px;
padding-bottom: 2px;
}
.table3 {
border: 1px solid #EAF1FB;
border-top: none;
box-sizing: border-box;
width: 630px;
page-break-inside: avoid;
page-break-before: auto;
page-break-after: auto;
}
.text-per-item-table3 {
border: 1px solid #EAF1FB;
border-top: none;
padding-right: 30px;
box-sizing: border-box;
width: 260px;
/* height: 100px; */
position: absolute;
right: -25;
}
td.invoice-total1 {
text-align:left;
padding: 15px 0 15px 10px;
font-size:12px;
line-height: 18px;
color: #55547A;
border-bottom:1px solid #E8E8E8;
border-top:1px solid #E8E8E8;
border-left:1px solid #E8E8E8;
}
td.invoice-total2 {
font-weight: 500;
text-align: right;
font-size:12px;
line-height: 18px;
padding: 15px 10px 15px 0;
color: #5851DB;
border-bottom:1px solid #E8E8E8;
border-top:1px solid #E8E8E8;
border-right:1px solid #E8E8E8;
}
.inv-item {
border-color: #d9d9d9;
}
.no-border {
border: none;
}
.desc {
font-weight: 100;
text-align: justify;
font-size: 10px;
margin-bottom: 15px;
margin-top:7px;
color:rgba(0, 0, 0, 0.85);
}
.company-details h1 {
margin:0;
font-style: normal;
font-weight: bold;
font-size: 15px;
line-height: 22px;
letter-spacing: 0.05em;
text-align: left;
max-width: 220px;
}
.company-details h4 {
margin:0;
font-style: normal;
font-weight: 100;
font-size: 18px;
line-height: 25px;
text-align: right;
}
.company-details h3 {
margin-bottom:1px;
margin-top:0;
}
tr.total td {
border-bottom:1px solid #E8E8E8;
border-top:1px solid #E8E8E8;
}
.notes {
font-style: normal;
font-weight: 300;
font-size: 12px;
color: #595959;
margin-top: 15px;
margin-left: 30px;
width: 442px;
text-align: left;
page-break-inside: avoid;
}
.notes-label {
font-style: normal;
font-weight: normal;
font-size: 15px;
line-height: 22px;
letter-spacing: 0.05em;
color: #040405;
width: 108px;
height: 19.87px;
padding-bottom: 10px;
}
.content-header {
margin-top: 120px;
width: 100%;
text-align: center;
}
p {
padding: 0 0 0 0;
margin: 0 0 0 0;
}
.content-header span {
font-weight: 500;
font-size: 14px;
line-height: 25px;
border-bottom: 1px solid #B9C1D1;
}
.total-amount {
width: 315px;
display: block;
margin-right: 30px;
background: #F9FBFF;
border: 1px solid #EAF1FB;
box-sizing: border-box;
float: right;
padding: 12px 15px 15px 15px;
}
.total-amount-label {
display: inline;
font-weight: 600;
font-size: 14px;
line-height: 21px;
color: #595959;
}
.total-amount span {
float: right;
font-weight: 500;
font-size: 14px;
line-height: 21px;
text-align: right;
color: #5851D8;
}
</style>
</head>
<body>
<div class="header-table">
<table width="100%">
<tr>
@if($logo)
<td class="header-left">
<img class="header-logo" src="{{ $logo }}" alt="Company Logo">
@else
@if($payment->user->company)
<td class="header-left" style="padding-top:0px;">
<h1 class="header-logo"> {{$payment->user->company->name}} </h1>
@endif
@endif
</td>
<td class="header-right company-details">
@include('app.pdf.payment.partials.company-address')
</td>
</tr>
</table>
</div>
<hr style="border: 0.620315px solid #E8E8E8;">
<p class="content-header">
<span>PAYMENT RECEIPT</span>
</p>
<div class="wrapper">
<div class="address">
<div class="bill-add">
<div style="float:left;">
@include('app.pdf.payment.partials.billing-address')
</div>
<div style="float:right;">
</div>
<div style="clear: both;"></div>
</div>
<div class="job-add">
<table width="100%">
<tr>
<td class="textStyle1" style="text-align: left; color: #55547A">Payment Date</td>
<td class="textStyle2"> &nbsp;{{$payment->formattedPaymentDate}}</td>
</tr>
<tr>
<td class="textStyle1" style="text-align: left; color: #55547A">Payment Number</td>
<td class="textStyle2"> &nbsp;{{$payment->payment_number}}</td>
</tr>
<tr>
<td class="textStyle1" style="text-align: left; color: #55547A">Payment Mode</td>
<td class="textStyle2"> &nbsp;{{$payment->paymentMethod->name}}</td>
</tr>
@if ($payment->invoice && $payment->invoice->invoice_number)
<tr>
<td class="textStyle1" style="text-align: left; color: #55547A">Invoice</td>
<td class="textStyle2"> &nbsp;{{$payment->invoice->invoice_number}}</td>
</tr>
@endif
</table>
</div>
</div>
<div style="clear: both;"></div>
</div>
<div class="total-amount">
<p class="total-amount-label">Amount Received</p>
<span>{!! format_money_pdf($payment->amount, $payment->user->currency) !!}</span>
</div>
</body>
</html>

View File

@ -0,0 +1,31 @@
@component('mail::layout')
{{-- Header --}}
@slot('header')
@component('mail::header', ['url' => ''])
@if($data['company']['logo'])
<img class="header-logo" src="{{asset($data['company']['logo'])}}" alt="{{$data['company']['name']}}">
@else
{{$data['company']['name']}}
@endif
@endcomponent
@endslot
{{-- Body --}}
<!-- Body here -->
{{-- Subcopy --}}
@slot('subcopy')
@component('mail::subcopy')
You have received a new payment from <span class="company-name">{{$data['company']['name']}}</span>
@component('mail::button', ['url' => url('/payments/pdf/'.$data['payment']['unique_hash'])])
View Payment
@endcomponent
@endcomponent
@endslot
{{-- Footer --}}
@slot('footer')
@component('mail::footer')
Powered by <a class="footer-link" href="https://craterapp.com">Crater</a>
@endcomponent
@endslot
@endcomponent

View File

@ -0,0 +1,12 @@
@component('mail::message')
# Test Email
{{ $my_message }}
@component('mail::button', ['url' => ''])
Test
@endcomponent
Thanks,<br>
{{ config('app.name') }}
@endcomponent