Merge branch '7311_boilerplate_users' of https://gitea.verdnatura.es/verdnatura/salix-front into feature/MailAliases

This commit is contained in:
William Buezas 2024-05-30 08:38:23 -03:00
commit 0cf4f41d19
42 changed files with 3633 additions and 4 deletions

View File

@ -20,7 +20,13 @@ const itemComputed = computed(() => {
});
</script>
<template>
<QItem active-class="bg-hover" :to="{ name: itemComputed.name }" clickable v-ripple>
<QItem
active-class="bg-hover"
class="min-height"
:to="{ name: itemComputed.name }"
clickable
v-ripple
>
<QItemSection avatar v-if="itemComputed.icon">
<QIcon :name="itemComputed.icon" />
</QItemSection>
@ -33,3 +39,9 @@ const itemComputed = computed(() => {
</QItemSection>
</QItem>
</template>
<style lang="scss" scoped>
.q-item {
min-height: 5vh;
}
</style>

View File

@ -242,6 +242,7 @@ const emit = defineEmits(['onFetch']);
width: 256px;
.header {
display: flex;
align-items: center;
}
.icons {
margin: 0 10px;

View File

@ -101,6 +101,7 @@ globals:
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
role: Role
created: Created
worker: Worker
now: Now
@ -1253,6 +1254,13 @@ monitor:
pageTitles:
monitors: Monitors
list: List
zone:
pageTitles:
zones: Zones
zonesList: Zones
deliveryList: Delivery days
upcomingList: Upcoming deliveries
components:
topbar: {}
itemsFilterPanel:

View File

@ -101,6 +101,7 @@ globals:
zonesList: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
role: Role
created: Fecha creación
worker: Trabajador
now: Ahora
@ -1240,14 +1241,21 @@ item/itemType:
summary: Resumen
zone:
pageTitles:
zones: Zona
zonesList: Zonas
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
role:
pageTitles:
zones: Zonas
list: Zonas
deliveryList: Días de entrega
upcomingList: Próximos repartos
monitor:
pageTitles:
monitors: Monitores
list: Listado
components:
topbar: {}
itemsFilterPanel:

View File

@ -0,0 +1,111 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters';
const { t } = useI18n();
const newAccountForm = reactive({
supplierFk: null,
travelFk: null,
companyFk: null,
});
</script>
<template>
<QPage>
<VnSubToolbar />
<pre>TODO <b>Cuentas</b></pre>
<FormModel
url-create="Entries"
model="account"
:form-initial-data="newAccountForm"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Supplier')"
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
:rules="validate('account.supplierFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Travel')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptions"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
:rules="validate('account.travelFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }} ({{
toDate(scope.opt?.shipped)
}}) &#x2192;
{{ scope.opt?.warehouseOutName }} ({{
toDate(scope.opt?.landed)
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Company')"
class="full-width"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
map-options
hide-selected
:required="true"
:rules="validate('account.companyFk')"
/>
</div>
</VnRow>
</template>
</FormModel>
</QPage>
</template>

View File

@ -0,0 +1,114 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'filters/index';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import AccountFilter from './AccountFilter.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import AccountSummary from './Card/AccountSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const STATE_COLOR = {
pending: 'warning',
managed: 'info',
resolved: 'positive',
};
function getApiUrl() {
return new URL(window.location).origin;
}
function stateColor(code) {
return STATE_COLOR[code];
}
function navigate(event, id) {
if (event.ctrlKey || event.metaKey)
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
router.push({ path: `/account/${id}` });
}
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<AccountFilter data-key="AccountList" />
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="AccountList"
url="Accounts/filter"
:order="['priority ASC', 'created DESC']"
auto-load
>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.clientName"
@click="navigate($event, row.id)"
v-for="row of rows"
>
<template #list-items>
<VnLv :label="t('account.list.customer')">
<template #value>
<span class="link" @click.stop>
{{ row.clientName }}
</span>
</template>
</VnLv>
<VnLv :label="t('account.list.assignedTo')">
<template #value>
<span @click.stop>
<VnUserLink
:name="row.workerName"
:worker-id="row.workerFk"
/>
</span>
</template>
</VnLv>
<VnLv
:label="t('account.list.created')"
:value="toDate(row.created)"
/>
<VnLv :label="t('account.list.state')">
<template #value>
<QBadge
text-color="black"
:color="stateColor(row.stateCode)"
dense
>
{{ row.stateDescription }}
</QBadge>
</template>
</VnLv>
</template>
<template #actions>
<QBtn
:label="t('globals.description')"
@click.stop
outline
style="margin-top: 15px"
>
<CustomerDescriptorProxy :id="row.clientFk" />
</QBtn>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, AccountSummary)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template>

View File

@ -0,0 +1,75 @@
<script setup>
import { onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue';
const { t } = useI18n();
const filter = computed(() => ({
fields: ['id', 'created', 'userId'],
include: { relation: 'user', scope: { fields: ['username'] } },
order: 'created DESC',
limit: 20,
}));
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const urlPath = computed(() => `AccessTokens`);
const entityId = computed(() => $props.id || useRoute().params.id);
onMounted(async () => {});
</script>
<template>
<QPage class="column items-center q-pa-md">
<div class="full-width" style="max-width: 400px">
<VnPaginate
ref="paginateRef"
data-key="AccessTokens"
:filter="filter"
:url="urlPath"
auto-load
>
<template #body="{ rows }">
<QTable :rows="data" :columns="columns" hide-header>
<template #body="{ row, rowIndex }">
<QTr>
<QTd>
<span>{{ row.warehouse?.name }}</span>
</QTd>
<QTd style="width: 50px !important">
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="
openConfirmationModal(
t('warehouses.deleteTitle'),
t('warehouses.deleteSubtitle'),
() => deleteWarehouse(row, rows, rowIndex)
)
"
>
<QTooltip>
{{ t('warehouses.delete') }}
</QTooltip>
</QIcon>
</QTd>
</QTr>
</template>
</QTable>
</template>
</VnPaginate>
</div>
<QPageSticky position="bottom-right" :offset="[18, 18]">
<QBtn fab icon="add" color="primary" @click="createMailAlias()">
<QTooltip>{{ t('warehouses.add') }}</QTooltip>
</QBtn>
</QPageSticky>
</QPage>
</template>

View File

@ -0,0 +1,75 @@
<script setup>
import { onMounted, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnPaginate from 'components/ui/VnPaginate.vue';
const { t } = useI18n();
const filter = computed(() => ({
fields: ['id', 'created', 'userId'],
include: { relation: 'user', scope: { fields: ['username'] } },
order: 'created DESC',
limit: 20,
}));
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const urlPath = computed(() => `AccessTokens`);
const entityId = computed(() => $props.id || useRoute().params.id);
onMounted(async () => {});
</script>
<template>
<QPage class="column items-center q-pa-md">
<div class="full-width" style="max-width: 400px">
<VnPaginate
ref="paginateRef"
data-key="AccessTokens"
:filter="filter"
:url="urlPath"
auto-load
>
<template #body="{ rows }">
<QTable :rows="data" :columns="columns" hide-header>
<template #body="{ row, rowIndex }">
<QTr>
<QTd>
<span>{{ row.warehouse?.name }}</span>
</QTd>
<QTd style="width: 50px !important">
<QIcon
name="delete"
size="sm"
class="cursor-pointer"
color="primary"
@click="
openConfirmationModal(
t('warehouses.deleteTitle'),
t('warehouses.deleteSubtitle'),
() => deleteWarehouse(row, rows, rowIndex)
)
"
>
<QTooltip>
{{ t('warehouses.delete') }}
</QTooltip>
</QIcon>
</QTd>
</QTr>
</template>
</QTable>
</template>
</VnPaginate>
</div>
<QPageSticky position="bottom-right" :offset="[18, 18]">
<QBtn fab icon="refresh" color="primary" @click="refresh()">
<QTooltip>{{ t('warehouses.refresh') }}</QTooltip>
</QBtn>
</QPageSticky>
</QPage>
</template>

View File

@ -0,0 +1,159 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import FetchData from 'components/FetchData.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useStateStore } from 'stores/useStateStore';
import { useState } from 'src/composables/useState';
import { toDate } from 'src/filters';
const state = useState();
const { t } = useI18n();
const route = useRoute();
const stateStore = useStateStore();
const user = state.getUser();
const newAccountForm = reactive({
supplierFk: null,
travelFk: Number(route.query?.travelFk) || null,
companyFk: user.value.companyFk || null,
});
const suppliersOptions = ref([]);
const travelsOptions = ref([]);
const companiesOptions = ref([]);
</script>
<template>
<FetchData
url="Suppliers"
:filter="{ fields: ['id', 'nickname'] }"
order="nickname"
@on-fetch="(data) => (suppliersOptions = data)"
auto-load
/>
<FetchData
url="Travels/filter"
:filter="{ fields: ['id', 'warehouseInName'] }"
order="id"
@on-fetch="(data) => (travelsOptions = data)"
auto-load
/>
<FetchData
ref="companiesRef"
url="Companies"
:filter="{ fields: ['id', 'code'] }"
order="code"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
url="Entries/filter"
custom-route-redirect-name="AccountSummary"
data-key="AccountSummary"
:label="t('Search entries')"
:info="t('You can search by account reference')"
/>
</Teleport>
</template>
<QPage>
<VnSubToolbar />
<FormModel
url-create="Entries"
model="account"
:form-initial-data="newAccountForm"
>
<template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Supplier')"
class="full-width"
v-model="data.supplierFk"
:options="suppliersOptions"
option-value="id"
option-label="nickname"
hide-selected
:required="true"
:rules="validate('account.supplierFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
<QItemLabel caption>
#{{ scope.opt?.id }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Travel')"
class="full-width"
v-model="data.travelFk"
:options="travelsOptions"
option-value="id"
option-label="warehouseInName"
map-options
hide-selected
:required="true"
:rules="validate('account.travelFk')"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel
>{{ scope.opt?.agencyModeName }} -
{{ scope.opt?.warehouseInName }} ({{
toDate(scope.opt?.shipped)
}}) &#x2192;
{{ scope.opt?.warehouseOutName }} ({{
toDate(scope.opt?.landed)
}})</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
:label="t('Company')"
class="full-width"
v-model="data.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
map-options
hide-selected
:required="true"
:rules="validate('account.companyFk')"
/>
</div>
</VnRow>
</template>
</FormModel>
</QPage>
</template>
<i18n>
es:
Supplier: Proveedor
Travel: Envío
Company: Empresa
</i18n>

View File

@ -0,0 +1,225 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const workers = ref();
const states = ref();
</script>
<template>
<FetchData url="AccountStates" @on-fetch="(data) => (states = data)" auto-load />
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('Customer ID')"
v-model="params.clientFk"
lazy-rules
is-outlined
>
<template #prepend>
<QIcon name="badge" size="xs"></QIcon> </template
></VnInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('Client Name')"
v-model="params.clientName"
lazy-rules
is-outlined
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Salesperson')"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Attender')"
v-model="params.attenderFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Responsible')"
v-model="params.accountResponsibleFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!states">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="states">
<VnSelect
:label="t('State')"
v-model="params.accountStateFk"
@update:model-value="searchFn()"
:options="states"
option-value="id"
option-label="description"
emit-value
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('myTeam')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QSeparator />
<QExpansionItem :label="t('More options')" expand-separator>
<!-- <QItem>
<QItemSection>
<qSelect
:label="t('Item')"
v-model="params.itemFk"
:options="items"
:loading="loading"
@filter="filterFn"
@virtual-scroll="onScroll"
option-value="id"
option-label="name"
emit-value
map-options
/>
</QItemSection>
</QItem> -->
<QItem>
<QItemSection>
<VnInputDate
v-model="params.created"
:label="t('Created')"
is-outlined
/>
</QItemSection>
</QItem>
</QExpansionItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
search: Contains
clientFk: Customer
clientName: Customer
salesPersonFk: Salesperson
attenderFk: Attender
accountResponsibleFk: Responsible
accountStateFk: State
created: Created
myTeam: My team
es:
params:
search: Contiene
clientFk: Cliente
clientName: Cliente
salesPersonFk: Comercial
attenderFk: Asistente
accountResponsibleFk: Responsable
accountStateFk: Estado
created: Creada
Customer ID: ID cliente
Client Name: Nombre del cliente
Salesperson: Comercial
Attender: Asistente
Responsible: Responsable
State: Estado
Item: Artículo
Created: Creada
More options: Más opciones
myTeam: Mi equipo
</i18n>

View File

@ -0,0 +1,65 @@
<script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const { t } = useI18n();
const newAccountForm = reactive({
supplierFk: null,
travelFk: null,
companyFk: null,
});
</script>
<template>
<QPage>
<VnSubToolbar> </VnSubToolbar>
<pre>TODO <b>LDAP</b></pre>
<FormModel
url-create="Entries"
model="account"
:form-initial-data="newAccountForm"
>
<template #moreActions>
<QBtnGroup push class="q-gutter-x-sm">
<QBtn round flat color="primary" :label="t('ldap.testConnection')">
<QTooltip>
{{ t('ldap.testConnection') }}
</QTooltip>
</QBtn>
</QBtnGroup>
</template>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('ldap.enableSync')"
v-model="data.enableSync"
/>
</div>
</VnRow>
<template v-if="data.enableSync">
<VnInput :label="t('ldap.server')" clearable v-model="data.server" />
<VnInput :label="t('ldap.rdn')" clearable v-model="data.rdn" />
<VnInput
:label="t('ldap.passwordAD')"
clearable
type="password"
v-model="data.passwordAD"
/>
<VnInput :label="t('ldap.userDN')" clearable v-model="data.userDN" />
<VnInput
:label="t('ldap.groupDN')"
clearable
v-model="data.groupDN"
/>
</template>
</template>
</FormModel>
</QPage>
</template>

View File

@ -0,0 +1,109 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'filters/index';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnSearchbar from 'components/ui/VnSearchbar.vue';
import AccountFilter from './AccountFilter.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import AccountSummary from './Card/AccountSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const filter = {
fields: ['id', 'nickname', 'name', 'role'],
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
};
function getApiUrl() {
return new URL(window.location).origin;
}
function navigate(event, id) {
if (event.ctrlKey || event.metaKey)
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
router.push({ path: `/account/${id}` });
}
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#searchbar">
<VnSearchbar
data-key="AccountList"
:label="t('account.search')"
:info="t('You can search by account id or customer name')"
/>
</Teleport>
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<!-- <AccountFilter data-key="AccountList" /> -->
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
:filter="filter"
data-key="AccountList"
url="VnUsers/preview"
auto-load
>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.clientName"
@click="navigate($event, row.id)"
v-for="row of rows"
>
<template #list-items>
<VnLv :label="t('account.card.name')" :value="row.nickname">
</VnLv>
<VnLv
:label="t('account.card.nickname')"
:value="row.username"
>
</VnLv>
</template>
<template #actions>
<QBtn
:label="t('globals.description')"
@click.stop
outline
style="margin-top: 15px"
>
</QBtn>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, AccountSummary)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
</QPage>
</template>

View File

@ -0,0 +1,17 @@
<script setup>
import { useStateStore } from 'stores/useStateStore';
import LeftMenu from 'components/LeftMenu.vue';
const stateStore = useStateStore();
</script>
<template>
<QDrawer v-model="stateStore.leftDrawer" show-if-above :width="256">
<QScrollArea class="fit text-grey-8">
<LeftMenu />
</QScrollArea>
</QDrawer>
<QPageContainer>
<RouterView></RouterView>
</QPageContainer>
</template>

View File

@ -0,0 +1,82 @@
<script setup>
import { reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const { t } = useI18n();
const newAccountForm = reactive({
supplierFk: null,
travelFk: null,
companyFk: null,
});
</script>
<template>
<QPage>
<VnSubToolbar></VnSubToolbar>
<pre>TODO <b>SAMBA</b></pre>
<FormModel
url-create="Entries"
model="account"
:form-initial-data="newAccountForm"
>
<template #moreActions>
<QBtn round flat color="primary" :label="t('samba.testConnection')">
<QTooltip>
{{ t('samba.testConnection') }}
</QTooltip>
</QBtn>
<QBtn round flat color="primary" :label="t('samba.verifyCertificate')">
<QTooltip>
{{ t('samba.verifyCertificate') }}
</QTooltip>
</QBtn>
</template>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<QCheckbox
:label="t('samba.enableSync')"
v-model="data.enableSync"
/>
</div>
</VnRow>
<template v-if="data.enableSync">
<VnInput
:label="t('samba.domainController')"
clearable
v-model="data.domainController"
/>
<VnInput
:label="t('samba.domainAD')"
clearable
v-model="data.domainAD"
/>
<VnInput
:label="t('samba.groupDN')"
clearable
v-model="data.groupDN"
/>
<VnInput :label="t('samba.userAD')" clearable v-model="data.userAD" />
<VnInput
:label="t('samba.passwordAD')"
clearable
type="password"
v-model="data.passwordAD"
/>
<VnInput
:label="t('samba.domainPart')"
clearable
v-model="data.domainPart"
/>
</template>
</template>
</FormModel>
</QPage>
</template>

View File

@ -0,0 +1,55 @@
<script setup>
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import VnSelect from 'src/components/common/VnSelect.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const route = useRoute();
const { t } = useI18n();
const accountFilter = {
where: { id: route.params.id },
fields: ['id', 'email', 'nickname', 'name', 'accountStateFk', 'packages', 'pickup'],
include: [],
};
</script>
<template>
<FormModel
:url="`VnUsers/preview`"
:url-update="`VnUsers/${route.params.id}/update-user`"
:filter="accountFilter"
model="Accounts"
auto-load
>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.name" :label="t('account.card.nickname')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.nickname" :label="t('account.card.alias')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.email" :label="t('account.card.email')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnSelect
v-model="data.lang"
:options="['es', 'en']"
:label="t('account.card.lang')"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,15 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import AccountDescriptor from './AccountDescriptor.vue';
</script>
<template>
<VnCard
data-key="Account"
base-url="Accounts"
:descriptor="AccountDescriptor"
searchbar-data-key="AccountList"
searchbar-url="Accounts/filter"
searchbar-label="Search account"
searchbar-info="You can search by account id or customer name"
/>
</template>

View File

@ -0,0 +1,121 @@
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toDate, toPercentage } from 'src/filters';
import { useState } from 'src/composables/useState';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { getUrl } from 'src/composables/getUrl';
import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
import { useSession } from 'src/composables/useSession';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const state = useState();
const { t } = useI18n();
const salixUrl = ref();
const { getTokenMultimedia } = useSession();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.nickname, entity.id));
const filter = {
where: { id: entityId },
fields: ['id', 'nickname', 'name', 'role'],
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
};
function getAccountAvatar() {
const token = getTokenMultimedia();
return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`;
}
</script>
<template>
<CardDescriptor
ref="descriptor"
:url="`VnUsers/preview`"
:filter="filter"
module="Account"
@on-fetch="setData"
data-key="accountData"
:title="data.title"
:subtitle="data.subtitle"
>
<template #header-extra-action>
<QBtn
round
flat
size="md"
color="white"
icon="face"
:to="{ name: 'AccountList' }"
>
<QTooltip>
{{ t('Go to module index') }}
</QTooltip>
</QBtn>
</template>
<template #menu="{ entity }">
<AccountDescriptorMenu :account="entity" />
</template>
<template #before>
<QImg :src="getAccountAvatar()" class="photo">
<template #error>
<div
class="absolute-full picture text-center q-pa-md flex flex-center"
>
<div>
<div class="text-grey-5" style="opacity: 0.4; font-size: 5vh">
<QIcon name="vn:claims" />
</div>
<div class="text-grey-5" style="opacity: 0.4">
{{ t('account.imageNotFound') }}
</div>
</div>
</div>
</template>
</QImg>
</template>
<template #body="{ entity }">
<VnLv :label="t('account.card.nickname')" :value="entity.nickname" />
<VnLv :label="t('account.card.role')" :value="entity.role.name" />
</template>
<template #actions="{ entity }">
<QCardActions>
<QBtn
round
flat
size="md"
color="primary"
class="fill-icon"
icon="contact_mail"
:href="salixUrl + 'ticket/' + entity.ticketFk + '/tracking/index'"
>
<QTooltip>{{ t('account.card.ticketTracking') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>
</template>
<style scoped>
.q-item__label {
margin-top: 0;
}
</style>
<i18n>
en:
accountRate: Claming rate
es:
accountRate: Ratio de reclamación
</i18n>

View File

@ -0,0 +1,115 @@
<script setup>
import axios from 'axios';
import { ref } from 'vue';
import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { usePrintService } from 'composables/usePrintService';
const $props = defineProps({
account: {
type: Object,
required: true,
},
});
import { useVnConfirm } from 'composables/useVnConfirm';
const { t } = useI18n();
const { openReport } = usePrintService();
const { openConfirmationModal } = useVnConfirm();
const account = ref($props.account);
</script>
<template>
<QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('Confirm deletion'),
t('Are you sure...TODO'),
setPassword
)
"
>
<QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
</QItem>
<QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('account.card.actions.disableAccount.title'),
t('account.card.actions.disableAccount.subtitle'),
disableAccount
)
"
>
<QItemSection>{{ t('account.card.actions.disableAccount.name') }}</QItemSection>
</QItem>
<QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('account.card.actions.disableUser.title'),
t('account.card.actions.disableUser.title'),
actiondisableUser
)
"
>
<QItemSection>{{ t('account.card.actions.disableUser.name') }}</QItemSection>
</QItem>
<QItem
v-ripple
clickable
@click="
openConfirmationModal(
t('account.card.actions.sync.title'),
t('account.card.actions.sync.subtitle'),
sync
)
"
>
<QItemSection>{{ t('account.card.actions.sync.name') }}</QItemSection>
</QItem>
<QSeparator />
<QItem
@click="
openConfirmationModal(
t('account.card.actions.delete.title'),
t('account.card.actions.delete.subTitle'),
removeAccount
)
"
v-ripple
clickable
>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('account.card.actions.delete.name') }}</QItemSection>
</QItem>
</template>
<i18n>
{
"en": {
"pickupOrder": "Pickup order",
"openPickupOrder": "Open pickup order",
"sendPickupOrder": "Send pickup order",
"deleteAccount": "Delete account",
"confirmDeletion": "Confirm deletion",
"confirmDeletionMessage": "Are you sure you want to delete this account?"
},
"es": {
"pickupOrder": "Orden de recogida",
"openPickupOrder": "Abrir orden de recogida",
"sendPickupOrder": "Enviar orden de recogida",
"deleteAccount": "Eliminar reclamación",
"confirmDeletion": "Confirmar eliminación",
"confirmDeletionMessage": "Seguro que quieres eliminar esta reclamación?"
}
}
</i18n>

View File

@ -0,0 +1,256 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile';
const route = useRoute();
const { t } = useI18n();
const accountDevelopmentForm = ref();
const accountReasons = ref([]);
const accountResults = ref([]);
const accountResponsibles = ref([]);
const accountRedeliveries = ref([]);
const workers = ref([]);
const selected = ref([]);
const saveButtonRef = ref();
const developmentsFilter = {
fields: [
'id',
'accountFk',
'accountReasonFk',
'accountResultFk',
'accountResponsibleFk',
'workerFk',
'accountRedeliveryFk',
],
where: {
accountFk: route.params.id,
},
};
const columns = computed(() => [
{
name: 'accountReason',
label: t('Reason'),
field: (row) => row.accountReasonFk,
sortable: true,
options: accountReasons.value,
required: true,
model: 'accountReasonFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 1,
align: 'left',
},
{
name: 'accountResult',
label: t('Result'),
field: (row) => row.accountResultFk,
sortable: true,
options: accountResults.value,
required: true,
model: 'accountResultFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 2,
align: 'left',
},
{
name: 'accountResponsible',
label: t('Responsible'),
field: (row) => row.accountResponsibleFk,
sortable: true,
options: accountResponsibles.value,
required: true,
model: 'accountResponsibleFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 3,
align: 'left',
},
{
name: 'worker',
label: t('Worker'),
field: (row) => row.workerFk,
sortable: true,
options: workers.value,
model: 'workerFk',
optionValue: 'id',
optionLabel: 'nickname',
tabIndex: 4,
align: 'left',
},
{
name: 'accountRedelivery',
label: t('Redelivery'),
field: (row) => row.accountRedeliveryFk,
sortable: true,
options: accountRedeliveries.value,
required: true,
model: 'accountRedeliveryFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 5,
align: 'left',
},
]);
</script>
<template>
<FetchData
url="AccountReasons"
order="description"
@on-fetch="(data) => (accountReasons = data)"
auto-load
/>
<FetchData
url="AccountResults"
order="description"
@on-fetch="(data) => (accountResults = data)"
auto-load
/>
<FetchData
url="AccountResponsibles"
order="description"
@on-fetch="(data) => (accountResponsibles = data)"
auto-load
/>
<FetchData
url="AccountRedeliveries"
order="description"
@on-fetch="(data) => (accountRedeliveries = data)"
auto-load
/>
<FetchData
url="Workers/search"
:where="{ active: 1 }"
order="name ASC"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<CrudModel
data-key="AccountDevelopments"
url="AccountDevelopments"
model="accountDevelopment"
:filter="developmentsFilter"
ref="accountDevelopmentForm"
:data-required="{ accountFk: route.params.id }"
v-model:selected="selected"
auto-load
@save-changes="$router.push(`/account/${route.params.id}/action`)"
:default-save="false"
>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
row-key="$index"
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
table-header-class="text-left"
>
<template #body-cell="{ row, col }">
<QTd
auto-width
@keyup.ctrl.enter.stop="accountDevelopmentForm.saveChanges()"
>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
>
<template #option="scope" v-if="col.name == 'worker'">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}
{{ scope.opt?.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard
bordered
flat
@keyup.ctrl.enter.stop="accountDevelopmentForm?.saveChanges()"
>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<QItemSection>
<VnSelect
:label="col.label"
v-model="props.row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
dense
input-debounce="0"
:autofocus="col.tabIndex == 1"
hide-selected
/>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
<template #moreAfterActions>
<QBtn
:label="tMobile('globals.save')"
ref="saveButtonRef"
color="primary"
icon="save"
:disable="!accountDevelopmentForm?.hasChanges"
@click="accountDevelopmentForm?.onSubmit"
:title="t('globals.save')"
/>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
fab
color="primary"
icon="add"
@keydown.tab.prevent="saveButtonRef.$el.focus()"
@click="accountDevelopmentForm.insert()"
/>
</QPageSticky>
</template>
<style lang="scss" scoped>
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
</style>
<i18n>
es:
Reason: Motivo
Result: Consecuencia
Responsible: Responsable
Worker: Trabajador
Redelivery: Devolución
</i18n>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="VnUser"></VnLog>
</template>

View File

@ -0,0 +1,256 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile';
const route = useRoute();
const { t } = useI18n();
const accountDevelopmentForm = ref();
const accountReasons = ref([]);
const accountResults = ref([]);
const accountResponsibles = ref([]);
const accountRedeliveries = ref([]);
const workers = ref([]);
const selected = ref([]);
const saveButtonRef = ref();
const developmentsFilter = {
fields: [
'id',
'accountFk',
'accountReasonFk',
'accountResultFk',
'accountResponsibleFk',
'workerFk',
'accountRedeliveryFk',
],
where: {
accountFk: route.params.id,
},
};
const columns = computed(() => [
{
name: 'accountReason',
label: t('Reason'),
field: (row) => row.accountReasonFk,
sortable: true,
options: accountReasons.value,
required: true,
model: 'accountReasonFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 1,
align: 'left',
},
{
name: 'accountResult',
label: t('Result'),
field: (row) => row.accountResultFk,
sortable: true,
options: accountResults.value,
required: true,
model: 'accountResultFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 2,
align: 'left',
},
{
name: 'accountResponsible',
label: t('Responsible'),
field: (row) => row.accountResponsibleFk,
sortable: true,
options: accountResponsibles.value,
required: true,
model: 'accountResponsibleFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 3,
align: 'left',
},
{
name: 'worker',
label: t('Worker'),
field: (row) => row.workerFk,
sortable: true,
options: workers.value,
model: 'workerFk',
optionValue: 'id',
optionLabel: 'nickname',
tabIndex: 4,
align: 'left',
},
{
name: 'accountRedelivery',
label: t('Redelivery'),
field: (row) => row.accountRedeliveryFk,
sortable: true,
options: accountRedeliveries.value,
required: true,
model: 'accountRedeliveryFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 5,
align: 'left',
},
]);
</script>
<template>
<FetchData
url="AccountReasons"
order="description"
@on-fetch="(data) => (accountReasons = data)"
auto-load
/>
<FetchData
url="AccountResults"
order="description"
@on-fetch="(data) => (accountResults = data)"
auto-load
/>
<FetchData
url="AccountResponsibles"
order="description"
@on-fetch="(data) => (accountResponsibles = data)"
auto-load
/>
<FetchData
url="AccountRedeliveries"
order="description"
@on-fetch="(data) => (accountRedeliveries = data)"
auto-load
/>
<FetchData
url="Workers/search"
:where="{ active: 1 }"
order="name ASC"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<CrudModel
data-key="AccountDevelopments"
url="AccountDevelopments"
model="accountDevelopment"
:filter="developmentsFilter"
ref="accountDevelopmentForm"
:data-required="{ accountFk: route.params.id }"
v-model:selected="selected"
auto-load
@save-changes="$router.push(`/account/${route.params.id}/action`)"
:default-save="false"
>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
row-key="$index"
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
table-header-class="text-left"
>
<template #body-cell="{ row, col }">
<QTd
auto-width
@keyup.ctrl.enter.stop="accountDevelopmentForm.saveChanges()"
>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
>
<template #option="scope" v-if="col.name == 'worker'">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}
{{ scope.opt?.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard
bordered
flat
@keyup.ctrl.enter.stop="accountDevelopmentForm?.saveChanges()"
>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<QItemSection>
<VnSelect
:label="col.label"
v-model="props.row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
dense
input-debounce="0"
:autofocus="col.tabIndex == 1"
hide-selected
/>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
<template #moreAfterActions>
<QBtn
:label="tMobile('globals.save')"
ref="saveButtonRef"
color="primary"
icon="save"
:disable="!accountDevelopmentForm?.hasChanges"
@click="accountDevelopmentForm?.onSubmit"
:title="t('globals.save')"
/>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
fab
color="primary"
icon="add"
@keydown.tab.prevent="saveButtonRef.$el.focus()"
@click="accountDevelopmentForm.insert()"
/>
</QPageSticky>
</template>
<style lang="scss" scoped>
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
</style>
<i18n>
es:
Reason: Motivo
Result: Consecuencia
Responsible: Responsable
Worker: Trabajador
Redelivery: Devolución
</i18n>

View File

@ -0,0 +1,256 @@
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue';
import VnSelect from 'components/common/VnSelect.vue';
import { tMobile } from 'composables/tMobile';
const route = useRoute();
const { t } = useI18n();
const accountDevelopmentForm = ref();
const accountReasons = ref([]);
const accountResults = ref([]);
const accountResponsibles = ref([]);
const accountRedeliveries = ref([]);
const workers = ref([]);
const selected = ref([]);
const saveButtonRef = ref();
const developmentsFilter = {
fields: [
'id',
'accountFk',
'accountReasonFk',
'accountResultFk',
'accountResponsibleFk',
'workerFk',
'accountRedeliveryFk',
],
where: {
accountFk: route.params.id,
},
};
const columns = computed(() => [
{
name: 'accountReason',
label: t('Reason'),
field: (row) => row.accountReasonFk,
sortable: true,
options: accountReasons.value,
required: true,
model: 'accountReasonFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 1,
align: 'left',
},
{
name: 'accountResult',
label: t('Result'),
field: (row) => row.accountResultFk,
sortable: true,
options: accountResults.value,
required: true,
model: 'accountResultFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 2,
align: 'left',
},
{
name: 'accountResponsible',
label: t('Responsible'),
field: (row) => row.accountResponsibleFk,
sortable: true,
options: accountResponsibles.value,
required: true,
model: 'accountResponsibleFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 3,
align: 'left',
},
{
name: 'worker',
label: t('Worker'),
field: (row) => row.workerFk,
sortable: true,
options: workers.value,
model: 'workerFk',
optionValue: 'id',
optionLabel: 'nickname',
tabIndex: 4,
align: 'left',
},
{
name: 'accountRedelivery',
label: t('Redelivery'),
field: (row) => row.accountRedeliveryFk,
sortable: true,
options: accountRedeliveries.value,
required: true,
model: 'accountRedeliveryFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 5,
align: 'left',
},
]);
</script>
<template>
<FetchData
url="AccountReasons"
order="description"
@on-fetch="(data) => (accountReasons = data)"
auto-load
/>
<FetchData
url="AccountResults"
order="description"
@on-fetch="(data) => (accountResults = data)"
auto-load
/>
<FetchData
url="AccountResponsibles"
order="description"
@on-fetch="(data) => (accountResponsibles = data)"
auto-load
/>
<FetchData
url="AccountRedeliveries"
order="description"
@on-fetch="(data) => (accountRedeliveries = data)"
auto-load
/>
<FetchData
url="Workers/search"
:where="{ active: 1 }"
order="name ASC"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<CrudModel
data-key="AccountDevelopments"
url="AccountDevelopments"
model="accountDevelopment"
:filter="developmentsFilter"
ref="accountDevelopmentForm"
:data-required="{ accountFk: route.params.id }"
v-model:selected="selected"
auto-load
@save-changes="$router.push(`/account/${route.params.id}/action`)"
:default-save="false"
>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
row-key="$index"
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
table-header-class="text-left"
>
<template #body-cell="{ row, col }">
<QTd
auto-width
@keyup.ctrl.enter.stop="accountDevelopmentForm.saveChanges()"
>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
>
<template #option="scope" v-if="col.name == 'worker'">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption>
{{ scope.opt?.nickname }}
{{ scope.opt?.code }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard
bordered
flat
@keyup.ctrl.enter.stop="accountDevelopmentForm?.saveChanges()"
>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList dense>
<QItem v-for="col in props.cols" :key="col.name">
<QItemSection>
<VnSelect
:label="col.label"
v-model="props.row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
dense
input-debounce="0"
:autofocus="col.tabIndex == 1"
hide-selected
/>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
<template #moreAfterActions>
<QBtn
:label="tMobile('globals.save')"
ref="saveButtonRef"
color="primary"
icon="save"
:disable="!accountDevelopmentForm?.hasChanges"
@click="accountDevelopmentForm?.onSubmit"
:title="t('globals.save')"
/>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
fab
color="primary"
icon="add"
@keydown.tab.prevent="saveButtonRef.$el.focus()"
@click="accountDevelopmentForm.insert()"
/>
</QPageSticky>
</template>
<style lang="scss" scoped>
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
</style>
<i18n>
es:
Reason: Motivo
Result: Consecuencia
Responsible: Responsable
Worker: Trabajador
Redelivery: Devolución
</i18n>

View File

@ -0,0 +1,338 @@
<script setup>
import axios from 'axios';
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { useRoute } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { useArrayData } from 'composables/useArrayData';
import { toDate, toCurrency, toPercentage } from 'filters/index';
import CrudModel from 'components/CrudModel.vue';
import FetchData from 'components/FetchData.vue';
import VnDiscount from 'components/common/vnDiscount.vue';
const quasar = useQuasar();
const route = useRoute();
const { t } = useI18n();
const stateStore = useStateStore();
const arrayData = useArrayData('AccountLines');
const store = arrayData.store;
const accountFilter = {
fields: ['ticketFk'],
};
const linesFilter = {
include: {
relation: 'sale',
scope: {
fields: ['concept', 'ticketFk', 'price', 'quantity', 'discount', 'itemFk'],
include: {
relation: 'ticket',
},
},
},
};
const accountLinesForm = ref();
const account = ref(null);
async function onFetchAccount(data) {
account.value = data;
fetchMana();
}
const amount = ref();
const amountAccounted = ref();
async function onFetch(rows, newRows) {
if (newRows) rows = newRows;
amount.value = 0;
amountAccounted.value = 0;
if (!rows || !rows.length) return;
for (const row of rows) {
const { sale } = row;
amount.value = amount.value + totalRow(sale);
const price = row.quantity * sale.price;
const discount = (sale.discount * price) / 100;
amountAccounted.value = amountAccounted.value + (price - discount);
}
}
function totalRow({ price, quantity, discount }) {
const amount = price * quantity;
const appliedDiscount = (discount * amount) / 100;
return amount - appliedDiscount;
}
const columns = computed(() => [
{
name: 'dated',
label: t('Delivered'),
field: ({ sale: { ticket } }) => toDate(ticket.landed),
sortable: true,
},
{
name: 'quantity',
label: t('Quantity'),
field: ({ sale }) => sale.quantity,
sortable: true,
},
{
name: 'accounted',
label: t('Accounted'),
field: (row) => row.quantity,
sortable: true,
},
{
name: 'description',
label: t('Description'),
field: ({ sale }) => sale.concept,
},
{
name: 'price',
label: t('Price'),
field: ({ sale }) => sale.price,
format: (value) => toCurrency(value),
sortable: true,
},
{
name: 'discount',
label: t('Discount'),
field: ({ sale }) => sale.discount,
format: (value) => toPercentage(value / 100),
sortable: true,
},
{
name: 'total',
label: t('Total'),
field: ({ sale }) => totalRow(sale),
format: (value) => toCurrency(value),
sortable: true,
},
]);
const selected = ref([]);
const mana = ref(0);
async function fetchMana() {
const ticketId = account.value.ticketFk;
const response = await axios.get(`Tickets/${ticketId}/getSalesPersonMana`);
mana.value = response.data;
}
async function updateDiscount({ saleFk, discount, canceller }) {
const body = { salesIds: [saleFk], newDiscount: discount };
const accountId = account.value.ticketFk;
const query = `Tickets/${accountId}/updateDiscount`;
await axios.post(query, body, {
signal: canceller.signal,
});
await accountLinesForm.value.reload();
}
function onUpdateDiscount(response) {
const row = store.data[response.rowIndex];
row.sale.discount = response.discount;
quasar.notify({
message: t('Discount updated'),
type: 'positive',
});
}
async function saveWhenHasChanges() {
if (accountLinesForm.value.getChanges().updates) {
await accountLinesForm.value.onSubmit();
await accountLinesForm.value.reload();
}
}
</script>
<template>
<Teleport to="#st-data" v-if="stateStore.isSubToolbarShown()">
<div class="row q-gutter-md">
<div>
{{ t('Amount') }}
<QChip :dense="$q.screen.lt.sm" text-color="white">
{{ toCurrency(amount) }}
</QChip>
</div>
<QSeparator dark vertical />
<div>
{{ t('Amount Accounted') }}
<QChip color="positive" :dense="$q.screen.lt.sm">
{{ toCurrency(amountAccounted) }}
</QChip>
</div>
</div>
</Teleport>
<FetchData
:url="`Accounts/${route.params.id}`"
:filter="accountFilter"
@on-fetch="onFetchAccount"
auto-load
/>
<div class="q-pa-md">
<CrudModel
data-key="AccountLines"
ref="accountLinesForm"
:url="`Accounts/${route.params.id}/lines`"
save-url="AccountBeginnings/crud"
:filter="linesFilter"
@on-fetch="onFetch"
v-model:selected="selected"
:default-save="false"
:default-reset="false"
auto-load
:limit="0"
>
<template #body="{ rows }">
<QTable
:columns="columns"
:rows="rows"
:dense="$q.screen.lt.md"
row-key="id"
selection="multiple"
v-model:selected="selected"
:grid="$q.screen.lt.md"
>
<template #body-cell-accounted="{ row }">
<QTd auto-width align="right" class="text-primary">
<QInput
v-model="row.quantity"
type="number"
dense
@keyup.enter="saveWhenHasChanges()"
@blur="saveWhenHasChanges()"
/>
</QTd>
</template>
<template #body-cell-description="{ row, value }">
<QTd auto-width align="right" class="text-primary">
{{ value }} {{ row }}
</QTd>
</template>
<template #body-cell-discount="{ row, value, rowIndex }">
<QTd auto-width align="right" class="text-primary">
{{ value }}
<VnDiscount
:quantity="row.quantity"
:price="row.sale.price"
:discount="row.sale.discount"
:mana="mana"
:promise="updateDiscount"
:data="{ saleFk: row.sale.id, rowIndex: rowIndex }"
@on-update="onUpdateDiscount"
/>
</QTd>
</template>
<!-- View for grid mode -->
<template #item="props">
<div
class="q-mb-md col-12 grid-style-transition"
:style="props.selected ? 'transform: scale(0.95);' : ''"
>
<QCard>
<QCardSection>
<QCheckbox v-model="props.selected" />
</QCardSection>
<QSeparator inset />
<QList dense>
<QItem
v-for="column of props.cols"
:key="column.name"
>
<QItemSection>
<QItemLabel caption>
{{ column.label }}
</QItemLabel>
</QItemSection>
<QItemSection side>
<template v-if="column.name === 'accounted'">
<QItemLabel class="text-primary">
<QInput
v-model="props.row.quantity"
type="number"
dense
autofocus
@keyup.enter="
saveWhenHasChanges()
"
@blur="saveWhenHasChanges()"
/>
</QItemLabel>
</template>
<template
v-else-if="column.name === 'discount'"
>
<QItemLabel class="text-primary">
{{ column.value }}
<VnDiscount
:quantity="props.row.quantity"
:price="props.row.sale.price"
:discount="
props.row.sale.discount
"
:mana="mana"
:promise="updateDiscount"
:data="{
saleFk: props.row.sale.id,
rowIndex: props.rowIndex,
}"
@on-update="onUpdateDiscount"
/>
</QItemLabel>
</template>
<template v-else>
<QItemLabel>
{{ column.value }}
</QItemLabel>
</template>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</CrudModel>
</div>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn fab color="primary" icon="add" @click="showImportDialog()" />
</QPageSticky>
</template>
<style lang="scss" scoped>
.list {
padding-top: 50px;
max-width: 900px;
width: 100%;
}
.grid-style-transition {
transition: transform 0.28s, background-color 0.28s;
}
</style>
<i18n>
en:
You are about to remove {count} rows: '
You are about to remove <strong>{count}</strong> row |
You are about to remove <strong>{count}</strong> rows'
es:
Delivered: Entregado
Quantity: Cantidad
Accounted: Reclamada
Description: Descripción
Price: Precio
Discount: Descuento
Actions: Acciones
Amount: Total
Amount Accounted: Cantidad reclamada
Delete accounted sales: Eliminar ventas reclamadas
Discount updated: Descuento actualizado
Accounted quantity: Cantidad reclamada
You are about to remove {count} rows: '
Vas a eliminar <strong>{count}</strong> línea |
Vas a eliminar <strong>{count}</strong> líneas'
</i18n>

View File

@ -0,0 +1,108 @@
<script setup>
import { onMounted, ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { toDate, toCurrency } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue';
import FetchData from 'components/FetchData.vue';
import { getUrl } from 'src/composables/getUrl';
import { useSession } from 'src/composables/useSession';
import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import VnTitle from 'src/components/common/VnTitle.vue';
import axios from 'axios';
import dashIfEmpty from 'src/filters/dashIfEmpty';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
const { getTokenMultimedia } = useSession();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const { store } = useArrayData('Account');
const account = ref(store.data);
const entityId = computed(() => $props.id || route.params.id);
const filter = {
where: { id: entityId },
fields: ['id', 'nickname', 'name', 'role'],
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
};
</script>
<template>
<CardSummary
ref="AccountSummary"
:url="`VnUsers/preview`"
:filter="filter"
@on-fetch="(data) => (account = data)"
>
<template #header>{{ account.id }} - {{ account.nickname }}</template>
<template #body>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
<a
class="header header-link"
:href="`#/VnUser/${entityId}/basic-data`"
>
{{ t('globals.pageTitles.basicData') }}
<QIcon name="open_in_new" />
</a>
</QCardSection>
<VnLv :label="t('account.card.nickname')" :value="account.nickname" />
<VnLv :label="t('account.card.role')" :value="account.role.name" />
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped>
.q-dialog__inner--minimized > div {
max-width: 80%;
}
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 15px;
}
.multimedia-container {
flex: 1 0 21%;
}
.multimedia {
transition: all 0.5s;
opacity: 1;
height: 250px;
.q-img {
object-fit: cover;
background-color: black;
}
video {
object-fit: cover;
background-color: black;
}
}
.multimedia:hover {
opacity: 0.5;
}
.close-button {
top: 1%;
right: 10%;
}
.zindex {
z-index: 1;
}
.change-state {
width: 10%;
}
</style>

