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
|
// https://v2.quasar.dev/quasar-cli-webpack/quasar-config-js#Property%3A-css
|
||||||
css: [
|
css: [
|
||||||
'app.scss'
|
'app.scss',
|
||||||
|
'width.scss',
|
||||||
|
'responsive.scss'
|
||||||
],
|
],
|
||||||
|
|
||||||
// https://github.com/quasarframework/quasar/tree/dev/extras
|
// https://github.com/quasarframework/quasar/tree/dev/extras
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import { boot } from 'quasar/wrappers'
|
import { boot } from 'quasar/wrappers'
|
||||||
import { appStore } from 'stores/app'
|
import { appStore } from 'stores/app'
|
||||||
|
import { userStore } from 'stores/user'
|
||||||
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
const myApp = appStore()
|
const props = app.config.globalProperties
|
||||||
app.config.globalProperties.$app = myApp
|
props.$app = appStore()
|
||||||
|
props.$user = userStore()
|
||||||
|
props.$actions = document.createElement('div')
|
||||||
})
|
})
|
||||||
|
|
|
@ -20,7 +20,15 @@ export default async ({ app }) => {
|
||||||
function errorHandler (err, vm) {
|
function errorHandler (err, vm) {
|
||||||
let message
|
let message
|
||||||
let tMessage
|
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) {
|
if (res) {
|
||||||
const status = res.status
|
const status = res.status
|
||||||
|
|
|
@ -13,4 +13,6 @@ export default boot(({ app }) => {
|
||||||
|
|
||||||
// Set i18n instance on app
|
// Set i18n instance on app
|
||||||
app.use(i18n)
|
app.use(i18n)
|
||||||
|
|
||||||
|
window.i18n = i18n.global
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
font-family: 'Open Sans';
|
font-family: 'Open Sans';
|
||||||
src: url(./opensans.ttf) format('truetype');
|
src: url(./opensans.ttf) format('truetype');
|
||||||
}
|
}
|
||||||
|
@mixin mobile {
|
||||||
|
@media screen and (max-width: 960px) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: 'Poppins', 'Verdana', 'Sans';
|
font-family: 'Poppins', 'Verdana', 'Sans';
|
||||||
|
@ -25,3 +30,6 @@ a.link {
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, .1);
|
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;
|
$negative : #C10015;
|
||||||
$info : #31CCEC;
|
$info : #31CCEC;
|
||||||
$warning : #F2C037;
|
$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',
|
somethingWentWrong: 'Something went wrong',
|
||||||
loginFailed: 'Login failed',
|
loginFailed: 'Login failed',
|
||||||
authenticationRequired: 'Authentication required',
|
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',
|
somethingWentWrong: 'Algo salió mal',
|
||||||
loginFailed: 'Usuario o contraseña incorrectos',
|
loginFailed: 'Usuario o contraseña incorrectos',
|
||||||
authenticationRequired: 'Autenticación requerida',
|
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
|
* @return {ResultSet} The result
|
||||||
*/
|
*/
|
||||||
async execQuery (query, params) {
|
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)]
|
const value = params[key.substring(1)]
|
||||||
return value ? this.renderValue(params) : key
|
return value ? this.renderValue(value) : key
|
||||||
})
|
})
|
||||||
|
|
||||||
return await this.execSql(sql)
|
return await this.execSql(sql)
|
||||||
|
|
|
@ -60,7 +60,13 @@ export class JsonConnection extends VnObject {
|
||||||
* Called when REST response is received.
|
* Called when REST response is received.
|
||||||
*/
|
*/
|
||||||
async sendWithUrl (method, url, params) {
|
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({
|
return this.request({
|
||||||
method,
|
method,
|
||||||
url,
|
url,
|
||||||
|
@ -81,7 +87,9 @@ export class JsonConnection extends VnObject {
|
||||||
|
|
||||||
const headers = config.headers
|
const headers = config.headers
|
||||||
if (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) => {
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
@ -143,8 +151,9 @@ export class JsonConnection extends VnObject {
|
||||||
data = jsData
|
data = jsData
|
||||||
} else {
|
} else {
|
||||||
let exception = jsData.exception
|
let exception = jsData.exception
|
||||||
const error = jsData.error
|
|
||||||
const err = new JsonException()
|
const err = new JsonException()
|
||||||
|
err.statusCode = request.status
|
||||||
|
|
||||||
if (exception) {
|
if (exception) {
|
||||||
exception = exception
|
exception = exception
|
||||||
|
@ -158,14 +167,8 @@ export class JsonConnection extends VnObject {
|
||||||
err.file = jsData.file
|
err.file = jsData.file
|
||||||
err.line = jsData.line
|
err.line = jsData.line
|
||||||
err.trace = jsData.trace
|
err.trace = jsData.trace
|
||||||
err.statusCode = request.status
|
|
||||||
} else if (error) {
|
|
||||||
err.message = error.message
|
|
||||||
err.code = error.code
|
|
||||||
err.statusCode = request.status
|
|
||||||
} else {
|
} else {
|
||||||
err.message = request.statusText
|
err.message = request.statusText
|
||||||
err.statusCode = request.status
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
Home
|
Home
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
|
<div id="actions" ref="actions">
|
||||||
|
</div>
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
|
@ -118,6 +120,8 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
@import "src/css/responsive";
|
||||||
|
|
||||||
.q-drawer {
|
.q-drawer {
|
||||||
.q-item {
|
.q-item {
|
||||||
padding-left: 38px;
|
padding-left: 38px;
|
||||||
|
@ -126,6 +130,26 @@
|
||||||
padding-left: 50px;
|
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>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -151,6 +175,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted () {
|
async mounted () {
|
||||||
|
this.$refs.actions.appendChild(this.$actions)
|
||||||
await this.user.loadData()
|
await this.user.loadData()
|
||||||
await this.$app.loadConfig()
|
await this.$app.loadConfig()
|
||||||
await this.fetchData()
|
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>
|
<template>
|
||||||
<div>
|
<div style="padding: 0;">
|
||||||
<div class="q-pa-sm row items-start">
|
<div class="q-pa-sm row items-start">
|
||||||
<div
|
<div
|
||||||
class="new-card q-pa-sm"
|
class="new-card q-pa-sm"
|
||||||
|
@ -17,12 +17,12 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
<q-page-sticky>
|
||||||
<q-btn
|
<q-btn
|
||||||
fab
|
fab
|
||||||
icon="add_shopping_cart"
|
icon="add_shopping_cart"
|
||||||
color="accent"
|
color="accent"
|
||||||
to="/catalog"
|
to="/ecomerce/catalog"
|
||||||
:title="$t('startOrder')"
|
:title="$t('startOrder')"
|
||||||
/>
|
/>
|
||||||
</q-page-sticky>
|
</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>
|
<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
|
<div
|
||||||
v-if="orders && !orders.length"
|
v-if="!orders?.length"
|
||||||
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
class="text-subtitle1 text-center text-grey-7 q-pa-md">
|
||||||
{{$t('noOrdersFound')}}
|
{{$t('noOrdersFound')}}
|
||||||
</div>
|
</div>
|
||||||
<q-card
|
<q-card v-if="orders?.length">
|
||||||
v-if="orders && orders.length"
|
<q-list bordered separator padding >
|
||||||
class="vn-w-md">
|
|
||||||
<q-list bordered separator>
|
|
||||||
<q-item
|
<q-item
|
||||||
v-for="order in orders"
|
v-for="order in orders"
|
||||||
:key="order.id"
|
:key="order.id"
|
||||||
:to="`/order/${order.id}/`"
|
:to="`ticket/${order.id}`"
|
||||||
clickable
|
clickable
|
||||||
v-ripple>
|
v-ripple>
|
||||||
<q-item-section>
|
<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.id}}</q-item-label>
|
||||||
<q-item-label caption>{{order.address.nickname}}</q-item-label>
|
<q-item-label caption>{{order.nickname}}</q-item-label>
|
||||||
<q-item-label caption>{{order.address.city}}</q-item-label>
|
<q-item-label caption>{{order.agency}}</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section side top>
|
<q-item-section side top>
|
||||||
{{order.taxableBase}}
|
{{order.total}}€
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-card>
|
</q-card>
|
||||||
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
<q-page-sticky>
|
||||||
<q-btn
|
<q-btn
|
||||||
fab
|
fab
|
||||||
icon="add_shopping_cart"
|
icon="add_shopping_cart"
|
||||||
color="accent"
|
color="accent"
|
||||||
to="/catalog"
|
to="/ecomerce/catalog"
|
||||||
:title="$t('startOrder')"/>
|
:title="$t('startOrder')"/>
|
||||||
</q-page-sticky>
|
</q-page-sticky>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</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>
|
<script>
|
||||||
|
import { date, currency } from 'src/lib/filters.js'
|
||||||
|
import { tpvStore } from 'stores/tpv'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OrdersPendingIndex',
|
name: 'OrdersPendingIndex',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
orders: null
|
orders: null,
|
||||||
|
debt: 0,
|
||||||
|
tpv: tpvStore()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async mounted () {
|
async mounted () {
|
||||||
|
await this.tpv.check(this.$route)
|
||||||
|
|
||||||
this.orders = await this.$jApi.query(
|
this.orders = await this.$jApi.query(
|
||||||
'CALL myTicket_list(NULL, NULL)'
|
'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>
|
</script>
|
||||||
|
@ -58,7 +142,11 @@ export default {
|
||||||
en-US:
|
en-US:
|
||||||
startOrder: Start order
|
startOrder: Start order
|
||||||
noOrdersFound: No orders found
|
noOrdersFound: No orders found
|
||||||
|
makePayment: Make payment
|
||||||
|
shoppingCart: Shopping cart
|
||||||
es-ES:
|
es-ES:
|
||||||
startOrder: Empezar pedido
|
startOrder: Empezar pedido
|
||||||
noOrdersFound: No se encontrado pedidos
|
noOrdersFound: No se encontrado pedidos
|
||||||
|
makePayment: Realizar pago
|
||||||
|
shoppingCart: Cesta de la compra
|
||||||
</i18n>
|
</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',
|
name: 'orders',
|
||||||
path: '/ecomerce/orders',
|
path: '/ecomerce/orders',
|
||||||
component: () => import('pages/Ecomerce/Orders.vue')
|
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