Merge branch 'feature/news-view' of https://gitea.verdnatura.es/wbuezas/hedera-web-mindshore into feature/news-view
gitea/hedera-web/pipeline/pr-beta This commit looks good Details

This commit is contained in:
William Buezas 2025-04-04 11:50:02 +02:00
commit 2604ba386e
10 changed files with 221 additions and 116 deletions

View File

@ -1,2 +1,5 @@
debian debian
node_modules node_modules
.quasar
build
.vscode

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, onMounted, inject, onBeforeUnmount } from 'vue'; import { ref, onMounted, inject, onBeforeUnmount, h } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
@ -12,12 +12,49 @@ import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const { t } = useI18n(); const { t } = useI18n();
const jApi = inject('jApi'); const api = inject('api');
const router = useRouter(); const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
const filter = {
include: [
{
relation: 'visitUser',
scope: {
include: [
{
relation: 'visitAccess',
scope: {
fields: ['id', 'agentFk'],
include: [
{
relation: 'visitAgent',
scope: {
fields: [
'platform',
'browser',
'version'
]
}
}
]
}
},
{
relation: 'user',
scope: {
fields: ['id', 'nickname', 'name']
}
}
],
fields: ['userFk', 'stamp', 'accessFk']
}
}
],
order: 'lastUpdate DESC'
};
const connections = ref([]); const connections = ref([]);
const loading = ref(false); const loading = ref(false);
const intervalId = ref(null); const intervalId = ref(null);
@ -25,20 +62,28 @@ const intervalId = ref(null);
const getConnections = async () => { const getConnections = async () => {
try { try {
loading.value = true; loading.value = true;
connections.value = await jApi.query(
`SELECT vu.userFk userId, vu.stamp, u.nickname, s.lastUpdate, const { data } = await api.get('/userSessions', {
a.platform, a.browser, a.version, u.name user params: { filter: JSON.stringify(filter) }
FROM userSession s });
JOIN visitUser vu ON vu.id = s.userVisitFk
JOIN visitAccess ac ON ac.id = vu.accessFk if (!data) {
JOIN visitAgent a ON a.id = ac.agentFk connections.value = [];
JOIN visit v ON v.id = a.visitFk return;
JOIN account.user u ON u.id = vu.userFk }
ORDER BY lastUpdate DESC`
); connections.value = data
loading.value = false; .map(({ visitUser = {}, ...rest }) => ({
...rest,
user: visitUser.user,
stamp: visitUser.stamp,
visitAgent: visitUser.visitAccess?.visitAgent
}))
.filter(({ user }) => user);
} catch (error) { } catch (error) {
console.error('Error getting connections:', error); console.error('Error getting connections:', error);
} finally {
loading.value = false;
} }
}; };
@ -58,6 +103,13 @@ onMounted(async () => {
}); });
onBeforeUnmount(() => clearInterval(intervalId.value)); onBeforeUnmount(() => clearInterval(intervalId.value));
const renderAgentDetails = connection => {
const agent = connection.visitAgent;
return agent?.platform && agent?.browser && agent?.version
? h('span', `${agent.platform} - ${agent.browser} - ${agent.version}`)
: null;
};
</script> </script>
<template> <template>
@ -89,11 +141,11 @@ onBeforeUnmount(() => clearInterval(intervalId.value));
<CardList <CardList
v-for="(connection, index) in connections" v-for="(connection, index) in connections"
:key="index" :key="index"
:to="{ name: 'accessLog', params: { id: connection.userId } }" :to="{ name: 'accessLog', params: { id: connection.user?.id } }"
> >
<template #content> <template #content>
<span class="text-bold q-mb-sm"> <span class="text-bold q-mb-sm">
{{ connection.nickname }} {{ connection.user?.nickname }}
</span> </span>
<span> <span>
{{ {{
@ -107,23 +159,16 @@ onBeforeUnmount(() => clearInterval(intervalId.value));
) )
}}</span }}</span
> >
<span <component :is="renderAgentDetails(connection)" />
v-if="
connection.platform &&
connection.browser &&
connection.version
"
>
{{ connection.platform }} - {{ connection.browser }} -
{{ connection.version }}
</span>
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
icon="people" icon="people"
flat flat
rounded rounded
@click.stop.prevent="supplantUser(connection.user)" @click.stop.prevent="
supplantUser(connection.user?.name)
"
> >
<QTooltip> <QTooltip>
{{ t('supplantUser') }} {{ t('supplantUser') }}

View File