View File

@ -0,0 +1,95 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'filters/index';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import CardList from 'src/components/ui/CardList.vue';
import AccountSummary from '../Card/AccountSummary.vue';
import { ref } from 'vue';
import RoleForm from './Card/RoleForm.vue';
const stateStore = useStateStore();
const router = useRouter();
const { t } = useI18n();
const roleCreateDialogRef = ref(null);
const openCreateModal = () => {
roleCreateDialogRef.value.show();
};
function getApiUrl() {
return new URL(window.location).origin;
}
function navigate(event, id) {
if (event.ctrlKey || event.metaKey)
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
router.push({ path: `/role/${id}/summary` });
}
</script>
<template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<!-- <AccountFilter data-key="RolesList" /> -->
</QScrollArea>
</QDrawer>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate data-key="RolesList" url="VnRoles" auto-load>
<template #body="{ rows }">
<CardList
:id="row.id"
:key="row.id"
:title="row.clientName"
@click="navigate($event, row.id)"
v-for="row of rows"
>
<template #list-items>
<div style="flex-direction: column; width: 100%">
<VnLv :label="t('role.card.name')" :value="row.name">
</VnLv>
<VnLv
:label="t('role.card.description')"
:value="row.description"
>
</VnLv>
</div>
</template>
<template #actions>
<QBtn
:label="t('globals.description')"
@click.stop
outline
style="margin-top: 15px"
>
</QBtn>
<QBtn
:label="t('components.smartCard.openSummary')"
@click.stop="viewSummary(row.id, AccountSummary)"
color="primary"
style="margin-top: 15px"
/>
</template>
</CardList>
</template>
</VnPaginate>
</div>
<QDialog
ref="roleCreateDialogRef"
transition-show="scale"
transition-hide="scale"
>
<RoleForm />
</QDialog>
<QPageSticky :offset="[20, 20]">
<QBtn fab icon="add" color="primary" @click="openCreateModal()" />
<QTooltip>
{{ t('account.pageTitles.newRole') }}
</QTooltip>
</QPageSticky>
</QPage>
</template>
<i18n>
New rol: Nuevo role
</i18n>

