forked from verdnatura/hedera-web
#4922 invoices & orders
This commit is contained in:
parent
7e26aa773c
commit
0234e14c6b
|
@ -33,7 +33,9 @@ module.exports = configure(function (ctx) {
|
|||
|
||||
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
||||
css: [
|
||||
'app.scss'
|
||||
'app.scss',
|
||||
'width.scss',
|
||||
'responsive.scss'
|
||||
],
|
||||
|
||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
import { boot } from 'quasar/wrappers'
|
||||
import { appStore } from 'stores/app'
|
||||
import { userStore } from 'stores/user'
|
||||
|
||||
export default boot(({ app }) => {
|
||||
const myApp = appStore()
|
||||
app.config.globalProperties.$app = myApp
|
||||
const props = app.config.globalProperties
|
||||
props.$app = appStore()
|
||||
props.$user = userStore()
|
||||
props.$actions = document.createElement('div')
|
||||
})
|
||||
|
|
|
@ -20,7 +20,15 @@ export default async ({ app }) => {
|
|||
function errorHandler (err, vm) {
|
||||
let message
|
||||
let tMessage
|
||||
const res = err.response
|
||||
let res = err.response
|
||||
|
||||
// XXX: Compatibility with old JSON service
|
||||
if (err.name === 'JsonException') {
|
||||
res = {
|
||||
status: err.statusCode,
|
||||
data: { error: { message: err.message } }
|
||||
}
|
||||
}
|
||||
|
||||
if (res) {
|
||||
const status = res.status
|
||||
|
|
|
@ -13,4 +13,6 @@ export default boot(({ app }) => {
|
|||
|
||||
// Set i18n instance on app
|
||||
app.use(i18n)
|
||||
|
||||
window.i18n = i18n.global
|
||||
})
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
font-family: 'Open Sans';
|
||||
src: url(./opensans.ttf) format('truetype');
|
||||
}
|
||||
@mixin mobile {
|
||||
@media screen and (max-width: 960px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Poppins', 'Verdana', 'Sans';
|
||||
|
@ -25,3 +30,6 @@ a.link {
|
|||
border-radius: 7px;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .1);
|
||||
}
|
||||
.q-page-sticky.fixed-bottom-right {
|
||||
margin: 18px;
|
||||
}
|
||||
|
|
|
@ -23,3 +23,11 @@ $positive : #21BA45;
|
|||
$negative : #C10015;
|
||||
$info : #31CCEC;
|
||||
$warning : #F2C037;
|
||||
|
||||
// Width
|
||||
|
||||
$width-xs: 400px;
|
||||
$width-sm: 544px;
|
||||
$width-md: 800px;
|
||||
$width-lg: 1280px;
|
||||
$width-xl: 1600px;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@mixin mobile {
|
||||
@media screen and (max-width: 1023px) {
|
||||
@content;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
%margin-auto {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
.vn-w-xs {
|
||||
@extend %margin-auto;
|
||||
max-width: $width-xs;
|
||||
}
|
||||
.vn-w-sm {
|
||||
@extend %margin-auto;
|
||||
max-width: $width-sm;
|
||||
}
|
||||
.vn-w-md {
|
||||
@extend %margin-auto;
|
||||
max-width: $width-md;
|
||||
}
|
||||
.vn-w-lg {
|
||||
@extend %margin-auto;
|
||||
max-width: $width-lg;
|
||||
}
|
||||
.vn-w-xl {
|
||||
@extend %margin-auto;
|
||||
max-width: $width-xl;
|
||||
}
|
|
@ -8,5 +8,56 @@ export default {
|
|||
somethingWentWrong: 'Something went wrong',
|
||||
loginFailed: 'Login failed',
|
||||
authenticationRequired: 'Authentication required',
|
||||
notFound: 'Not found'
|
||||
notFound: 'Not found',
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
tomorrow: 'Tomorrow',
|
||||
date: {
|
||||
days: [
|
||||
'Sunday',
|
||||
'Monday',
|
||||
'Tuesday',
|
||||
'Wednesday',
|
||||
'Thursday',
|
||||
'Friday',
|
||||
'Saturday'
|
||||
],
|
||||
daysShort: [
|
||||
'Sun',
|
||||
'Mon',
|
||||
'Tue',
|
||||
'Wed',
|
||||
'Thu',
|
||||
'Fri',
|
||||
'Sat'
|
||||
],
|
||||
months: [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
],
|
||||
shortMonths: [
|
||||
'Jan',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Apr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Aug',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dec'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,5 +8,56 @@ export default {
|
|||
somethingWentWrong: 'Algo salió mal',
|
||||
loginFailed: 'Usuario o contraseña incorrectos',
|
||||
authenticationRequired: 'Autenticación requerida',
|
||||
notFound: 'No encontrado'
|
||||
notFound: 'No encontrado',
|
||||
today: 'Hoy',
|
||||
yesterday: 'Ayer',
|
||||
tomorrow: 'Mañana',
|
||||
date: {
|
||||
days: [
|
||||
'Domingo',
|
||||
'Lunes',
|
||||
'Martes',
|
||||
'Miércoles',
|
||||
'Jueves',
|
||||
'Viernes',
|
||||
'Sábado'
|
||||
],
|
||||
daysShort: [
|
||||
'Do',
|
||||
'Lu',
|
||||
'Mi',
|
||||
'Mi',
|
||||
'Ju',
|
||||
'Vi',
|
||||
'Sa'
|
||||
],
|
||||
months: [
|
||||
'Enero',
|
||||
'Febrero',
|
||||
'Marzo',
|
||||
'Abril',
|
||||
'Mayo',
|
||||
'Junio',
|
||||
'Julio',
|
||||
'Agosto',
|
||||
'Septiembre',
|
||||
'Octubre',
|
||||
'Noviembre',
|
||||
'Diciembre'
|
||||
],
|
||||
shortMonths: [
|
||||
'Ene',
|
||||
'Feb',
|
||||
'Mar',
|
||||
'Abr',
|
||||
'May',
|
||||
'Jun',
|
||||
'Jul',
|
||||
'Ago',
|
||||
'Sep',
|
||||
'Oct',
|
||||
'Nov',
|
||||
'Dic'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,9 +100,9 @@ export class Connection extends JsonConnection {
|
|||
* @return {ResultSet} The result
|
||||
*/
|
||||
async execQuery (query, params) {
|
||||
const sql = query.replace(/#\w+/g, function (key) {
|
||||
const sql = query.replace(/#\w+/g, key => {
|
||||
const value = params[key.substring(1)]
|
||||
return value ? this.renderValue(params) : key
|
||||
return value ? this.renderValue(value) : key
|
||||
})
|
||||
|
||||
return await this.execSql(sql)
|
||||
|
|
|
@ -60,7 +60,13 @@ export class JsonConnection extends VnObject {
|
|||
* Called when REST response is received.
|
||||
*/
|
||||
async sendWithUrl (method, url, params) {
|
||||
const urlParams = new URLSearchParams(params)
|
||||
const urlParams = new URLSearchParams()
|
||||
for (const key in params) {
|
||||
if (params[key] != null) {
|
||||
urlParams.set(key, params[key])
|
||||
}
|
||||
}
|
||||
|
||||
return this.request({
|
||||
method,
|
||||
url,
|
||||
|
@ -81,7 +87,9 @@ export class JsonConnection extends VnObject {
|
|||
|
||||
const headers = config.headers
|
||||
if (headers) {
|
||||
for (const header in headers) { request.setRequestHeader(header, headers[header]) }
|
||||
for (const header in headers) {
|
||||
request.setRequestHeader(header, headers[header])
|
||||
}
|
||||
}
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
|
@ -143,8 +151,9 @@ export class JsonConnection extends VnObject {
|
|||
data = jsData
|
||||
} else {
|
||||
let exception = jsData.exception
|
||||
const error = jsData.error
|
||||
|
||||
const err = new JsonException()
|
||||
err.statusCode = request.status
|
||||
|
||||
if (exception) {
|
||||
exception = exception
|
||||
|
@ -158,14 +167,8 @@ export class JsonConnection extends VnObject {
|
|||
err.file = jsData.file
|
||||
err.line = jsData.line
|
||||
err.trace = jsData.trace
|
||||
err.statusCode = request.status
|
||||
} else if (error) {
|
||||
err.message = error.message
|
||||
err.code = error.code
|
||||
err.statusCode = request.status
|
||||
} else {
|
||||
err.message = request.statusText
|
||||
err.statusCode = request.status
|
||||
}
|
||||
|
||||
throw err
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
<q-toolbar-title>
|
||||
Home
|
||||
</q-toolbar-title>
|
||||
<div id="actions" ref="actions">
|
||||
</div>
|
||||
</q-toolbar>
|
||||
</q-header>
|
||||
<q-drawer
|
||||
|
@ -118,6 +120,8 @@
|
|||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import "src/css/responsive";
|
||||
|
||||
.q-drawer {
|
||||
.q-item {
|
||||
padding-left: 38px;
|
||||
|
@ -126,6 +130,26 @@
|
|||
padding-left: 50px;
|
||||
}
|
||||
}
|
||||
.q-page-container > * {
|
||||
padding: 16px;
|
||||
}
|
||||
@include mobile {
|
||||
#actions > div {
|
||||
.q-btn {
|
||||
border-radius: 50%;
|
||||
padding: 10px;
|
||||
|
||||
&__content {
|
||||
& > .q-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
& > .block {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
|
@ -151,6 +175,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
async mounted () {
|
||||
this.$refs.actions.appendChild(this.$actions)
|
||||
await this.user.loadData()
|
||||
await this.$app.loadConfig()
|
||||
await this.fetchData()
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
|
||||
import { date as qdate, format } from 'quasar'
|
||||
const { pad } = format
|
||||
|
||||
export function currency (val) {
|
||||
return typeof val === 'number' ? val.toFixed(2) + '€' : val
|
||||
}
|
||||
|
||||
export function date (val, format) {
|
||||
if (val == null) return val
|
||||
if (!(val instanceof Date)) {
|
||||
val = new Date(val)
|
||||
}
|
||||
return qdate.formatDate(val, format, window.i18n.tm('date'))
|
||||
}
|
||||
|
||||
export function relDate (val) {
|
||||
if (val == null) return val
|
||||
if (!(val instanceof Date)) {
|
||||
val = new Date(val)
|
||||
}
|
||||
|
||||
const dif = qdate.getDateDiff(new Date(), val, 'days')
|
||||
let day
|
||||
|
||||
switch (dif) {
|
||||
case 0:
|
||||
day = 'today'
|
||||
break
|
||||
case 1:
|
||||
day = 'yesterday'
|
||||
break
|
||||
case -1:
|
||||
day = 'tomorrow'
|
||||
break
|
||||
}
|
||||
|
||||
if (day) {
|
||||
day = window.i18n.t(day)
|
||||
} else {
|
||||
if (dif > 0 && dif <= 7) {
|
||||
day = qdate.formatDate(val, 'ddd', window.i18n.tm('date'))
|
||||
} else {
|
||||
day = qdate.formatDate(val, 'ddd, MMMM Do', window.i18n.tm('date'))
|
||||
}
|
||||
}
|
||||
|
||||
return day
|
||||
}
|
||||
|
||||
export function relTime (val) {
|
||||
if (val == null) return val
|
||||
if (!(val instanceof Date)) {
|
||||
val = new Date(val)
|
||||
}
|
||||
return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss')
|
||||
}
|
||||
|
||||
export function elapsedTime (val) {
|
||||
if (val == null) return val
|
||||
if (!(val instanceof Date)) {
|
||||
val = new Date(val)
|
||||
}
|
||||
const now = (new Date()).getTime()
|
||||
val = Math.floor((now - val.getTime()) / 1000)
|
||||
|
||||
const hours = Math.floor(val / 3600)
|
||||
val -= hours * 3600
|
||||
const minutes = Math.floor(val / 60)
|
||||
val -= minutes * 60
|
||||
const seconds = val
|
||||
|
||||
return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}`
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div style="padding: 0;">
|
||||
<div class="q-pa-sm row items-start">
|
||||
<div
|
||||
class="new-card q-pa-sm"
|
||||
|
@ -17,12 +17,12 @@
|
|||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-page-sticky>
|
||||
<q-btn
|
||||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
to="/catalog"
|
||||
to="/ecomerce/catalog"
|
||||
:title="$t('startOrder')"
|
||||
/>
|
||||
</q-page-sticky>
|
||||
|
|
|
@ -0,0 +1,165 @@
|
|||
<template>
|
||||
<Teleport :to="$actions">
|
||||
<q-select
|
||||
v-model="year"
|
||||
:options="years"
|
||||
color="white"
|
||||
dark
|
||||
standout
|
||||
dense
|
||||
rounded />
|
||||
</Teleport>
|
||||
<div class="vn-w-sm">
|
||||
<div
|
||||
v-if="!invoices?.length"
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||
{{$t('noInvoicesFound')}}
|
||||
</div>
|
||||
<q-card v-if="invoices?.length">
|
||||
<q-table
|
||||
:columns="columns"
|
||||
:pagination="pagination"
|
||||
:rows="invoices"
|
||||
row-key="id"
|
||||
hide-header
|
||||
hide-bottom>
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td key="ref" :props="props">
|
||||
{{ props.row.ref }}
|
||||
</q-td>
|
||||
<q-td key="issued" :props="props">
|
||||
{{ date(props.row.issued, 'ddd, MMMM Do') }}
|
||||
</q-td>
|
||||
<q-td key="amount" :props="props">
|
||||
{{ currency(props.row.amount) }}
|
||||
</q-td>
|
||||
<q-td key="hasPdf" :props="props">
|
||||
<q-btn
|
||||
v-if="props.row.hasPdf"
|
||||
icon="download"
|
||||
:title="$t('downloadInvoicePdf')"
|
||||
:href="invoiceUrl(props.row.id)"
|
||||
target="_blank"
|
||||
flat
|
||||
round/>
|
||||
<q-icon
|
||||
v-else
|
||||
name="warning"
|
||||
:title="$t('notDownloadable')"
|
||||
color="warning"
|
||||
size="24px"/>
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
|
||||
export default {
|
||||
name: 'OrdersPendingIndex',
|
||||
data () {
|
||||
const curYear = (new Date()).getFullYear()
|
||||
const years = []
|
||||
|
||||
for (let year = curYear - 5; year <= curYear; year++) {
|
||||
years.push(year)
|
||||
}
|
||||
|
||||
return {
|
||||
columns: [
|
||||
{ name: 'ref', label: 'serial', field: 'ref', align: 'left' },
|
||||
{ name: 'issued', label: 'issued', field: 'issued', align: 'left' },
|
||||
{ name: 'amount', label: 'amount', field: 'amount' },
|
||||
{ name: 'hasPdf', label: 'download', field: 'hasPdf', align: 'center' }
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 0
|
||||
},
|
||||
year: curYear,
|
||||
years,
|
||||
invoices: null
|
||||
}
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
await this.loadData()
|
||||
},
|
||||
|
||||
watch: {
|
||||
async year () {
|
||||
await this.loadData()
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
date,
|
||||
currency,
|
||||
|
||||
async loadData () {
|
||||
const params = {
|
||||
from: new Date(this.year, 0),
|
||||
to: new Date(this.year, 11, 31, 23, 59, 59)
|
||||
}
|
||||
this._invoices = await this.$jApi.query(
|
||||
`SELECT id, ref, issued, amount, hasPdf
|
||||
FROM myInvoice
|
||||
WHERE issued BETWEEN #from AND #to
|
||||
ORDER BY issued DESC
|
||||
LIMIT 500`,
|
||||
params
|
||||
)
|
||||
},
|
||||
|
||||
invoiceUrl (id) {
|
||||
return '?' + new URLSearchParams({
|
||||
srv: 'rest:dms/invoice',
|
||||
invoice: id,
|
||||
access_token: this.$user.token
|
||||
}).toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<i18n lang="yaml">
|
||||
en-US:
|
||||
noInvoicesFound: No invoices found
|
||||
serial: Serial
|
||||
issued: Date
|
||||
amount: Import
|
||||
downloadInvoicePdf: Download invoice PDF
|
||||
notDownloadable: Not available for download, request the invoice to your salesperson
|
||||
es-ES:
|
||||
noInvoicesFound: No se han encontrado facturas
|
||||
serial: Serie
|
||||
issued: Fecha
|
||||
amount: Importe
|
||||
downloadInvoicePdf: Descargar factura en PDF
|
||||
notDownloadable: No disponible para descarga, solicita la factura a tu comercial
|
||||
ca-ES:
|
||||
noInvoicesFound: No s'han trobat factures
|
||||
serial: Sèrie
|
||||
issued: Data
|
||||
amount: Import
|
||||
downloadInvoicePdf: Descarregar PDF
|
||||
notDownloadable: No disponible per cescarrega, sol·licita la factura al teu comercial
|
||||
fr-FR:
|
||||
noInvoicesFound: Aucune facture trouvée
|
||||
serial: Série
|
||||
issued: Date
|
||||
amount: Montant
|
||||
downloadInvoicePdf: Télécharger le PDF
|
||||
notDownloadable: Non disponible en téléchargement, demander la facture à votre commercial
|
||||
pt-PT:
|
||||
noInvoicesFound: Nenhuma fatura encontrada
|
||||
serial: Serie
|
||||
issued: Data
|
||||
amount: Importe
|
||||
downloadInvoicePdf: Baixar PDF
|
||||
notDownloadable: Não disponível para download, solicite a fatura ao seu comercial
|
||||
</i18n>
|
|
@ -1,55 +1,139 @@
|
|||
<template>
|
||||
<div class="vn-pp row justify-center">
|
||||
<Teleport :to="$actions">
|
||||
<div class="balance">
|
||||
<span class="label">{{$t('balance')}}</span>
|
||||
<span
|
||||
class="amount"
|
||||
:class="{negative: debt < 0}">
|
||||
{{currency(debt || 0)}}
|
||||
</span>
|
||||
<q-icon
|
||||
name="info"
|
||||
:title="$t('paymentInfo')"
|
||||
class="info"
|
||||
size="24px"/>
|
||||
</div>
|
||||
<q-btn
|
||||
icon="payments"
|
||||
:label="$t('makePayment')"
|
||||
@click="onPayClick()"
|
||||
rounded
|
||||
no-caps/>
|
||||
<q-btn
|
||||
to="/ecomerce/basket"
|
||||
icon="shopping_cart"
|
||||
:label="$t('shoppingCart')"
|
||||
rounded
|
||||
no-caps/>
|
||||
</Teleport>
|
||||
<div class="vn-w-sm">
|
||||
<div
|
||||
v-if="orders && !orders.length"
|
||||
v-if="!orders?.length"
|
||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||
{{$t('noOrdersFound')}}
|
||||
</div>
|
||||
<q-card
|
||||
v-if="orders && orders.length"
|
||||
class="vn-w-md">
|
||||
<q-list bordered separator>
|
||||
<q-card v-if="orders?.length">
|
||||
<q-list bordered separator padding >
|
||||
<q-item
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
:to="`/order/${order.id}/`"
|
||||
:to="`ticket/${order.id}`"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
<q-item-label>{{order.landed}}</q-item-label>
|
||||
<q-item-label>
|
||||
{{date(order.landed, 'ddd, MMMM Do')}}
|
||||
</q-item-label>
|
||||
<q-item-label caption>#{{order.id}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.address.city}}</q-item-label>
|
||||
<q-item-label caption>{{order.nickname}}</q-item-label>
|
||||
<q-item-label caption>{{order.agency}}</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side top>
|
||||
{{order.taxableBase}}
|
||||
{{order.total}}€
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||
<q-page-sticky>
|
||||
<q-btn
|
||||
fab
|
||||
icon="add_shopping_cart"
|
||||
color="accent"
|
||||
to="/catalog"
|
||||
to="/ecomerce/catalog"
|
||||
:title="$t('startOrder')"/>
|
||||
</q-page-sticky>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.balance {
|
||||
margin-right: 8px;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
& > .amount {
|
||||
padding: 4px;
|
||||
margin: 0 4px;
|
||||
|
||||
&.negative {
|
||||
background-color: #e55;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 0 5px #333;
|
||||
}
|
||||
}
|
||||
& > .info {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
import { tpvStore } from 'stores/tpv'
|
||||
|
||||
export default {
|
||||
name: 'OrdersPendingIndex',
|
||||
data () {
|
||||
return {
|
||||
orders: null
|
||||
orders: null,
|
||||
debt: 0,
|
||||
tpv: tpvStore()
|
||||
}
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
await this.tpv.check(this.$route)
|
||||
|
||||
this.orders = await this.$jApi.query(
|
||||
'CALL myTicket_list(NULL, NULL)'
|
||||
)
|
||||
this.debt = await this.$jApi.getValue(
|
||||
'SELECT -myClient_getDebt(NULL)'
|
||||
)
|
||||
},
|
||||
|
||||
methods: {
|
||||
date,
|
||||
currency,
|
||||
|
||||
async onPayClick () {
|
||||
let amount = -this.debt
|
||||
amount = amount <= 0 ? null : amount
|
||||
|
||||
let defaultAmountStr = ''
|
||||
if (amount !== null) {
|
||||
defaultAmountStr = amount
|
||||
}
|
||||
amount = prompt(this.$t('amountToPay'), defaultAmountStr)
|
||||
|
||||
if (amount != null) {
|
||||
amount = parseFloat(amount.replace(',', '.'))
|
||||
await this.tpv.pay(amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -58,7 +142,11 @@ export default {
|
|||
en-US:
|
||||
startOrder: Start order
|
||||
noOrdersFound: No orders found
|
||||
makePayment: Make payment
|
||||
shoppingCart: Shopping cart
|
||||
es-ES:
|
||||
startOrder: Empezar pedido
|
||||
noOrdersFound: No se encontrado pedidos
|
||||
makePayment: Realizar pago
|
||||
shoppingCart: Cesta de la compra
|
||||
</i18n>
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<template>
|
||||
<Teleport :to="$actions">
|
||||
<q-btn
|
||||
icon="print"
|
||||
:label="$t('printDeliveryNote')"
|
||||
@click="onPrintClick()"
|
||||
rounded
|
||||
no-caps/>
|
||||
</Teleport>
|
||||
<div>
|
||||
<q-card class="vn-w-sm">
|
||||
<q-card-section>
|
||||
<div class="text-h6">#{{ticket.id}}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{$t('shippingInformation')}}</div>
|
||||
<div>{{$t('preparation')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div>
|
||||
<div>{{$t('delivery')}} {{date(ticket.shipped, 'ddd, MMMM Do')}}</div>
|
||||
<div>{{$t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse')}} {{ticket.agency}}</div>
|
||||
</q-card-section>
|
||||
<q-card-section>
|
||||
<div class="text-h6">{{$t('deliveryAddress')}}</div>
|
||||
<div>{{ticket.nickname}}</div>
|
||||
<div>{{ticket.street}}</div>
|
||||
<div>{{ticket.postalCode}} {{ticket.city}} ({{ticket.province}})</div>
|
||||
</q-card-section>
|
||||
<q-separator inset />
|
||||
<q-list v-for="row in rows" :key="row.itemFk">
|
||||
<q-item>
|
||||
<q-item-section avatar>
|
||||
<q-avatar size="68px">
|
||||
<img :src="`${$app.imageUrl}/catalog/200x200/${row.image}`">
|
||||
</q-avatar>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label lines="1">
|
||||
{{row.concept}}
|
||||
</q-item-label>
|
||||
<q-item-label lines="1" caption>
|
||||
{{row.value5}} {{row.value6}} {{row.value7}}
|
||||
</q-item-label>
|
||||
<q-item-label lines="1">
|
||||
{{row.quantity}} x {{currency(row.price)}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
<q-item-section side class="total">
|
||||
<q-item-label>
|
||||
<span class="discount" v-if="row.discount">
|
||||
{{currency(discountSubtotal(row))}} -
|
||||
{{currency(row.discount)}} =
|
||||
</span>
|
||||
{{currency(subtotal(row))}}
|
||||
</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.total {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { date, currency } from 'src/lib/filters.js'
|
||||
|
||||
export default {
|
||||
name: 'OrdersConfirmedView',
|
||||
|
||||
data () {
|
||||
return {
|
||||
ticket: {},
|
||||
rows: null,
|
||||
services: null,
|
||||
packages: null
|
||||
}
|
||||
},
|
||||
|
||||
async mounted () {
|
||||
const params = {
|
||||
ticket: parseInt(this.$route.params.id)
|
||||
}
|
||||
this.ticket = await this.$jApi.getObject(
|
||||
'CALL myTicket_get(#ticket)',
|
||||
params
|
||||
)
|
||||
this.rows = await this.$jApi.query(
|
||||
'CALL myTicket_getRows(#ticket)',
|
||||
params
|
||||
)
|
||||
this.services = await this.$jApi.query(
|
||||
'CALL myTicket_getServices(#ticket)',
|
||||
params
|
||||
)
|
||||
this.packages = await this.$jApi.query(
|
||||
'CALL myTicket_getPackages(#ticket)',
|
||||
params
|
||||
)
|
||||
},
|
||||
|
||||
methods: {
|
||||
date,
|
||||
currency,
|
||||
|
||||
discountSubtotal (line) {
|
||||
return line.quantity * line.price
|
||||
},
|
||||
|
||||
subtotal (line) {
|
||||
const discount = line.discount
|
||||
return this.discountSubtotal(line) * ((100 - discount) / 100)
|
||||
},
|
||||
|
||||
onPrintClick () {
|
||||
const params = new URLSearchParams({
|
||||
access_token: this.$user.token,
|
||||
recipientId: this.$user.id,
|
||||
type: 'deliveryNote'
|
||||
})
|
||||
window.open(`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -34,6 +34,14 @@ const routes = [
|
|||
name: 'orders',
|
||||
path: '/ecomerce/orders',
|
||||
component: () => import('pages/Ecomerce/Orders.vue')
|
||||
}, {
|
||||
name: 'ticket',
|
||||
path: '/ecomerce/ticket/:id',
|
||||
component: () => import('pages/Ecomerce/Ticket.vue')
|
||||
}, {
|
||||
name: 'invoices',
|
||||
path: '/ecomerce/invoices',
|
||||
component: () => import('pages/Ecomerce/Invoices.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { jApi } from 'boot/axios'
|
||||
|
||||
export const tpvStore = defineStore('tpv', {
|
||||
actions: {
|
||||
async check (route) {
|
||||
const order = route.query.tpvOrder
|
||||
const status = route.query.tpvStatus
|
||||
if (!(order && status)) return null
|
||||
|
||||
await jApi.execQuery(
|
||||
'CALL myTpvTransaction_end(#order, #status)',
|
||||
{ order, status }
|
||||
)
|
||||
|
||||
if (status === 'ko') {
|
||||
const retry = confirm('retryPayQuestion')
|
||||
if (retry) { this.retryPay(order) }
|
||||
}
|
||||
|
||||
return status
|
||||
},
|
||||
|
||||
async pay (amount, company) {
|
||||
await this.realPay(amount * 100, company)
|
||||
},
|
||||
|
||||
async retryPay (order) {
|
||||
const payment = await jApi.getObject(
|
||||
`SELECT t.amount, m.companyFk
|
||||
FROM myTpvTransaction t
|
||||
JOIN tpvMerchant m ON m.id = t.merchantFk
|
||||
WHERE t.id = #order`,
|
||||
{ order }
|
||||
)
|
||||
await this.realPay(payment.amount, payment.companyFk)
|
||||
},
|
||||
|
||||
async realPay (amount, company) {
|
||||
if (!isNumeric(amount) || amount <= 0) {
|
||||
throw new Error('payAmountError')
|
||||
}
|
||||
|
||||
const json = await jApi.send('tpv/transaction', {
|
||||
amount: parseInt(amount),
|
||||
urlOk: this.makeUrl('ok'),
|
||||
urlKo: this.makeUrl('ko'),
|
||||
company
|
||||
})
|
||||
|
||||
const postValues = json.postValues
|
||||
|
||||
const form = document.createElement('form')
|
||||
form.method = 'POST'
|
||||
form.action = json.url
|
||||
document.body.appendChild(form)
|
||||
|
||||
for (const field in postValues) {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'hidden'
|
||||
input.name = field
|
||||
form.appendChild(input)
|
||||
|
||||
if (postValues[field]) { input.value = postValues[field] }
|
||||
}
|
||||
|
||||
form.submit()
|
||||
},
|
||||
|
||||
makeUrl (status) {
|
||||
let path = location.protocol + '//' + location.hostname
|
||||
path += location.port ? ':' + location.port : ''
|
||||
path += location.pathname
|
||||
path += location.search ? location.search : ''
|
||||
path += '#/ecomerce/orders'
|
||||
path += '?' + new URLSearchParams({
|
||||
tpvStatus: status,
|
||||
tpvOrder: '_transactionId_'
|
||||
}).toString()
|
||||
return path
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function isNumeric (n) {
|
||||
return !isNaN(parseFloat(n)) && isFinite(n)
|
||||
}
|
Loading…
Reference in New Issue