@ -1,16 +1,15 @@
<script setup> <script setup>
import { ref, onMounted, inject } from 'vue'; import { ref, onMounted, inject } from 'vue';
const jApi = inject('jApi'); const api = inject('api');
const links = ref([]); const links = ref([]);
const getLinks = async () => { const getLinks = async () => {
try { try {
links.value = await jApi.query( const filter = { order: 'name ASC' };
`SELECT image, name, description, link FROM link const { data } = await api.get('Links', { params: { filter } });
ORDER BY name` links.value = data;
);
} catch (error) { } catch (error) {
console.error('Error getting links:', error); console.error('Error getting links:', error);
} }

View File

@ -3,11 +3,10 @@ import { useI18n } from 'vue-i18n';
import { ref, onMounted, inject, reactive, computed } from 'vue'; import { ref, onMounted, inject, reactive, computed } from 'vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnForm from 'src/components/common/VnForm.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import FormModel from 'src/components/common/FormModel.vue';
const jApi = inject('jApi');
const api = inject('api'); const api = inject('api');
const { t } = useI18n(); const { t } = useI18n();
const { notify } = useNotify(); const { notify } = useNotify();
@ -33,7 +32,7 @@ const statusIcons = {
} }
}; };
const formInitialData = reactive({ const formInitialData = reactive({
schema: 'catalog', collectionFk: 'catalog',
updateMatching: true updateMatching: true
}); });
const imageCollections = ref([]); const imageCollections = ref([]);
@ -45,9 +44,8 @@ const isSubmitable = computed(() =>
const getImageCollections = async () => { const getImageCollections = async () => {
try { try {
imageCollections.value = await jApi.query( const { data } = await api.get('imageCollections');
'SELECT name, `desc` FROM imageCollection ORDER BY `desc`' imageCollections.value = data;
);
} catch (error) { } catch (error) {
console.error('Error getting image collections:', error); console.error('Error getting image collections:', error);
} }
@ -61,38 +59,36 @@ const onSubmit = async data => {
const filteredFiles = addedFiles.value.filter( const filteredFiles = addedFiles.value.filter(
file => file.uploadStatus === 'pending' file => file.uploadStatus === 'pending'
); );
const promises = filteredFiles.map((file, index) => { const promises = filteredFiles.map(async (file, index) => {
const fileIndex = filteredFiles[index].index; const fileIndex = filteredFiles[index].index;
addedFiles.value[fileIndex].uploadStatus = 'uploading'; addedFiles.value[fileIndex].uploadStatus = 'uploading';
const formData = new FormData(); const formData = new FormData();
formData.append('updateMatching', data.updateMatching); const now = new Date();
formData.append('image', file.file); const timestamp = now.getTime();
formData.append('name', file.name); const fileName = `${file.name}_${timestamp}`;
formData.append('schema', data.schema); formData.append('image', file.file, fileName);
formData.append('srv', 'json:image/upload');
return api({ await api.post('images/uploadPhotoAdmin', formData, {
method: 'post', params: {
url: location.origin, name: fileName,
data: formData, collection: data.collectionFk,
updateMatching: data.updateMatching
},
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }
}); });
}); });
const results = await Promise.allSettled(promises); const results = await Promise.allSettled(promises);
results.forEach((result, index) => { results.forEach((result, index) => {
const fileIndex = filteredFiles[index].index; const fileIndex = filteredFiles[index].index;
addedFiles.value[fileIndex].uploadStatus = result.status; addedFiles.value[fileIndex].uploadStatus = result.status;
if (result.status === 'rejected') { if (result.status === 'rejected') {
addedFiles.value[fileIndex].errorMessage = t( addedFiles.value[fileIndex].errorMessage = t(
result.reason?.response?.data?.data?.message result.reason?.response?.data?.data?.message
); );
} }
}); });
const allSuccessful = results.every( const allSuccessful = results.every(
result => result.status === 'fulfilled' result => result.status === 'fulfilled'
); );
@ -139,16 +135,17 @@ onMounted(async () => getImageCollections());
<template> <template>
<QPage class="vn-w-sm"> <QPage class="vn-w-sm">
<VnForm <FormModel
ref="vnFormRef" ref="vnFormRef"
:default-actions="false" :default-actions="false"
:form-initial-data="formInitialData" :form-initial-data="formInitialData"
separation-between-inputs="md" separation-between-inputs="md"
show-bottom-actions show-bottom-actions
:save-fn="onSubmit"
> >
<template #form="{ data }"> <template #form="{ data }">
<VnSelect <VnSelect
v-model="data.schema" v-model="data.collectionFk"
:label="t('collection')" :label="t('collection')"
option-label="desc" option-label="desc"
option-value="name" option-value="name"
@ -269,7 +266,7 @@ onMounted(async () => getImageCollections());
data-cy="photoUploadSubmitBtn" data-cy="photoUploadSubmitBtn"
/> />
</template> </template>
</VnForm> </FormModel>
</QPage> </QPage>
</template> </template>

View File

@ -10,7 +10,7 @@ import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
const { t } = useI18n(); const { t } = useI18n();
const jApi = inject('jApi'); const api = inject('api');
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const appStore = useAppStore(); const appStore = useAppStore();
@ -24,28 +24,21 @@ const visitsData = ref(null);
const getVisits = async () => { const getVisits = async () => {
try { try {
loading.value = true; loading.value = true;
const [visitsResponse] = await jApi.query(
`SELECT browser, const { data } = await api.get('visitUsers/getVisits', {
MIN(CAST(version AS DECIMAL(4,1))) minVersion, params: {
MAX(CAST(version AS DECIMAL(4,1))) maxVersion,
MAX(c.stamp) lastVisit,
COUNT(DISTINCT c.id) visits,
SUM(a.firstAccessFk = c.id AND v.firstAgentFk = a.id) newVisits
FROM visitUser e
JOIN visitAccess c ON c.id = e.accessFk
JOIN visitAgent a ON a.id = c.agentFk
JOIN visit v ON v.id = a.visitFk
WHERE c.stamp BETWEEN TIMESTAMP(#from,'00:00:00') AND TIMESTAMP(#to,'23:59:59')
GROUP BY browser ORDER BY visits DESC`,
{
from: date(from.value), from: date(from.value),
to: date(to.value) to: date(to.value)
} }
); });
visitsData.value = visitsResponse;
loading.value = false; if (!data || !data.length) return;
visitsData.value = data[0];
} catch (error) { } catch (error) {
console.error('Error getting visits:', error); console.error('Error getting visits:', error);
} finally {
loading.value = false;
} }
}; };

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import VnTable from 'src/components/ui/VnTable.vue'; import VnTable from 'src/components/ui/VnTable.vue';
const jApi = inject('jApi'); const api = inject('api');
const { t } = useI18n(); const { t } = useI18n();
const packages = ref([]); const packages = ref([]);
@ -43,7 +43,12 @@ const columns = computed(() => [
const getPackages = async () => { const getPackages = async () => {
try { try {
const data = await jApi.query('CALL vn.agencyVolume()'); const { data } = await api.post(
'applications/agencyVolume/execute-proc',
{
schema: 'vn'
}
);
packages.value = data; packages.value = data;
} catch (error) { } catch (error) {
console.error(error); console.error(error);

View File

@ -8,9 +8,10 @@ import { currency, formatDate } from 'src/lib/filters.js';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
import { useAppStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onUserId } from 'src/utils/onUserId';
const { t } = useI18n(); const { t } = useI18n();
const jApi = inject('jApi'); const api = inject('api');
const { openReport } = usePrintService(); const { openReport } = usePrintService();
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
@ -50,27 +51,38 @@ const columns = computed(() => [
} }
]); ]);
const fetchInvoices = async () => { const fetchInvoices = async clientFk => {
const params = { const { from, to } = {
from: new Date(currentYear.value, 0), from: new Date(currentYear.value, 0),
to: new Date(currentYear.value, 11, 31, 23, 59, 59) to: new Date(currentYear.value, 11, 31, 23, 59, 59)
}; };
invoices.value = await jApi.query( const filter = {
`SELECT id, ref, issued, amount, hasPdf where: {
FROM myInvoice clientFk,
WHERE issued BETWEEN #from AND #to issued: {
ORDER BY issued DESC between: [from, to]
LIMIT 100`, }
params },
); order: ['issued DESC'],
fields: ['id', 'ref', 'issued', 'amount', 'hasPdf'],
limit: 100
};
const { data } = await api.get('InvoiceOuts', {
params: {
filter: JSON.stringify(filter)
}
});
invoices.value = data;
}; };
onMounted(async () => { onMounted(async () => {
await fetchInvoices();
for (let year = currentYear.value - 5; year <= currentYear.value; year++) { for (let year = currentYear.value - 5; year <= currentYear.value; year++) {
years.value.push(year); years.value.push(year);
} }
}); });
onUserId(fetchInvoices);
</script> </script>
<template> <template>