View File

@ -0,0 +1,225 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const workers = ref();
const states = ref();
</script>
<template>
<FetchData url="AccountStates" @on-fetch="(data) => (states = data)" auto-load />
<FetchData
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (workers = data)"
auto-load
/>
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<QItem class="q-my-sm">
<QItemSection>
<VnInput
:label="t('Customer ID')"
v-model="params.clientFk"
lazy-rules
is-outlined
>
<template #prepend>
<QIcon name="badge" size="xs"></QIcon> </template
></VnInput>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection>
<VnInput
:label="t('Client Name')"
v-model="params.clientName"
lazy-rules
is-outlined
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Salesperson')"
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Attender')"
v-model="params.attenderFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!workers">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="workers">
<VnSelect
:label="t('Responsible')"
v-model="params.accountResponsibleFk"
@update:model-value="searchFn()"
:options="workers"
option-value="id"
option-label="name"
emit-value
map-options
use-input
hide-selected
dense
outlined
rounded
:input-debounce="0"
/>
</QItemSection>
</QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="!states">
<QSkeleton type="QInput" class="full-width" />
</QItemSection>
<QItemSection v-if="states">
<VnSelect
:label="t('State')"
v-model="params.accountStateFk"
@update:model-value="searchFn()"
:options="states"
option-value="id"
option-label="description"
emit-value
map-options
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
v-model="params.myTeam"
:label="t('myTeam')"
toggle-indeterminate
/>
</QItemSection>
</QItem>
<QSeparator />
<QExpansionItem :label="t('More options')" expand-separator>
<!-- <QItem>
<QItemSection>
<qSelect
:label="t('Item')"
v-model="params.itemFk"
:options="items"
:loading="loading"
@filter="filterFn"
@virtual-scroll="onScroll"
option-value="id"
option-label="name"
emit-value
map-options
/>
</QItemSection>
</QItem> -->
<QItem>
<QItemSection>
<VnInputDate
v-model="params.created"
:label="t('Created')"
is-outlined
/>
</QItemSection>
</QItem>
</QExpansionItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
search: Contains
clientFk: Customer
clientName: Customer
salesPersonFk: Salesperson
attenderFk: Attender
accountResponsibleFk: Responsible
accountStateFk: State
created: Created
myTeam: My team
es:
params:
search: Contiene
clientFk: Cliente
clientName: Cliente
salesPersonFk: Comercial
attenderFk: Asistente
accountResponsibleFk: Responsable
accountStateFk: Estado
created: Creada
Customer ID: ID cliente
Client Name: Nombre del cliente
Salesperson: Comercial
Attender: Asistente
Responsible: Responsable
State: Estado
Item: Artículo
Created: Creada
More options: Más opciones
myTeam: Mi equipo
</i18n>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="Account"></VnLog>
</template>

