355 lines
11 KiB
Vue
355 lines
11 KiB
Vue
<template>
|
|
<div class="transaction-create">
|
|
<nav class="breadcrumb" aria-label="breadcrumbs" v-if="account">
|
|
<ul>
|
|
<li>
|
|
<router-link :to="{ name: 'account', params: { id: account._id } }">{{ account.name }}</router-link>
|
|
</li>
|
|
<li v-if="tag && tag !== noTag">
|
|
<a href="#" @click.prevent>{{ transactionTagLabel[tag].label }}</a>
|
|
</li>
|
|
<li class="is-active">
|
|
<a href="#" @click.prevent>
|
|
<span v-if="transaction._id">{{ name }}</span>
|
|
<span v-else>Nouvelle dépense</span>
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
<div class="columns">
|
|
<div class="column">
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Nom</label>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field">
|
|
<div class="control">
|
|
<input
|
|
type="text"
|
|
class="input"
|
|
v-model.trim="name"
|
|
maxlength="30"
|
|
placeholder="Nom de la dépense"
|
|
/>
|
|
<p
|
|
class="help is-info"
|
|
>{{ $tc('validation.max_char', 30 - name.length, { max: 30 - name.length }) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Montant</label>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field">
|
|
<div class="field has-addons">
|
|
<p class="control is-expanded">
|
|
<input
|
|
type="number"
|
|
class="input"
|
|
v-model.number="amount"
|
|
placeholder="Montant"
|
|
inputmode="numeric"
|
|
pattern="[0-9]*"
|
|
min="0"
|
|
max="1000000"
|
|
/>
|
|
</p>
|
|
<p class="control">
|
|
<span v-if="account.currencies.length > 1" class="select">
|
|
<select name="currency" id="currency" v-model="currency">
|
|
<option
|
|
v-for="(currency, k) in account.currencies"
|
|
:key="k"
|
|
:value="currency"
|
|
>{{ currency.symbol || currency.name }}</option>
|
|
</select>
|
|
</span>
|
|
<a
|
|
v-else
|
|
class="button is-static"
|
|
>{{ account.mainCurrency.symbol || account.mainCurrency.name }}</a>
|
|
</p>
|
|
</div>
|
|
<p class="help is-info">montant max : {{ maxAmount | money(currency) }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Localisation</label>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field">
|
|
<div class="control" v-if="transaction.location">
|
|
<input type="text" readonly class="input" v-model="transaction.location.place" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Date</label>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field">
|
|
<div class="control">
|
|
<input type="date" class="input" v-model="date" placeholder="Date" :max="today" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<transaction-tag-update v-model="tag" />
|
|
</div>
|
|
<div class="column">
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Payé par</label>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field is-narrow">
|
|
<div class="control">
|
|
<div class="select is-fullwidth">
|
|
<select name="pay-by" id="pay-by" v-model="payBy">
|
|
<option
|
|
v-for="(user, k) in account.users"
|
|
:key="k"
|
|
:value="user.alias"
|
|
>{{ user.alias }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="field is-horizontal">
|
|
<div class="field-label is-normal">
|
|
<label class="label">Pour</label>
|
|
<button
|
|
class="button is-primary is-small"
|
|
:class="{ 'is-outlined': payForUsers.length !== account.users.length}"
|
|
type="button"
|
|
@click="selectAll"
|
|
>tous</button>
|
|
</div>
|
|
<div class="field-body">
|
|
<div class="field is-narrow">
|
|
<div class="control">
|
|
<div v-for="(split, k) in payFor" :key="k" class="user-item">
|
|
<transaction-split :split="payFor[k]" />
|
|
<hr v-if="k !== payFor.length - 1" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<fab-button @valid="submitTransaction" :margin="true">
|
|
<awe-icon icon="check" />
|
|
</fab-button>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue, Watch } from 'vue-property-decorator'
|
|
import { Getter } from 'vuex-class'
|
|
import IAccount from '@/models/IAccount'
|
|
import ICurrency from '@/models/ICurrency'
|
|
import ITransaction from '@/models/ITransaction'
|
|
import IUser from '@/models/IUser'
|
|
import ISplit from '@/models/ISplit'
|
|
import TransactionType from '@/enums/TransactionType'
|
|
import TransactionTag, { TransactionTagLabel } from '@/enums/TransactionTag'
|
|
import accountService from '@/services/AccountService'
|
|
import exchangeService from '@/services/ExchangeService'
|
|
import transactionService from '@/services/TransactionService'
|
|
import formatDate from '@/utils/format-date'
|
|
import notif from '@/utils/notif'
|
|
import { money } from '@/utils/filters'
|
|
import { confirmation, alertMessage } from '@/utils'
|
|
|
|
const today: Date = new Date()
|
|
|
|
@Component({
|
|
components: {
|
|
'fab-button': () => import('@/components/FabButton.vue'),
|
|
'transaction-split': () => import('@/components/TransactionSplit.vue'),
|
|
'transaction-tag-update': () =>
|
|
import('@/components/TransactionTagUpdate.vue')
|
|
}
|
|
})
|
|
export default class TransactionCreate extends Vue {
|
|
@Getter
|
|
public user!: IUser | null
|
|
@Prop({ type: Object, required: true })
|
|
public transaction!: ITransaction
|
|
@Prop({ type: Object, required: true })
|
|
public account!: IAccount
|
|
public name: string = ''
|
|
public amount: number | null = null
|
|
public tag: TransactionTag = TransactionTag.None
|
|
public today: string = formatDate(today)
|
|
public date: string = formatDate(today)
|
|
public location: string = ''
|
|
public currency: ICurrency | null = null
|
|
public payBy: string = ''
|
|
public payFor: ISplit[] = []
|
|
public maxAmount: number = 1000000
|
|
public noTag: string = TransactionTag.None
|
|
public transactionTagLabel: typeof TransactionTagLabel = TransactionTagLabel
|
|
|
|
public async created(): Promise<void> {
|
|
this.setData(this.transaction)
|
|
}
|
|
|
|
public setData(transaction: ITransaction): void {
|
|
this.name = transaction.name
|
|
this.date = formatDate(transaction.date)
|
|
this.tag = transaction.tag
|
|
this.amount = transaction.amount || null
|
|
this.payBy = transaction.payBy
|
|
this.payFor = transaction.payFor
|
|
if (this.account.users) {
|
|
this.account.users.forEach((user: IUser) => {
|
|
if (!transaction.payFor.find((p: ISplit) => p.alias === user.alias)) {
|
|
this.payFor.push({
|
|
alias: user.alias || '',
|
|
weight: 0
|
|
})
|
|
}
|
|
})
|
|
}
|
|
this.currency = transaction.mainCurrency
|
|
}
|
|
|
|
public async submitTransaction(next: any): Promise<void> {
|
|
if (!this.validate()) {
|
|
next()
|
|
return
|
|
}
|
|
if (!this.currency || !this.account) {
|
|
next()
|
|
return
|
|
}
|
|
const currencies: string[] = this.account.currencies.map(
|
|
(c: ICurrency) => c.code
|
|
)
|
|
const date: Date = new Date(this.date)
|
|
const hasId: boolean = !!this.transaction._id
|
|
const amount: number = this.amount
|
|
? parseFloat(this.amount.toString().replace(',', '.'))
|
|
: 0
|
|
const transaction: ITransaction = {
|
|
_id: this.transaction._id,
|
|
_rev: this.transaction._rev,
|
|
doctype: this.transaction.doctype,
|
|
accountId: this.account._id || '',
|
|
name: this.name,
|
|
date,
|
|
tag: this.tag,
|
|
amount,
|
|
payBy: this.payBy,
|
|
payFor: this.payForUsers,
|
|
userIds: this.account.userIds,
|
|
mainCurrency: this.currency,
|
|
currencies: this.account.currencies,
|
|
transactionType: TransactionType.normal,
|
|
exchange: await exchangeService.get(this.currency.code, date, currencies)
|
|
}
|
|
const response: PouchDB.Core.Response = hasId
|
|
? await transactionService.save(transaction)
|
|
: await transactionService.add(this.account._id || '', transaction)
|
|
if (response.ok) {
|
|
if (hasId) {
|
|
this.$router.push({
|
|
name: 'transaction',
|
|
params: { id: this.transaction._id || '' }
|
|
})
|
|
confirmation('Dépense mise à jour.')
|
|
} else {
|
|
this.$router.push({
|
|
name: 'account',
|
|
params: { id: this.account._id || '' }
|
|
})
|
|
confirmation('Dépense créée.')
|
|
}
|
|
} else {
|
|
// tslint:disable-next-line:no-console
|
|
console.warn(response)
|
|
alertMessage(`Une erreur s'est produite à la création d'une transaction.`)
|
|
}
|
|
next()
|
|
}
|
|
|
|
public selectAll(): void {
|
|
if (!this.account) {
|
|
return
|
|
}
|
|
this.payFor.forEach((p: ISplit) => (p.weight = p.weight || 1))
|
|
}
|
|
|
|
public get payForUsers(): ISplit[] {
|
|
return this.payFor.filter((p, index) => p.weight > 0)
|
|
}
|
|
|
|
@Watch('payBy')
|
|
public onPayByChange(payBy: string, oldPayBy: string) {
|
|
if (
|
|
oldPayBy &&
|
|
this.account &&
|
|
this.payFor.length !== this.account.users.length
|
|
) {
|
|
const old = this.payFor.find((p: ISplit) => p.alias === oldPayBy)
|
|
if (old) {
|
|
old.weight = 0
|
|
}
|
|
}
|
|
if (payBy) {
|
|
const newPayBy = this.payFor.find((p: ISplit) => p.alias === payBy)
|
|
if (newPayBy) {
|
|
newPayBy.weight = newPayBy.weight || 1
|
|
}
|
|
}
|
|
}
|
|
|
|
private validate(): boolean {
|
|
if (
|
|
!this.name ||
|
|
!this.amount ||
|
|
(!this.currency && !this.date && this.payFor.length === 0)
|
|
) {
|
|
if (this.amount === 0) {
|
|
notif.alert('Le montant doit être supérieur à 0.')
|
|
} else {
|
|
notif.alert('Tous les champs sont requis.')
|
|
}
|
|
return false
|
|
}
|
|
if (isNaN(this.amount) || this.amount > this.maxAmount) {
|
|
notif.alert(
|
|
`Veuillez saisir un montant numérique inférieur à ${money(
|
|
this.maxAmount,
|
|
this.currency
|
|
)}.`
|
|
)
|
|
return false
|
|
}
|
|
if (this.amount < 0) {
|
|
notif.alert('Le montant doit être supérieur à 0.')
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.help {
|
|
text-align: left;
|
|
}
|
|
</style>
|