View File

@ -8,6 +8,7 @@ import VnInput from 'src/components/common/VnInput.vue';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import VnList from 'src/components/ui/VnList.vue'; import VnList from 'src/components/ui/VnList.vue';
import { onUserId } from 'src/utils/onUserId';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { currency, formatDateTitle } from 'src/lib/filters.js'; import { currency, formatDateTitle } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv'; import { tpvStore } from 'stores/tpv';
@ -16,7 +17,7 @@ import { storeToRefs } from 'pinia';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const jApi = inject('jApi'); const api = inject('api');
const { notify } = useNotify(); const { notify } = useNotify();
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
@ -30,8 +31,16 @@ const tpv = tpvStore();
onMounted(async () => { onMounted(async () => {
await tpv.check(route); await tpv.check(route);
orders.value = await jApi.query('CALL myTicket_list(NULL, NULL)'); const myTickets = await api.post('applications/myTicket_list/execute-proc', {
debt.value = await jApi.getValue('SELECT -myClient_getDebt(NULL)'); schema: 'hedera',
params: [null, null],
});
orders.value = myTickets.data;
});
onUserId(async (userId) => {
const myClientDebt = await api.get(`clients/${userId}/getDebt`);
debt.value = -myClientDebt.data;
}); });
const onPayClick = async () => { const onPayClick = async () => {

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, inject, onMounted } from 'vue'; import { ref, inject } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
@ -11,8 +11,10 @@ import { useVnConfirm } from 'src/composables/useVnConfirm.js';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useAppStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { onUserId } from 'src/utils/onUserId';
const jApi = inject('jApi'); const jApi = inject('jApi');
const api = inject('api');
const { t } = useI18n(); const { t } = useI18n();
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify(); const { notify } = useNotify();
@ -23,18 +25,48 @@ const router = useRouter();
const loading = ref(false); const loading = ref(false);
const orders = ref([]); const orders = ref([]);
const getOrders = async () => { const getOrders = async (clientFk) => {
try { try {
loading.value = true; loading.value = true;
orders.value = await jApi.query(
`SELECT o.id, o.sent, o.deliveryMethodFk, o.taxableBase, const filter = {
a.nickname, am.description agency where: {
FROM myOrder o clientFk,
JOIN myAddress a ON a.id = o.addressFk isConfirmed: false,
JOIN vn.agencyMode am ON am.id = o.agencyModeFk source_app: 'WEB',
WHERE NOT o.isConfirmed },
ORDER BY o.sent DESC` include: [
); {
relation: 'address',
scope: {
fields: ['nickname', 'city'],
}
},
{
relation: 'agencyMode',
scope: {
fields: ['description'],
}
},
],
fields: [
'id',
'landed',
'delivery_method_id',
'taxableBase',
'addressFk',
'agencyModeFk'
]
};
const { data: salixOrders } = await api.get('Orders', {
params: {
filter: JSON.stringify(filter)
}
});
orders.value = salixOrders;
loading.value = false; loading.value = false;
} catch (error) { } catch (error) {
console.error('Error getting orders:', error); console.error('Error getting orders:', error);
@ -43,14 +75,7 @@ const getOrders = async () => {
const removeOrder = async (id, index) => { const removeOrder = async (id, index) => {
try { try {
await jApi.execQuery( await api.delete(`Orders/${id}`);
`START TRANSACTION;
DELETE FROM hedera.myOrder WHERE ((id = #id));
COMMIT`,
{
id
}
);
orders.value.splice(index, 1); orders.value.splice(index, 1);
notify(t('dataSaved'), 'positive'); notify(t('dataSaved'), 'positive');
} catch (error) { } catch (error) {
@ -63,9 +88,8 @@ const loadOrder = orderId => {
router.push({ name: 'catalog' }); router.push({ name: 'catalog' });
}; };
onMounted(async () => { onUserId(getOrders);
getOrders();
});
</script> </script>
<template> <template>
@ -93,11 +117,11 @@ onMounted(async () => {
> >
<template #content> <template #content>
<QItemLabel class="text-bold q-mb-sm"> <QItemLabel class="text-bold q-mb-sm">
{{ formatDateTitle(order.sent) }} {{ formatDateTitle(order.landed) }}
</QItemLabel> </QItemLabel>
<QItemLabel> #{{ order.id }} </QItemLabel> <QItemLabel> #{{ order.id }} </QItemLabel>
<QItemLabel>{{ order.nickname }}</QItemLabel> <QItemLabel>{{ order.address.nickname }}</QItemLabel>
<QItemLabel>{{ order.agency }}</QItemLabel> <QItemLabel>{{ order.agencyMode.description }}</QItemLabel>
<QItemLabel>{{ currency(order.taxableBase) }}</QItemLabel> <QItemLabel>{{ currency(order.taxableBase) }}</QItemLabel>
</template> </template>
<template #actions> <template #actions>

18
src/utils/onUserId.js Normal file
View File

@ -0,0 +1,18 @@
import { watch } from 'vue';
import { useUserStore } from 'stores/user';
const userStore = useUserStore();
export const onUserId = (cb) => watch(
() => userStore?.user?.id,
async userId => {
if (!userId) return;
try {
await cb(userId);
} catch (error) {
console.error(error);
}
},
{ immediate: true }
);