View File

@ -0,0 +1,29 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
const route = useRoute();
const { t } = useI18n();
</script>
<template>
<FormModel :url="`VnRoles/${route.params.id}`" model="VnRole" auto-load>
<template #form="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
</div>
</VnRow>
</template>
</FormModel>
</template>

View File

@ -0,0 +1,15 @@
<script setup>
import VnCard from 'components/common/VnCard.vue';
import RoleDescriptor from './RoleDescriptor.vue';
</script>
<template>
<VnCard
data-key="Role"
base-url="VnRoles"
:descriptor="RoleDescriptor"
searchbar-data-key="RoleList"
searchbar-url="Roles/filter"
searchbar-label="Search Role"
searchbar-info="You can search by Role id or customer name"
/>
</template>

View File

@ -0,0 +1,93 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState';
import CardDescriptor from 'components/ui/CardDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import useCardDescription from 'src/composables/useCardDescription';
import { useQuasar } from 'quasar';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const route = useRoute();
const quasar = useQuasar();
const router = useRouter();
const { notify } = useNotify();
const { t } = useI18n();
const entityId = computed(() => {
return $props.id || route.params.id;
});
const data = ref(useCardDescription());
const setData = (entity) => (data.value = useCardDescription(entity.name, entity.id));
const filter = {
where: { id: entityId },
};
const removeRole = () => {
quasar
.dialog({
title: 'Are you sure you want to delete it?',
message: 'Delete department',
ok: {
push: true,
color: 'primary',
},
cancel: true,
})
.onOk(async () => {
try {
await axios.post(
`/Departments/${entityId.value}/removeChild`,
entityId.value
);
router.push({ name: 'WorkerDepartment' });
notify('department.departmentRemoved', 'positive');
} catch (err) {
console.error('Error removing department');
}
});
};
</script>
<template>
<CardDescriptor
ref="descriptor"
:url="`VnRoles`"
:filter="filter"
module="Account"
@on-fetch="setData"
data-key="accountData"
:title="data.title"
:subtitle="data.subtitle"
>
<template #menu>
<QItem v-ripple clickable @click="removeRole()">
<QItemSection>{{ t('Delete') }}</QItemSection>
</QItem>
</template>
<template #body="{ entity }">
<VnLv :label="t('role.card.description')" :value="entity.description" />
</template>
</CardDescriptor>
</template>
<style scoped>
.q-item__label {
margin-top: 0;
}
</style>
<i18n>
en:
accountRate: Claming rate
es:
accountRate: Ratio de reclamación
</i18n>

View File

@ -0,0 +1,47 @@
<script setup>
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import FormModelPopup from 'components/FormModelPopup.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInput from 'src/components/common/VnInput.vue';
const route = useRoute();
const { t } = useI18n();
const shelvingId = route.params?.id || null;
const isNew = Boolean(!shelvingId);
const defaultInitialData = {
name: null,
description: null,
};
</script>
<template>
<FormModelPopup
:title="t('Create role')"
ref="formModelPopupRef"
url-create="VnRoles"
model="VnRole"
:form-initial-data="defaultInitialData"
@on-data-saved="onSave"
>
<template #form-inputs="{ data }">
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput v-model="data.name" :label="t('role.card.name')" />
</div>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<div class="col">
<VnInput
v-model="data.description"
:label="t('role.card.description')"
/>
</div>
</VnRow>
</template>
</FormModelPopup>
</template>
<i18n>
es:
Create role: Crear role
</i18n>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="VnRoles"></VnLog>
</template>

View File

@ -0,0 +1,98 @@
<script setup>
import { ref, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardSummary from 'components/ui/CardSummary.vue';
import VnLv from 'src/components/ui/VnLv.vue';
import { useArrayData } from 'src/composables/useArrayData';
const route = useRoute();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
default: 0,
},
});
const { store } = useArrayData('Role');
const role = ref(store.data);
const entityId = computed(() => $props.id || route.params.id);
const filter = {
where: { id: entityId },
};
</script>
<template>
<CardSummary
ref="summary"
:url="`VnRoles`"
:filter="filter"
@on-fetch="(data) => (role = data)"
>
<template #header> {{ role.id }} - {{ role.name }} </template>
<template #body>
<QCard class="vn-one">
<QCardSection class="q-pa-none">
<a
class="header header-link"
:href="`#/VnUser/${entityId}/basic-data`"
>
{{ t('globals.pageTitles.basicData') }}
<QIcon name="open_in_new" />
</a>
</QCardSection>
<VnLv :label="t('role.card.id')" :value="role.id" />
<VnLv :label="t('role.card.name')" :value="role.name" />
<VnLv :label="t('role.card.description')" :value="role.description" />
</QCard>
</template>
</CardSummary>
</template>
<style lang="scss" scoped>
.q-dialog__inner--minimized > div {
max-width: 80%;
}
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 15px;
}
.multimedia-container {
flex: 1 0 21%;
}
.multimedia {
transition: all 0.5s;
opacity: 1;
height: 250px;
.q-img {
object-fit: cover;
background-color: black;
}
video {
object-fit: cover;
background-color: black;
}
}
.multimedia:hover {
opacity: 0.5;
}
.close-button {
top: 1%;
right: 10%;
}
.zindex {
z-index: 1;
}
.change-state {
width: 10%;
}
</style>

View File

@ -0,0 +1,6 @@
<script setup>
import VnLog from 'src/components/common/VnLog.vue';
</script>
<template>
<VnLog model="VnRoles"></VnLog>
</template>

View File

@ -0,0 +1,64 @@
account:
pageTitles:
users: Users
list: Users
roles: Roles
alias: Mail aliasses
accounts: Accounts
ldap: LDAP
samba: Samba
acls: ACLs
connections: Connections
inheritedRoles: Inherited Roles
subRoles: Sub Roles
newRole: New role
privileges: Privileges
mailAlias: Mail Alias
mailForwarding: Mail Forwarding
card:
name: Name
nickname: User
role: Rol
email: Email
alias: Alias
lang: Language
actions:
setPassword: Set password
disableAccount:
name: Disable account
title: La cuenta será deshabilitada
subtitle: ¿Seguro que quieres continuar?
disableUser: Disable user
sync: Sync
delete: Delete
search: Search user
role:
pageTitles:
inheritedRoles: Inherited Roles
subRoles: Sub Roles
card:
description: Description
id: Id
name: Name
ldap:
enableSync: Enable synchronization
server: Server
rdn: RDN
userDN: User DN
filter: Filter
groupDN: Group DN
testConnection: Test connection
success: LDAP connection established!
samba:
enableSync: Enable synchronization
domainController: Domain controller
domainAD: AD domain
userAD: AD user
groupDN: Group DN
passwordAD: AD password
domainPart: User DN (without domain part)
verifyCertificate: Verify certificate
testConnection: Test connection
success: Samba connection established!

View File

@ -0,0 +1,74 @@
account:
pageTitles:
users: Usuarios
list: Usuarios
roles: Roles
alias: Alias de correo
accounts: Cuentas
ldap: LDAP
samba: Samba
acls: ACLs
connections: Conexiones
inheritedRoles: Roles heredados
newRole: Nuevo rol
subRoles: Subroles
privileges: Privilegios
mailAlias: Alias de correo
mailForwarding: Reenvío de correo
card:
nickname: Usuario
name: Nombre
role: Rol
email: Mail
alias: Alias
lang: dioma
actions:
setPassword: Establecer contraseña
disableAccount:
name: Deshabilitar cuenta
title: La cuenta será deshabilitada
subtitle: ¿Seguro que quieres continuar?
disableUser:
name: Desactivar usuario
title: El usuario será deshabilitado
subtitle: ¿Seguro que quieres continuar?
sync:
name: Sincronizar
title: El usuario será sincronizado
subtitle: ¿Seguro que quieres continuar?
delete:
name: Eliminar
title: El usuario será eliminado
subtitle: ¿Seguro que quieres continuar?
search: Buscar usuario
role:
pageTitles:
inheritedRoles: Roles heredados
subRoles: Subroles
newRole: Nuevo rol
card:
description: Descripción
id: Idd
name: Nombre
ldap:
enableSync: Habilitar sincronización
server: Servidor
rdn: RDN
userDN: DN usuarios
filter: Filtro
groupDN: DN grupos
testConnection: Probar conexión
success: ¡Conexión con LDAP establecida!
samba:
enableSync: Habilitar sincronización
domainController: Controlador de dominio
domainAD: Dominio AD
groupDN: DN grupos
userAD: Usuario AD
passwordAD: Contraseña AD
domainPart: DN usuarios (sin la parte del dominio)
Verify certificate: Verificar certificado
testConnection: Probar conexión
success: ¡Conexión con Samba establecida!

View File

@ -0,0 +1,200 @@
import { RouterView } from 'vue-router';
export default {
path: '/account',
name: 'Account',
meta: {
title: 'users',
icon: 'face',
moduleName: 'Account',
},
component: RouterView,
redirect: { name: 'AccountMain' },
menus: {
main: [
'AccountList',
'AccountRoles',
'AccountAlias',
'AccountAccounts',
'AccountLdap',
'AccountSamba',
'AccountConnections',
],
card: [
'AccountBasicData',
'AccountInheritedRoles',
'AccountMailForwarding',
'AccountMailAlias',
'AccountPrivileges',
'AccountLog',
],
},
children: [
{
path: '',
name: 'AccountMain',
component: () => import('src/pages/Account/AccountMain.vue'),
redirect: { name: 'AccountList' },
children: [
{
path: 'list',
name: 'AccountList',
meta: {
title: 'list',
icon: 'view_list',
},
component: () => import('src/pages/Account/AccountList.vue'),
},
{
path: 'role',
name: 'AccountRoles',
meta: {
title: 'roles',
icon: 'group',
},
component: () => import('src/pages/Account/Role/AccountRoles.vue'),
},
{
path: 'alias',
name: 'AccountAlias',
meta: {
title: 'alias',
icon: 'email',
},
component: () => import('src/pages/Account/AccountAlias.vue'),
},
{
path: 'accounts',
name: 'AccountAccounts',
meta: {
title: 'accounts',
icon: 'accessibility',
},
component: () => import('src/pages/Account/AccountAccounts.vue'),
},
{
path: 'ldap',
name: 'AccountLdap',
meta: {
title: 'ldap',
icon: 'account_tree',
},
component: () => import('src/pages/Account/AccountLdap.vue'),
},
{
path: 'samba',
name: 'AccountSamba',
meta: {
title: 'samba',
icon: 'preview',
},
component: () => import('src/pages/Account/AccountSamba.vue'),
},
{
path: 'acls',
name: 'AccountAcls',
meta: {
title: 'acls',
icon: 'check',
},
component: () => import('src/pages/Account/AccountAcls.vue'),
},
{
path: 'connections',
name: 'AccountConnections',
meta: {
title: 'connections',
icon: 'check',
},
component: () => import('src/pages/Account/AccountConnections.vue'),
},
{
path: 'create',
name: 'AccountCreate',
meta: {
title: 'accountCreate',
icon: 'add',
},
component: () => import('src/pages/Account/AccountCreate.vue'),
},
],
},
{
name: 'AccountCard',
path: ':id',
component: () => import('src/pages/Account/Card/AccountCard.vue'),
redirect: { name: 'AccountSummary' },
children: [
{
name: 'AccountSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () => import('src/pages/Account/Card/AccountSummary.vue'),
},
{
name: 'AccountBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Account/Card/AccountBasicData.vue'),
},
{
name: 'AccountInheritedRoles',
path: 'inherited-oles',
meta: {
title: 'inheritedRoles',
icon: 'group',
},
component: () =>
import('src/pages/Account/Card/AccountInheritedRoles.vue'),
},
{
name: 'AccountMailForwarding',
path: 'mail-forwarding',
meta: {
title: 'mailForwarding',
icon: 'forward',
},
component: () =>
import('src/pages/Account/Card/AccountMailForwarding.vue'),
},
{
name: 'AccountMailAlias',
path: 'mail-alias',
meta: {
title: 'mailAlias',
icon: 'email',
},
component: () =>
import('src/pages/Account/Card/AccountMailAlias.vue'),
},
{
name: 'AccountPrivileges',
path: 'privileges',
meta: {
title: 'privileges',
icon: 'badge',
},
component: () =>
import('src/pages/Account/Card/AccountPrivileges.vue'),
},
{
name: 'AccountLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Account/Card/AccountLog.vue'),
},
],
},
],
};

View File

@ -12,12 +12,14 @@ import Supplier from './Supplier';
import Travel from './travel';
import Order from './order';
import Department from './department';
import Role from './role';
import Entry from './entry';
import roadmap from './roadmap';
import Parking from './parking';
import Agency from './agency';
import ItemType from './itemType';
import Zone from './zone';
import Account from './account';
import Monitor from './monitor';
export default [
@ -35,11 +37,13 @@ export default [
Order,
invoiceIn,
Department,
Role,
Entry,
roadmap,
Parking,
Agency,
ItemType,
Zone,
Account,
Monitor,
];

View File

@ -0,0 +1,76 @@
import { RouterView } from 'vue-router';
export default {
path: '/role',
name: 'Role',
meta: {
title: 'role',
icon: 'vn:greuge',
moduleName: 'Role',
},
component: RouterView,
redirect: { name: 'AccountRoles' },
menus: {
main: [],
card: ['RoleBasicData', 'SubRoles', 'InheritedRoles', 'RoleLog'],
},
children: [
{
name: 'RoleCard',
path: '/role/:id',
component: () => import('src/pages/Account/Role/Card/RoleCard.vue'),
redirect: { name: 'RoleSummary' },
children: [
{
name: 'RoleSummary',
path: 'summary',
meta: {
title: 'summary',
icon: 'launch',
},
component: () =>
import('src/pages/Account/Role/Card/RoleSummary.vue'),
},
{
name: 'RoleBasicData',
path: 'basic-data',
meta: {
title: 'basicData',
icon: 'vn:settings',
},
component: () =>
import('src/pages/Account/Role/Card/RoleBasicData.vue'),
},
{
name: 'SubRoles',
path: 'sub-roles',
meta: {
title: 'subRoles',
icon: 'group',
},
component: () => import('src/pages/Account/Role/Card/SubRoles.vue'),
},
{
name: 'InheritedRoles',
path: 'inherited-roles',
meta: {
title: 'inheritedRoles',
icon: 'account_tree',
},
component: () =>
import('src/pages/Account/Role/Card/InheritedRoles.vue'),
},
{
name: 'RoleLog',
path: 'log',
meta: {
title: 'log',
icon: 'history',
},
component: () => import('src/pages/Account/Role/Card/RoleLog.vue'),
},
],
},
],
};

View File

@ -24,7 +24,6 @@ export default {
'WorkerTimeControl',
'WorkerLocker',
],
departmentCard: ['BasicData'],
},
children: [
{

View File

@ -10,6 +10,7 @@ import supplier from './modules/Supplier';
import route from './modules/route';
import travel from './modules/travel';
import department from './modules/department';
import role from './modules/role';
import ItemType from './modules/itemType';
import shelving from 'src/router/modules/shelving';
import order from 'src/router/modules/order';
@ -18,6 +19,7 @@ import roadmap from 'src/router/modules/roadmap';
import parking from 'src/router/modules/parking';
import agency from 'src/router/modules/agency';
import zone from 'src/router/modules/zone';
import account from './modules/account';
import monitor from 'src/router/modules/monitor';
const routes = [
@ -73,12 +75,14 @@ const routes = [
supplier,
travel,
department,
role,
roadmap,
entry,
parking,
agency,
ItemType,
zone,
account,
{
path: '/:catchAll(.*)*',
name: 'NotFound',

View File

@ -23,6 +23,7 @@ export const useNavigationStore = defineStore('navigationStore', () => {
'worker',
'wagon',
'zone',
'account',
];
const pinnedModules = ref([]);
const role = useRole();