Reviewed-on: #629 Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
commit
5206f56cec
|
@ -0,0 +1,33 @@
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function getCurrentBranchName(p = process.cwd()) {
|
||||
if (!fs.existsSync(p)) return false;
|
||||
|
||||
const gitHeadPath = path.join(p, '.git', 'HEAD');
|
||||
|
||||
if (!fs.existsSync(gitHeadPath))
|
||||
return getCurrentBranchName(path.resolve(p, '..'));
|
||||
|
||||
const headContent = fs.readFileSync(gitHeadPath, 'utf-8');
|
||||
return headContent.trim().split('/')[2];
|
||||
}
|
||||
|
||||
const branchName = getCurrentBranchName();
|
||||
|
||||
if (branchName) {
|
||||
const msgPath = `.git/COMMIT_EDITMSG`;
|
||||
const msg = fs.readFileSync(msgPath, 'utf-8');
|
||||
const reference = branchName.match(/^\d+/);
|
||||
|
||||
const referenceTag = `refs #${reference}`;
|
||||
if (!msg.includes(referenceTag) && reference) {
|
||||
const splitedMsg = msg.split(':');
|
||||
|
||||
if (splitedMsg.length > 1) {
|
||||
const finalMsg = splitedMsg[0] + ': ' + referenceTag + splitedMsg.slice(1).join(':');
|
||||
fs.writeFileSync(msgPath, finalMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
echo "Running husky commit-msg hook"
|
||||
npx --no-install commitlint --edit
|
||||
echo "Adding reference tag to commit message"
|
||||
node .husky/addReferenceTag.js
|
||||
|
|
@ -0,0 +1 @@
|
|||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "24.32.0",
|
||||
"version": "24.34.0",
|
||||
"description": "Salix frontend",
|
||||
"productName": "Salix",
|
||||
"author": "Verdnatura",
|
||||
|
@ -13,7 +13,10 @@
|
|||
"test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run",
|
||||
"test": "echo \"See package.json => scripts for available tests.\" && exit 0",
|
||||
"test:unit": "vitest",
|
||||
"test:unit:ci": "vitest run"
|
||||
"test:unit:ci": "vitest run",
|
||||
"commitlint": "commitlint --edit",
|
||||
"prepare": "npx husky install",
|
||||
"addReferenceTag": "node .husky/addReferenceTag.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@quasar/cli": "^2.3.0",
|
||||
|
@ -29,6 +32,8 @@
|
|||
"vue-router": "^4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.2.1",
|
||||
"@commitlint/config-conventional": "^19.1.0",
|
||||
"@intlify/unplugin-vue-i18n": "^0.8.1",
|
||||
"@pinia/testing": "^0.1.2",
|
||||
"@quasar/app-vite": "^1.7.3",
|
||||
|
@ -41,6 +46,7 @@
|
|||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.3",
|
||||
"eslint-plugin-vue": "^9.14.1",
|
||||
"husky": "^8.0.0",
|
||||
"postcss": "^8.4.23",
|
||||
"prettier": "^2.8.8",
|
||||
"vitest": "^0.31.1"
|
||||
|
|
5806
pnpm-lock.yaml
5806
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -100,7 +100,6 @@ const isResetting = ref(false);
|
|||
const hasChanges = ref(!$props.observeFormChanges);
|
||||
const originalData = ref({});
|
||||
const formData = computed(() => state.get(modelValue));
|
||||
const formUrl = computed(() => $props.url);
|
||||
const defaultButtons = computed(() => ({
|
||||
save: {
|
||||
color: 'primary',
|
||||
|
@ -148,11 +147,14 @@ if (!$props.url)
|
|||
(val) => updateAndEmit('onFetch', val)
|
||||
);
|
||||
|
||||
watch(formUrl, async () => {
|
||||
watch(
|
||||
() => [$props.url, $props.filter],
|
||||
async () => {
|
||||
originalData.value = null;
|
||||
reset();
|
||||
await fetch();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeRouteLeave((to, from, next) => {
|
||||
if (hasChanges.value && $props.observeFormChanges)
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useQuasar } from 'quasar';
|
|||
import PinnedModules from './PinnedModules.vue';
|
||||
import UserPanel from 'components/UserPanel.vue';
|
||||
import VnBreadcrumbs from './common/VnBreadcrumbs.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import VnAvatar from './ui/VnAvatar.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
|
@ -72,22 +72,13 @@ const pinnedModulesRef = ref();
|
|||
</QTooltip>
|
||||
<PinnedModules ref="pinnedModulesRef" />
|
||||
</QBtn>
|
||||
<QBtn
|
||||
:class="{ 'q-pa-none': quasar.platform.is.mobile }"
|
||||
rounded
|
||||
dense
|
||||
flat
|
||||
no-wrap
|
||||
id="user"
|
||||
>
|
||||
<QAvatar size="lg">
|
||||
<VnImg
|
||||
:id="user.id"
|
||||
collection="user"
|
||||
size="160x160"
|
||||
:zoom-size="null"
|
||||
<QBtn class="q-pa-none" rounded dense flat no-wrap id="user">
|
||||
<VnAvatar
|
||||
:worker-id="user.id"
|
||||
:title="user.name"
|
||||
size="lg"
|
||||
color="transparent"
|
||||
/>
|
||||
</QAvatar>
|
||||
<QTooltip bottom>
|
||||
{{ t('globals.userPanel') }}
|
||||
</QTooltip>
|
||||
|
|
|
@ -11,8 +11,8 @@ import VnSelect from 'src/components/common/VnSelect.vue';
|
|||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import { useClipboard } from 'src/composables/useClipboard';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import VnAvatar from './ui/VnAvatar.vue';
|
||||
|
||||
const state = useState();
|
||||
const session = useSession();
|
||||
|
@ -136,7 +136,7 @@ const isEmployee = computed(() => useRole().isEmployee());
|
|||
@update:model-value="saveLanguage"
|
||||
:label="t(`globals.lang['${userLocale}']`)"
|
||||
icon="public"
|
||||
color="orange"
|
||||
color="primary"
|
||||
false-value="es"
|
||||
true-value="en"
|
||||
/>
|
||||
|
@ -145,7 +145,7 @@ const isEmployee = computed(() => useRole().isEmployee());
|
|||
@update:model-value="saveDarkMode"
|
||||
:label="t(`globals.darkMode`)"
|
||||
checked-icon="dark_mode"
|
||||
color="orange"
|
||||
color="primary"
|
||||
unchecked-icon="light_mode"
|
||||
/>
|
||||
</div>
|
||||
|
@ -153,10 +153,20 @@ const isEmployee = computed(() => useRole().isEmployee());
|
|||
<QSeparator vertical inset class="q-mx-lg" />
|
||||
|
||||
<div class="col column items-center q-mb-sm">
|
||||
<QAvatar size="80px">
|
||||
<VnImg :id="user.id" collection="user" size="160x160" />
|
||||
</QAvatar>
|
||||
|
||||
<VnAvatar
|
||||
:worker-id="user.id"
|
||||
:title="user.name"
|
||||
size="xxl"
|
||||
color="transparent"
|
||||
/>
|
||||
<QBtn
|
||||
v-if="isEmployee"
|
||||
class="q-mt-sm q-px-md"
|
||||
:to="`/worker/${user.id}`"
|
||||
color="primary"
|
||||
:label="t('globals.myAccount')"
|
||||
dense
|
||||
/>
|
||||
<div class="text-subtitle1 q-mt-md">
|
||||
<strong>{{ user.nickname }}</strong>
|
||||
</div>
|
||||
|
@ -168,7 +178,7 @@ const isEmployee = computed(() => useRole().isEmployee());
|
|||
</div>
|
||||
<QBtn
|
||||
id="logout"
|
||||
color="orange"
|
||||
color="primary"
|
||||
flat
|
||||
:label="t('globals.logOut')"
|
||||
size="sm"
|
||||
|
|
|
@ -7,9 +7,11 @@ import { dashIfEmpty } from 'src/filters';
|
|||
import VnSelect from 'components/common/VnSelect.vue';
|
||||
import VnSelectCache from 'components/common/VnSelectCache.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnInputNumber from 'components/common/VnInputNumber.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnComponent from 'components/common/VnComponent.vue';
|
||||
import VnUserLink from 'components/ui/VnUserLink.vue';
|
||||
|
||||
const model = defineModel(undefined, { required: true });
|
||||
const $props = defineProps({
|
||||
|
@ -66,7 +68,7 @@ const defaultComponents = {
|
|||
},
|
||||
},
|
||||
number: {
|
||||
component: markRaw(VnInput),
|
||||
component: markRaw(VnInputNumber),
|
||||
attrs: {
|
||||
disable: !$props.isEditable,
|
||||
class: 'fit',
|
||||
|
@ -98,14 +100,14 @@ const defaultComponents = {
|
|||
},
|
||||
checkbox: {
|
||||
component: markRaw(QCheckbox),
|
||||
attrs: (prop) => {
|
||||
attrs: ({ model }) => {
|
||||
const defaultAttrs = {
|
||||
disable: !$props.isEditable,
|
||||
'model-value': Boolean(prop),
|
||||
'model-value': Boolean(model),
|
||||
class: 'no-padding fit',
|
||||
};
|
||||
|
||||
if (typeof prop == 'number') {
|
||||
if (typeof model == 'number') {
|
||||
defaultAttrs['true-value'] = 1;
|
||||
defaultAttrs['false-value'] = 0;
|
||||
}
|
||||
|
@ -126,6 +128,9 @@ const defaultComponents = {
|
|||
icon: {
|
||||
component: markRaw(QIcon),
|
||||
},
|
||||
userLink: {
|
||||
component: markRaw(VnUserLink),
|
||||
},
|
||||
};
|
||||
|
||||
const value = computed(() => {
|
||||
|
@ -146,7 +151,7 @@ const col = computed(() => {
|
|||
};
|
||||
}
|
||||
if (
|
||||
(newColumn.name.startsWith('is') || newColumn.name.startsWith('has')) &&
|
||||
(/^is[A-Z]/.test(newColumn.name) || /^has[A-Z]/.test(newColumn.name)) &&
|
||||
newColumn.component == null
|
||||
)
|
||||
newColumn.component = 'checkbox';
|
||||
|
@ -163,14 +168,14 @@ const components = computed(() => $props.components ?? defaultComponents);
|
|||
v-if="col.before"
|
||||
:prop="col.before"
|
||||
:components="components"
|
||||
:value="model"
|
||||
:value="{ row, model }"
|
||||
v-model="model"
|
||||
/>
|
||||
<VnComponent
|
||||
v-if="col.component"
|
||||
:prop="col"
|
||||
:components="components"
|
||||
:value="model"
|
||||
:value="{ row, model }"
|
||||
v-model="model"
|
||||
/>
|
||||
<span :title="value" v-else>{{ value }}</span>
|
||||
|
@ -178,7 +183,7 @@ const components = computed(() => $props.components ?? defaultComponents);
|
|||
v-if="col.after"
|
||||
:prop="col.after"
|
||||
:components="components"
|
||||
:value="model"
|
||||
:value="{ row, model }"
|
||||
v-model="model"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -10,6 +10,8 @@ import VnInputDate from 'components/common/VnInputDate.vue';
|
|||
import VnInputTime from 'components/common/VnInputTime.vue';
|
||||
import VnTableColumn from 'components/VnTable/VnColumn.vue';
|
||||
|
||||
defineExpose({ addFilter });
|
||||
|
||||
const $props = defineProps({
|
||||
column: {
|
||||
type: Object,
|
||||
|
@ -32,7 +34,7 @@ const model = defineModel(undefined, { required: true });
|
|||
const arrayData = useArrayData($props.dataKey, { searchUrl: $props.searchUrl });
|
||||
const columnFilter = computed(() => $props.column?.columnFilter);
|
||||
|
||||
const updateEvent = { 'update:modelValue': addFilter };
|
||||
const updateEvent = { 'update:modelValue': addFilter, remove: () => addFilter(null) };
|
||||
const enterEvent = {
|
||||
'keyup.enter': () => addFilter(model.value),
|
||||
remove: () => addFilter(null),
|
||||
|
|
|
@ -126,7 +126,7 @@ const tableModes = [
|
|||
];
|
||||
onBeforeMount(() => {
|
||||
setUserParams(route.query[$props.searchUrl]);
|
||||
hasParams.value = Object.keys(params.value).length !== 0;
|
||||
hasParams.value = params.value && Object.keys(params.value).length !== 0;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
|
@ -164,13 +164,16 @@ watch(
|
|||
|
||||
const isTableMode = computed(() => mode.value == TABLE_MODE);
|
||||
|
||||
function setUserParams(watchedParams) {
|
||||
function setUserParams(watchedParams, watchedOrder) {
|
||||
if (!watchedParams) return;
|
||||
|
||||
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||
const filter = JSON.parse(watchedParams?.filter ?? '{}');
|
||||
const filter =
|
||||
typeof watchedParams?.filter == 'string'
|
||||
? JSON.parse(watchedParams?.filter ?? '{}')
|
||||
: watchedParams?.filter;
|
||||
const where = filter?.where;
|
||||
const order = filter?.order;
|
||||
const order = watchedOrder ?? filter?.order;
|
||||
|
||||
watchedParams = { ...watchedParams, ...where };
|
||||
delete watchedParams.filter;
|
||||
|
@ -291,6 +294,7 @@ defineExpose({
|
|||
v-model="params"
|
||||
:search-url="searchUrl"
|
||||
:redirect="!!redirect"
|
||||
@set-user-params="setUserParams"
|
||||
>
|
||||
<template #body>
|
||||
<div
|
||||
|
@ -333,6 +337,7 @@ defineExpose({
|
|||
v-bind="$attrs"
|
||||
:limit="20"
|
||||
ref="CrudModelRef"
|
||||
@on-fetch="(...args) => emit('onFetch', ...args)"
|
||||
:search-url="searchUrl"
|
||||
:disable-infinite-scroll="isTableMode"
|
||||
@save-changes="reload"
|
||||
|
@ -370,7 +375,7 @@ defineExpose({
|
|||
<template #top-left v-if="!$props.withoutHeader">
|
||||
<slot name="top-left"></slot>
|
||||
</template>
|
||||
<template #top-right>
|
||||
<template #top-right v-if="!$props.withoutHeader">
|
||||
<VnVisibleColumn
|
||||
v-if="isTableMode"
|
||||
v-model="splittedColumns.columns"
|
||||
|
@ -397,7 +402,7 @@ defineExpose({
|
|||
<div
|
||||
class="column self-start q-ml-xs ellipsis"
|
||||
:class="`text-${col?.align ?? 'left'}`"
|
||||
style="height: 75px"
|
||||
:style="$props.columnSearch ? 'height: 75px' : ''"
|
||||
>
|
||||
<div
|
||||
class="row items-center no-wrap"
|
||||
|
@ -438,7 +443,7 @@ defineExpose({
|
|||
</VnTableChip>
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell="{ col, row }">
|
||||
<template #body-cell="{ col, row, rowIndex }">
|
||||
<!-- Columns -->
|
||||
<QTd
|
||||
auto-width
|
||||
|
@ -451,7 +456,12 @@ defineExpose({
|
|||
rowCtrlClickFunction($event, row)
|
||||
"
|
||||
>
|
||||
<slot :name="`column-${col.name}`" :col="col" :row="row">
|
||||
<slot
|
||||
:name="`column-${col.name}`"
|
||||
:col="col"
|
||||
:row="row"
|
||||
:row-index="rowIndex"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
:row="row"
|
||||
|
@ -471,7 +481,6 @@ defineExpose({
|
|||
>
|
||||
<QBtn
|
||||
v-for="(btn, index) of col.actions"
|
||||
v-show="btn.show ? btn.show(row) : true"
|
||||
:key="index"
|
||||
:title="btn.title"
|
||||
:icon="btn.icon"
|
||||
|
@ -482,6 +491,11 @@ defineExpose({
|
|||
? 'text-primary-light'
|
||||
: 'color-vn-text '
|
||||
"
|
||||
:style="`visibility: ${
|
||||
(btn.show && btn.show(row)) ?? true
|
||||
? 'visible'
|
||||
: 'hidden'
|
||||
}`"
|
||||
@click="btn.action(row)"
|
||||
/>
|
||||
</QTd>
|
||||
|
@ -539,7 +553,9 @@ defineExpose({
|
|||
:class="$props.cardClass"
|
||||
>
|
||||
<div
|
||||
v-for="col of splittedColumns.cardVisible"
|
||||
v-for="(
|
||||
col, index
|
||||
) of splittedColumns.cardVisible"
|
||||
:key="col.name"
|
||||
class="fields"
|
||||
>
|
||||
|
@ -560,6 +576,7 @@ defineExpose({
|
|||
:name="`column-${col.name}`"
|
||||
:col="col"
|
||||
:row="row"
|
||||
:row-index="index"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="col"
|
||||
|
@ -619,9 +636,15 @@ defineExpose({
|
|||
>
|
||||
<template #form-inputs="{ data }">
|
||||
<div class="grid-create">
|
||||
<VnTableColumn
|
||||
<slot
|
||||
v-for="column of splittedColumns.create"
|
||||
:key="column.name"
|
||||
:name="`column-create-${column.name}`"
|
||||
:data="data"
|
||||
:column-name="column.name"
|
||||
:label="column.label"
|
||||
>
|
||||
<VnTableColumn
|
||||
:column="column"
|
||||
:row="{}"
|
||||
default="input"
|
||||
|
@ -629,6 +652,7 @@ defineExpose({
|
|||
:show-label="true"
|
||||
component-prop="columnCreate"
|
||||
/>
|
||||
</slot>
|
||||
<slot name="more-create-dialog" :data="data" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -46,7 +46,7 @@ const stateStore = useStateStore();
|
|||
</div>
|
||||
</Teleport>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<QScrollArea class="fit">
|
||||
<div id="right-panel"></div>
|
||||
<slot v-if="!hasContent" name="right-panel" />
|
||||
</QScrollArea>
|
||||
|
|
|
@ -17,13 +17,7 @@ const props = defineProps({
|
|||
descriptor: { type: Object, required: true },
|
||||
filterPanel: { type: Object, default: undefined },
|
||||
searchDataKey: { type: String, default: undefined },
|
||||
searchUrl: { type: String, default: undefined },
|
||||
searchbarLabel: { type: String, default: '' },
|
||||
searchbarInfo: { type: String, default: '' },
|
||||
searchCustomRouteRedirect: { type: String, default: undefined },
|
||||
searchRedirect: { type: Boolean, default: true },
|
||||
searchMakeFetch: { type: Boolean, default: true },
|
||||
searchUrlQuery: { type: String, default: undefined },
|
||||
searchbarProps: { type: Object, default: undefined },
|
||||
});
|
||||
|
||||
const stateStore = useStateStore();
|
||||
|
@ -66,15 +60,7 @@ if (props.baseUrl) {
|
|||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<slot name="searchbar" v-if="props.searchDataKey">
|
||||
<VnSearchbar
|
||||
:data-key="props.searchDataKey"
|
||||
:url="props.searchUrl"
|
||||
:label="props.searchbarLabel"
|
||||
:info="props.searchbarInfo"
|
||||
:search-url="props.searchUrlQuery"
|
||||
:custom-route-redirect-name="searchCustomRouteRedirect"
|
||||
:redirect="searchRedirect"
|
||||
/>
|
||||
<VnSearchbar :data-key="props.searchDataKey" v-bind="props.searchbarProps" />
|
||||
</slot>
|
||||
<slot v-else name="searchbar" />
|
||||
<RightMenu>
|
||||
|
|
|
@ -35,7 +35,7 @@ function mix(toComponent) {
|
|||
...toComponent,
|
||||
...toValueAttrs(customComponent?.forceAttrs),
|
||||
},
|
||||
event: event ?? customComponent?.event,
|
||||
event: { ...customComponent?.event, ...event },
|
||||
};
|
||||
return mixed;
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useCapitalize } from 'src/composables/useCapitalize';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: { type: [String, Number], default: '' },
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
const emit = defineEmits(['update:modelValue']);
|
||||
|
||||
const amount = computed({
|
||||
get() {
|
||||
return props.modelValue;
|
||||
},
|
||||
set(val) {
|
||||
emit('update:modelValue', val);
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<VnInput
|
||||
v-model="amount"
|
||||
type="number"
|
||||
step="any"
|
||||
:label="useCapitalize(t('amount'))"
|
||||
/>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
amount: importe
|
||||
</i18n>
|
|
@ -0,0 +1,8 @@
|
|||
<script setup>
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
const model = defineModel({ type: [Number, String] });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VnInput v-bind="$attrs" v-model.number="model" type="number" />
|
||||
</template>
|
|
@ -407,9 +407,12 @@ watch(
|
|||
@on-fetch="
|
||||
(data) =>
|
||||
(actions = data.map((item) => {
|
||||
const changedModel = item.changedModel;
|
||||
return {
|
||||
locale: useCapitalize(validations[item.changedModel].locale.name),
|
||||
value: item.changedModel,
|
||||
locale: useCapitalize(
|
||||
validations[changedModel]?.locale?.name ?? changedModel
|
||||
),
|
||||
value: changedModel,
|
||||
};
|
||||
}))
|
||||
"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { ref, toRefs, computed, watch, onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
const emit = defineEmits(['update:modelValue', 'update:options']);
|
||||
const emit = defineEmits(['update:modelValue', 'update:options', 'remove']);
|
||||
|
||||
const $props = defineProps({
|
||||
modelValue: {
|
||||
|
@ -49,6 +49,10 @@ const $props = defineProps({
|
|||
type: Array,
|
||||
default: null,
|
||||
},
|
||||
include: {
|
||||
type: [Object, Array],
|
||||
default: null,
|
||||
},
|
||||
where: {
|
||||
type: Object,
|
||||
default: null,
|
||||
|
@ -142,7 +146,7 @@ function filter(val, options) {
|
|||
async function fetchFilter(val) {
|
||||
if (!$props.url || !dataRef.value) return;
|
||||
|
||||
const { fields, sortBy, limit } = $props;
|
||||
const { fields, include, sortBy, limit } = $props;
|
||||
const key =
|
||||
optionFilterValue.value ??
|
||||
(new RegExp(/\d/g).test(val)
|
||||
|
@ -153,7 +157,7 @@ async function fetchFilter(val) {
|
|||
? { [key]: { like: `%${val}%` } }
|
||||
: { [key]: val };
|
||||
const where = { ...(val ? defaultWhere : {}), ...$props.where };
|
||||
const fetchOptions = { where, limit };
|
||||
const fetchOptions = { where, include, limit };
|
||||
if (fields) fetchOptions.fields = fields;
|
||||
if (sortBy) fetchOptions.order = sortBy;
|
||||
return dataRef.value.fetch(fetchOptions);
|
||||
|
@ -168,7 +172,10 @@ async function filterHandler(val, update) {
|
|||
let newOptions;
|
||||
|
||||
if (!$props.defaultFilter) return update();
|
||||
if ($props.url) {
|
||||
if (
|
||||
$props.url &&
|
||||
($props.limit || (!$props.limit && Object.keys(myOptions.value).length === 0))
|
||||
) {
|
||||
newOptions = await fetchFilter(val);
|
||||
} else newOptions = filter(val, myOptionsOriginal.value);
|
||||
update(
|
||||
|
@ -221,7 +228,12 @@ function nullishToTrue(value) {
|
|||
<QIcon
|
||||
v-show="value"
|
||||
name="close"
|
||||
@click.stop="value = null"
|
||||
@click.stop="
|
||||
() => {
|
||||
value = null;
|
||||
emit('remove');
|
||||
}
|
||||
"
|
||||
class="cursor-pointer"
|
||||
size="xs"
|
||||
/>
|
||||
|
|
|
@ -31,7 +31,7 @@ const dialog = ref(null);
|
|||
<div class="container order-catalog-item overflow-hidden">
|
||||
<QCard class="card shadow-6">
|
||||
<div class="img-wrapper">
|
||||
<VnImg :id="item.id" zoom-size="lg" class="image" />
|
||||
<VnImg :id="item.id" class="image" />
|
||||
<div v-if="item.hex && isCatalog" class="item-color-container">
|
||||
<div
|
||||
class="item-color"
|
||||
|
|
|
@ -73,8 +73,7 @@ const containerClasses = computed(() => {
|
|||
.q-calendar-month__head--workweek,
|
||||
.q-calendar-month__head--weekday,
|
||||
// .q-calendar-month__workweek.q-past-day,
|
||||
.q-calendar-month__week :nth-child(6),
|
||||
:nth-child(7) {
|
||||
.q-calendar-month__week :nth-child(n+6):nth-child(-n+7) {
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,45 +1,62 @@
|
|||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { useColor } from 'src/composables/useColor';
|
||||
import { getCssVar } from 'quasar';
|
||||
|
||||
const $props = defineProps({
|
||||
workerId: { type: Number, required: true },
|
||||
description: { type: String, default: null },
|
||||
size: { type: String, default: null },
|
||||
title: { type: String, default: null },
|
||||
color: { type: String, default: null },
|
||||
});
|
||||
|
||||
const { getTokenMultimedia } = useSession();
|
||||
const token = getTokenMultimedia();
|
||||
const { t } = useI18n();
|
||||
|
||||
const title = computed(() => $props.title ?? t('globals.system'));
|
||||
const src = computed(
|
||||
() => `/api/Images/user/160x160/${$props.workerId}/download?access_token=${token}`
|
||||
);
|
||||
const title = computed(() => $props.title?.toUpperCase() || t('globals.system'));
|
||||
const showLetter = ref(false);
|
||||
const backgroundColor = computed(() => {
|
||||
const color = $props.color || useColor(title.value);
|
||||
return getCssVar(color) || color;
|
||||
});
|
||||
|
||||
watch(src, () => (showLetter.value = false));
|
||||
</script>
|
||||
<template>
|
||||
<div class="avatar-picture column items-center">
|
||||
<div class="column items-center">
|
||||
<QAvatar
|
||||
:style="{
|
||||
backgroundColor: useColor(title),
|
||||
}"
|
||||
:size="$props.size"
|
||||
:title="title"
|
||||
:style="{ backgroundColor }"
|
||||
v-bind="$attrs"
|
||||
:title="title || t('globals.system')"
|
||||
>
|
||||
<template v-if="showLetter">{{ title.charAt(0) }}</template>
|
||||
<QImg
|
||||
v-else
|
||||
:src="`/api/Images/user/160x160/${$props.workerId}/download?access_token=${token}`"
|
||||
spinner-color="white"
|
||||
@error="showLetter = true"
|
||||
/>
|
||||
<template v-if="showLetter">
|
||||
{{ title.charAt(0) }}
|
||||
</template>
|
||||
<QImg v-else :src="src" spinner-color="white" @error="showLetter = true" />
|
||||
</QAvatar>
|
||||
<div class="description">
|
||||
<slot name="description" v-if="$props.description">
|
||||
<p>
|
||||
{{ $props.description }}
|
||||
</p>
|
||||
<slot name="description" v-if="description">
|
||||
<p v-text="description" />
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
[size='xxl'] {
|
||||
.q-avatar,
|
||||
.q-img {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.q-img {
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -57,7 +57,7 @@ const $props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
defineExpose({ search });
|
||||
defineExpose({ search, sanitizer });
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'refresh',
|
||||
|
@ -65,6 +65,7 @@ const emit = defineEmits([
|
|||
'search',
|
||||
'init',
|
||||
'remove',
|
||||
'setUserParams',
|
||||
]);
|
||||
|
||||
const arrayData = useArrayData($props.dataKey, {
|
||||
|
@ -81,22 +82,26 @@ onMounted(() => {
|
|||
});
|
||||
|
||||
function setUserParams(watchedParams) {
|
||||
if (!watchedParams) return;
|
||||
if (!watchedParams || Object.keys(watchedParams).length == 0) return;
|
||||
|
||||
if (typeof watchedParams == 'string') watchedParams = JSON.parse(watchedParams);
|
||||
if (typeof watchedParams?.filter == 'string')
|
||||
watchedParams.filter = JSON.parse(watchedParams.filter);
|
||||
|
||||
watchedParams = { ...watchedParams, ...watchedParams.filter?.where };
|
||||
const order = watchedParams.filter?.order;
|
||||
|
||||
delete watchedParams.filter;
|
||||
userParams.value = { ...userParams.value, ...watchedParams };
|
||||
userParams.value = { ...userParams.value, ...sanitizer(watchedParams) };
|
||||
emit('setUserParams', userParams.value, order);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.query[$props.searchUrl],
|
||||
(val) => setUserParams(val)
|
||||
);
|
||||
|
||||
watch(
|
||||
() => arrayData.store.userParams,
|
||||
(val) => setUserParams(val)
|
||||
() => [route.query[$props.searchUrl], arrayData.store.userParams],
|
||||
([newSearchUrl, newUserParams], [oldSearchUrl, oldUserParams]) => {
|
||||
if (newSearchUrl || oldSearchUrl) setUserParams(newSearchUrl);
|
||||
if (newUserParams || oldUserParams) setUserParams(newUserParams);
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
|
@ -112,14 +117,17 @@ async function search(evt) {
|
|||
isLoading.value = true;
|
||||
const filter = { ...userParams.value, ...$props.modelValue };
|
||||
store.userParamsChanged = true;
|
||||
try {
|
||||
const { params: newParams } = await arrayData.addFilter({
|
||||
params: filter,
|
||||
});
|
||||
userParams.value = newParams;
|
||||
|
||||
if (!$props.showAll && !Object.values(filter).length) store.data = [];
|
||||
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
|
||||
emit('search');
|
||||
}
|
||||
|
||||
|
@ -190,6 +198,14 @@ function formatValue(value) {
|
|||
|
||||
return `"${value}"`;
|
||||
}
|
||||
|
||||
function sanitizer(params) {
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (typeof value == 'object')
|
||||
params[key] = Object.values(value)[0].replaceAll('%', '');
|
||||
}
|
||||
return params;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -272,7 +288,7 @@ function formatValue(value) {
|
|||
<QSeparator />
|
||||
</QList>
|
||||
<QList dense class="list q-gutter-y-sm q-mt-sm">
|
||||
<slot name="body" :params="userParams" :search-fn="search"></slot>
|
||||
<slot name="body" :params="sanitizer(userParams)" :search-fn="search"></slot>
|
||||
</QList>
|
||||
</QForm>
|
||||
<QInnerLoading
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import noImage from '/no-user.png';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
|
||||
const $props = defineProps({
|
||||
storage: {
|
||||
|
@ -11,14 +13,17 @@ const $props = defineProps({
|
|||
type: String,
|
||||
default: 'catalog',
|
||||
},
|
||||
size: {
|
||||
resolution: {
|
||||
type: String,
|
||||
default: '200x200',
|
||||
},
|
||||
zoomSize: {
|
||||
zoomResolution: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'lg',
|
||||
default: null,
|
||||
},
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
id: {
|
||||
type: Number,
|
||||
|
@ -28,14 +33,16 @@ const $props = defineProps({
|
|||
const show = ref(false);
|
||||
const token = useSession().getTokenMultimedia();
|
||||
const timeStamp = ref(`timestamp=${Date.now()}`);
|
||||
import noImage from '/no-user.png';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
const url = computed(() => {
|
||||
const isEmployee = useRole().isEmployee();
|
||||
const isEmployee = useRole().isEmployee();
|
||||
|
||||
const getUrl = (zoom = false) => {
|
||||
const curResolution = zoom
|
||||
? $props.zoomResolution || $props.resolution
|
||||
: $props.resolution;
|
||||
return isEmployee
|
||||
? `/api/${$props.storage}/${$props.collection}/${$props.size}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||
? `/api/${$props.storage}/${$props.collection}/${curResolution}/${$props.id}/download?access_token=${token}&${timeStamp.value}`
|
||||
: noImage;
|
||||
});
|
||||
};
|
||||
const reload = () => {
|
||||
timeStamp.value = `timestamp=${Date.now()}`;
|
||||
};
|
||||
|
@ -45,23 +52,21 @@ defineExpose({
|
|||
</script>
|
||||
<template>
|
||||
<QImg
|
||||
:class="{ zoomIn: $props.zoomSize }"
|
||||
:src="url"
|
||||
:class="{ zoomIn: zoom }"
|
||||
:src="getUrl()"
|
||||
v-bind="$attrs"
|
||||
@click="show = !show"
|
||||
@click.stop="show = $props.zoom ? true : false"
|
||||
spinner-color="primary"
|
||||
/>
|
||||
<QDialog v-model="show" v-if="$props.zoomSize">
|
||||
<QDialog v-if="$props.zoom" v-model="show">
|
||||
<QImg
|
||||
:src="url"
|
||||
size="full"
|
||||
class="img_zoom"
|
||||
:src="getUrl(true)"
|
||||
v-bind="$attrs"
|
||||
spinner-color="primary"
|
||||
class="img_zoom"
|
||||
/>
|
||||
</QDialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.q-img {
|
||||
&.zoomIn {
|
||||
|
|
|
@ -34,8 +34,8 @@ function handleKeyUp(event) {
|
|||
}
|
||||
async function insert() {
|
||||
const body = $props.body;
|
||||
Object.assign(body, { text: newNote.value });
|
||||
await axios.post($props.url, body);
|
||||
const newBody = { ...body, ...{ text: newNote.value } };
|
||||
await axios.post($props.url, newBody);
|
||||
await vnPaginateRef.value.fetch();
|
||||
newNote.value = '';
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ defineExpose({ fetch, addFilter, paginate });
|
|||
>
|
||||
<slot name="body" :rows="store.data"></slot>
|
||||
<div v-if="isLoading" class="info-row q-pa-md text-center">
|
||||
<QSpinner color="orange" size="md" />
|
||||
<QSpinner color="primary" size="md" />
|
||||
</div>
|
||||
</QInfiniteScroll>
|
||||
</template>
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
<script setup>
|
||||
defineProps({ wrap: { type: Boolean, default: false } });
|
||||
</script>
|
||||
<template>
|
||||
<div class="vn-row q-gutter-md q-mb-md" :class="{ wrap }">
|
||||
<slot></slot>
|
||||
<div class="vn-row q-gutter-md q-mb-md">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scopped>
|
||||
<style lang="scss" scoped>
|
||||
.vn-row {
|
||||
display: flex;
|
||||
> * {
|
||||
> :deep(*) {
|
||||
flex: 1;
|
||||
}
|
||||
&[wrap] {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 800px) {
|
||||
.vn-row {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onBeforeMount } from 'vue';
|
||||
import { watch, computed } from 'vue';
|
||||
import { date } from 'quasar';
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import VnAvatar from '../ui/VnAvatar.vue';
|
||||
|
@ -10,7 +10,8 @@ const $props = defineProps({
|
|||
where: { type: Object, default: () => {} },
|
||||
});
|
||||
|
||||
const filter = {
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
fields: ['smsFk'],
|
||||
include: {
|
||||
relation: 'sms',
|
||||
|
@ -32,9 +33,9 @@ const filter = {
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
onBeforeMount(() => (filter.where = $props.where));
|
||||
...{ where: $props.where },
|
||||
};
|
||||
});
|
||||
|
||||
function formatNumber(number) {
|
||||
if (number.length <= 10) return number;
|
||||
|
|
|
@ -43,20 +43,9 @@ onBeforeUnmount(() => stateStore.toggleSubToolbar());
|
|||
</slot>
|
||||
</QToolbar>
|
||||
</template>
|
||||
<style lang="scss">
|
||||
.q-toolbar {
|
||||
background: var(--vn-section-color);
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.sticky {
|
||||
position: sticky;
|
||||
top: 61px;
|
||||
z-index: 1;
|
||||
}
|
||||
@media (max-width: $breakpoint-sm) {
|
||||
.sticky {
|
||||
top: 90px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,4 +18,3 @@ const { t } = useI18n();
|
|||
</slot>
|
||||
<WorkerDescriptorProxy v-if="$props.workerId" :id="$props.workerId" />
|
||||
</template>
|
||||
<style scoped></style>
|
||||
|
|
|
@ -3,12 +3,14 @@ import { useRole } from './useRole';
|
|||
import { useAcl } from './useAcl';
|
||||
import { useUserConfig } from './useUserConfig';
|
||||
import axios from 'axios';
|
||||
import { useRouter } from 'vue-router';
|
||||
import useNotify from './useNotify';
|
||||
import { useTokenConfig } from './useTokenConfig';
|
||||
const TOKEN_MULTIMEDIA = 'tokenMultimedia';
|
||||
const TOKEN = 'token';
|
||||
|
||||
export function useSession() {
|
||||
const router = useRouter();
|
||||
const { notify } = useNotify();
|
||||
let isCheckingToken = false;
|
||||
let intervalId = null;
|
||||
|
@ -102,6 +104,31 @@ export function useSession() {
|
|||
startInterval();
|
||||
}
|
||||
|
||||
async function setLogin(data) {
|
||||
const {
|
||||
data: { multimediaToken },
|
||||
} = await axios.get('VnUsers/ShareToken', {
|
||||
headers: { Authorization: data.token },
|
||||
});
|
||||
|
||||
if (!multimediaToken) return;
|
||||
|
||||
await login({
|
||||
...data,
|
||||
created: Date.now(),
|
||||
tokenMultimedia: multimediaToken.id,
|
||||
});
|
||||
|
||||
notify('login.loginSuccess', 'positive');
|
||||
|
||||
const currentRoute = router.currentRoute.value;
|
||||
if (currentRoute.query?.redirect) {
|
||||
router.push(currentRoute.query.redirect);
|
||||
} else {
|
||||
router.push({ name: 'Dashboard' });
|
||||
}
|
||||
}
|
||||
|
||||
function isLoggedIn() {
|
||||
const localToken = localStorage.getItem(TOKEN);
|
||||
const sessionToken = sessionStorage.getItem(TOKEN);
|
||||
|
@ -163,6 +190,7 @@ export function useSession() {
|
|||
setToken,
|
||||
destroy,
|
||||
login,
|
||||
setLogin,
|
||||
isLoggedIn,
|
||||
checkValidity,
|
||||
setSession,
|
||||
|
|
|
@ -190,6 +190,10 @@ select:-webkit-autofill {
|
|||
font-size: medium;
|
||||
}
|
||||
|
||||
.q-toolbar {
|
||||
background: var(--vn-section-color);
|
||||
}
|
||||
|
||||
.q-card__actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -90,6 +90,9 @@ globals:
|
|||
salesPerson: SalesPerson
|
||||
send: Send
|
||||
code: Code
|
||||
since: Since
|
||||
from: From
|
||||
to: To
|
||||
pageTitles:
|
||||
logIn: Login
|
||||
summary: Summary
|
||||
|
@ -246,6 +249,9 @@ globals:
|
|||
mailForwarding: Mail forwarding
|
||||
mailAlias: Mail alias
|
||||
privileges: Privileges
|
||||
ldap: LDAP
|
||||
samba: Samba
|
||||
serial: Serial
|
||||
created: Created
|
||||
worker: Worker
|
||||
now: Now
|
||||
|
@ -257,6 +263,8 @@ globals:
|
|||
unsavedPopup:
|
||||
title: Unsaved changes will be lost
|
||||
subtitle: Are you sure exit without saving?
|
||||
createInvoiceIn: Create invoice in
|
||||
myAccount: My account
|
||||
errors:
|
||||
statusUnauthorized: Access denied
|
||||
statusInternalServerError: An internal server error has ocurred
|
||||
|
|
|
@ -90,6 +90,9 @@ globals:
|
|||
salesPerson: Comercial
|
||||
send: Enviar
|
||||
code: Código
|
||||
since: Desde
|
||||
from: Desde
|
||||
to: Hasta
|
||||
pageTitles:
|
||||
logIn: Inicio de sesión
|
||||
summary: Resumen
|
||||
|
@ -248,6 +251,9 @@ globals:
|
|||
components: Componentes
|
||||
pictures: Fotos
|
||||
packages: Bultos
|
||||
ldap: LDAP
|
||||
samba: Samba
|
||||
serial: Facturas por serie
|
||||
created: Fecha creación
|
||||
worker: Trabajador
|
||||
now: Ahora
|
||||
|
@ -259,6 +265,8 @@ globals:
|
|||
unsavedPopup:
|
||||
title: Los cambios que no haya guardado se perderán
|
||||
subtitle: ¿Seguro que quiere salir sin guardar?
|
||||
createInvoiceIn: Crear factura recibida
|
||||
myAccount: Mi cuenta
|
||||
errors:
|
||||
statusUnauthorized: Acceso denegado
|
||||
statusInternalServerError: Ha ocurrido un error interno del servidor
|
||||
|
@ -695,8 +703,6 @@ invoiceOut:
|
|||
percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}'
|
||||
pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs'
|
||||
negativeBases:
|
||||
from: Desde
|
||||
to: Hasta
|
||||
company: Empresa
|
||||
country: País
|
||||
clientId: Id cliente
|
||||
|
@ -1238,8 +1244,6 @@ components:
|
|||
# LatestBuysFilter
|
||||
salesPersonFk: Comprador
|
||||
supplierFk: Proveedor
|
||||
from: Desde
|
||||
to: Hasta
|
||||
active: Activo
|
||||
visible: Visible
|
||||
floramondo: Floramondo
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import AclFilter from './Acls/AclFilter.vue';
|
||||
import AclFormView from './Acls/AclFormView.vue';
|
||||
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import VnConfirm from 'components/ui/VnConfirm.vue';
|
||||
|
||||
defineProps({
|
||||
id: {
|
||||
|
@ -25,10 +21,9 @@ defineProps({
|
|||
const { notify } = useNotify();
|
||||
const { t } = useI18n();
|
||||
const stateStore = useStateStore();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const quasar = useQuasar();
|
||||
|
||||
const paginateRef = ref();
|
||||
const formDialog = ref(false);
|
||||
const tableRef = ref();
|
||||
const rolesOptions = ref([]);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
|
@ -40,21 +35,86 @@ const exprBuilder = (param, value) => {
|
|||
}
|
||||
};
|
||||
|
||||
const deleteAcl = async (id) => {
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'id',
|
||||
label: t('id'),
|
||||
isId: true,
|
||||
field: 'id',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'model',
|
||||
label: t('model'),
|
||||
field: 'model',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'principalId',
|
||||
label: t('principalId'),
|
||||
field: 'principalId',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'property',
|
||||
label: t('property'),
|
||||
field: 'property',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'accessType',
|
||||
label: t('accessType'),
|
||||
field: 'accessType',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: '',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('Delete'),
|
||||
icon: 'delete',
|
||||
action: deleteAcl,
|
||||
isPrimary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const deleteAcl = async ({ id }) => {
|
||||
try {
|
||||
await new Promise((resolve) => {
|
||||
quasar
|
||||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t('Remove ACL'),
|
||||
message: t('Do you want to remove this ACL?'),
|
||||
},
|
||||
})
|
||||
.onOk(() => {
|
||||
resolve(true);
|
||||
})
|
||||
.onCancel(() => {
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
await axios.delete(`ACLs/${id}`);
|
||||
paginateRef.value.fetch();
|
||||
tableRef.value.reload();
|
||||
notify('ACL removed', 'positive');
|
||||
} catch (error) {
|
||||
console.error('Error deleting Acl: ', error);
|
||||
}
|
||||
};
|
||||
function showFormDialog(data) {
|
||||
formDialog.value = {
|
||||
show: true,
|
||||
formInitialData: { ...data },
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -64,8 +124,7 @@ function showFormDialog(data) {
|
|||
@on-fetch="(data) => (rolesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
|
||||
<VnSearchbar
|
||||
data-key="AccountAcls"
|
||||
url="ACLs"
|
||||
|
@ -73,73 +132,26 @@ function showFormDialog(data) {
|
|||
:label="t('acls.search')"
|
||||
:info="t('acls.searchInfo')"
|
||||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
|
||||
<QScrollArea class="fit text-grey-8">
|
||||
<AclFilter data-key="AccountAcls" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
|
||||
<QPage class="flex justify-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
ref="paginateRef"
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="AccountAcls"
|
||||
url="ACLs"
|
||||
:expr-builder="exprBuilder"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
v-for="row of rows"
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="`${row.model}.${row.property}`"
|
||||
@click="showFormDialog(row)"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv :label="t('acls.role')" :value="row.principalId" />
|
||||
<VnLv :label="t('acls.accessType')" :value="row.accessType" />
|
||||
<VnLv
|
||||
:label="t('acls.permissions')"
|
||||
:value="row.permission"
|
||||
:url="`ACLs`"
|
||||
:create="{
|
||||
urlCreate: 'ACLs',
|
||||
title: 'Create ACL',
|
||||
onDataSaved: () => tableRef.reload(),
|
||||
formInitialData: {},
|
||||
}"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
:right-search="true"
|
||||
:is-editable="true"
|
||||
:use-model="true"
|
||||
/>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('globals.delete')"
|
||||
@click.stop="
|
||||
openConfirmationModal(
|
||||
t('ACL will be removed'),
|
||||
t('Are you sure you want to continue?'),
|
||||
() => deleteAcl(row.id)
|
||||
)
|
||||
"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QDialog
|
||||
v-model="formDialog.show"
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<AclFormView
|
||||
:form-initial-data="formDialog.formInitialData"
|
||||
@on-data-change="paginateRef.fetch()"
|
||||
:roles-options="rolesOptions"
|
||||
/>
|
||||
</QDialog>
|
||||
<QPageSticky position="bottom-right" :offset="[18, 18]">
|
||||
<QBtn fab icon="add" color="primary" @click="showFormDialog()">
|
||||
<QTooltip class="text-no-wrap">{{ t('New ACL') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
|
@ -148,4 +160,6 @@ es:
|
|||
ACL removed: ACL eliminado
|
||||
ACL will be removed: El ACL será eliminado
|
||||
Are you sure you want to continue?: ¿Seguro que quieres continuar?
|
||||
Remove ACL: Eliminar Acl
|
||||
Do you want to remove this ACL?: ¿Quieres eliminar este ACL?
|
||||
</i18n>
|
||||
|
|
|
@ -1,30 +1,13 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import AliasSummary from './Alias/Card/AliasSummary.vue';
|
||||
import AliasCreateForm from './Alias/AliasCreateForm.vue';
|
||||
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
||||
defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
|
||||
const tableRef = ref();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const router = useRouter();
|
||||
const stateStore = useStateStore();
|
||||
const aliasCreateDialogRef = ref(null);
|
||||
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
|
@ -34,10 +17,32 @@ const exprBuilder = (param, value) => {
|
|||
: { alias: { like: `%${value}%` } };
|
||||
}
|
||||
};
|
||||
|
||||
const navigate = (id) => router.push({ name: 'AliasSummary', params: { id } });
|
||||
|
||||
const openCreateModal = () => aliasCreateDialogRef.value.show();
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'id',
|
||||
label: t('id'),
|
||||
isId: true,
|
||||
field: 'id',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'alias',
|
||||
label: t('alias'),
|
||||
field: 'alias',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'description',
|
||||
label: t('description'),
|
||||
field: 'description',
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -52,54 +57,22 @@ const openCreateModal = () => aliasCreateDialogRef.value.show();
|
|||
/>
|
||||
</Teleport>
|
||||
</template>
|
||||
<QPage class="flex justify-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
ref="paginateRef"
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="AccountAliasList"
|
||||
url="MailAliases"
|
||||
:expr-builder="exprBuilder"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
v-for="row of rows"
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.alias"
|
||||
@click="navigate(row.id)"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv :label="t('mailAlias.alias')" :value="row.alias">
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('mailAlias.description')"
|
||||
:value="row.description"
|
||||
>
|
||||
</VnLv>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, AliasSummary)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
:url="`MailAliases`"
|
||||
:create="{
|
||||
urlCreate: 'MailAliases',
|
||||
title: 'Create MailAlias',
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {},
|
||||
}"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
redirect="account/alias"
|
||||
:is-editable="true"
|
||||
:use-model="true"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QDialog
|
||||
ref="aliasCreateDialogRef"
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<AliasCreateForm />
|
||||
</QDialog>
|
||||
<QPageSticky position="bottom-right" :offset="[18, 18]">
|
||||
<QBtn fab icon="add" color="primary" @click="openCreateModal()">
|
||||
<QTooltip class="text-no-wrap">{{ t('mailAlias.newAlias') }}</QTooltip>
|
||||
</QBtn>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
|
|
@ -2,11 +2,9 @@
|
|||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
|
||||
import { toDateTimeFormat } from 'src/filters/date.js';
|
||||
import axios from 'axios';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
|
@ -15,7 +14,6 @@ const newAccountForm = reactive({
|
|||
active: true,
|
||||
});
|
||||
const rolesOptions = ref([]);
|
||||
|
||||
const redirectToAccountBasicData = (_, { id }) => {
|
||||
router.push({ name: 'AccountBasicData', params: { id } });
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<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';
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, computed } 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';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
|
@ -15,7 +13,6 @@ const { t } = useI18n();
|
|||
const { notify } = useNotify();
|
||||
|
||||
const arrayData = useArrayData('AccountLdap');
|
||||
|
||||
const URL_UPDATE = `LdapConfigs/${1}`;
|
||||
const URL_CREATE = `LdapConfigs`;
|
||||
const DEFAULT_DATA = {
|
||||
|
@ -27,11 +24,9 @@ const DEFAULT_DATA = {
|
|||
server: null,
|
||||
userDn: null,
|
||||
};
|
||||
|
||||
const initialData = ref({
|
||||
...DEFAULT_DATA,
|
||||
});
|
||||
|
||||
const hasData = computed({
|
||||
get: () => initialData.value.hasData,
|
||||
set: (val) => {
|
||||
|
@ -40,12 +35,10 @@ const hasData = computed({
|
|||
else formCustomFn.value = null;
|
||||
},
|
||||
});
|
||||
|
||||
const initialDataLoaded = ref(false);
|
||||
const formUrlCreate = ref(null);
|
||||
const formUrlUpdate = ref(null);
|
||||
const formCustomFn = ref(null);
|
||||
|
||||
const onTestConection = async () => {
|
||||
try {
|
||||
await axios.get(`LdapConfigs/test`);
|
||||
|
@ -54,7 +47,6 @@ const onTestConection = async () => {
|
|||
console.error('Error testing connection', error);
|
||||
}
|
||||
};
|
||||
|
||||
const getInitialLdapConfig = async () => {
|
||||
try {
|
||||
initialDataLoaded.value = false;
|
||||
|
@ -79,7 +71,6 @@ const getInitialLdapConfig = async () => {
|
|||
initialDataLoaded.value = true;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteMailForward = async () => {
|
||||
try {
|
||||
await axios.delete(URL_UPDATE);
|
||||
|
|
|
@ -1,33 +1,66 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import { ref, computed } from 'vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import VnLv from 'src/components/ui/VnLv.vue';
|
||||
import CardList from 'src/components/ui/CardList.vue';
|
||||
import AccountSummary from './Card/AccountSummary.vue';
|
||||
import AccountFilter from './AccountFilter.vue';
|
||||
import AccountCreate from './AccountCreate.vue';
|
||||
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import { QDialog } from 'quasar';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
const accountCreateDialogRef = ref(null);
|
||||
const showNewUserBtn = computed(() => useRole().hasAny(['itManagement']));
|
||||
|
||||
const filter = {
|
||||
fields: ['id', 'nickname', 'name', 'role'],
|
||||
include: { relation: 'role', scope: { fields: ['id', 'name'] } },
|
||||
};
|
||||
const tableRef = ref();
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'id',
|
||||
label: t('id'),
|
||||
isId: true,
|
||||
field: 'id',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'username',
|
||||
label: t('nickname'),
|
||||
isTitle: true,
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'name',
|
||||
label: t('name'),
|
||||
component: 'input',
|
||||
columnField: {
|
||||
component: null,
|
||||
},
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
label: '',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('View Summary'),
|
||||
icon: 'preview',
|
||||
action: (row) => viewSummary(row.id, AccountSummary),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
|
@ -46,99 +79,25 @@ const exprBuilder = (param, value) => {
|
|||
return { [param]: value };
|
||||
}
|
||||
};
|
||||
|
||||
const getApiUrl = () => new URL(window.location).origin;
|
||||
|
||||
const navigate = (event, id) => {
|
||||
if (event.ctrlKey || event.metaKey)
|
||||
return window.open(`${getApiUrl()}/#/account/${id}/summary`);
|
||||
router.push({ path: `/account/${id}` });
|
||||
};
|
||||
|
||||
const openCreateModal = () => accountCreateDialogRef.value.show();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
data-key="AccountList"
|
||||
url="VnUsers/preview"
|
||||
:expr-builder="exprBuilder"
|
||||
:label="t('account.search')"
|
||||
data-key="AccountUsers"
|
||||
:expr-builder="exprBuilder"
|
||||
:info="t('account.searchInfo')"
|
||||
/>
|
||||
</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" :expr-builder="exprBuilder" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
:filter="filter"
|
||||
data-key="AccountList"
|
||||
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="AccountUsers"
|
||||
url="VnUsers/preview"
|
||||
order="id DESC"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
v-for="row of rows"
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.nickname"
|
||||
@click="navigate($event, row.id)"
|
||||
>
|
||||
<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('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, AccountSummary)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
redirect="account"
|
||||
:use-model="true"
|
||||
/>
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
<QDialog
|
||||
ref="accountCreateDialogRef"
|
||||
transition-hide="scale"
|
||||
transition-show="scale"
|
||||
>
|
||||
<AccountCreate />
|
||||
</QDialog>
|
||||
<QPageSticky :offset="[20, 20]" v-if="showNewUserBtn">
|
||||
<QBtn @click="openCreateModal" color="primary" fab icon="add" />
|
||||
<QTooltip class="text-no-wrap">
|
||||
{{ t('account.card.newUser') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
<script setup>
|
||||
import { ref, onMounted, computed } 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';
|
||||
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const arrayData = useArrayData('AccountSamba');
|
||||
|
||||
const formModel = ref(null);
|
||||
|
||||
const URL_UPDATE = `SambaConfigs/${1}`;
|
||||
const URL_CREATE = `SambaConfigs`;
|
||||
|
||||
|
|
|
@ -25,9 +25,11 @@ const searchBarDataKeys = {
|
|||
base-url="MailAliases"
|
||||
:descriptor="AliasDescriptor"
|
||||
:search-data-key="searchBarDataKeys[routeName]"
|
||||
:search-custom-route-redirect="customRouteRedirectName"
|
||||
:search-redirect="!!customRouteRedirectName"
|
||||
:searchbar-label="t('mailAlias.search')"
|
||||
:searchbar-info="t('mailAlias.searchInfo')"
|
||||
:searchbar-props="{
|
||||
redirect: !!customRouteRedirectName,
|
||||
customRouteRedirectName,
|
||||
info: t('mailAlias.searchInfo'),
|
||||
label: t('mailAlias.search'),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -28,6 +28,7 @@ const entityId = computed(() => $props.id || route.params.id);
|
|||
ref="summary"
|
||||
:url="`MailAliases/${entityId}`"
|
||||
@on-fetch="(data) => (alias = data)"
|
||||
data-key="MailAliasesSummary"
|
||||
>
|
||||
<template #header> {{ alias.id }} - {{ alias.alias }} </template>
|
||||
<template #body>
|
||||
|
|
|
@ -26,9 +26,11 @@ const searchBarDataKeys = {
|
|||
data-key="Account"
|
||||
:descriptor="AccountDescriptor"
|
||||
:search-data-key="searchBarDataKeys[routeName]"
|
||||
:search-custom-route-redirect="customRouteRedirectName"
|
||||
:search-redirect="!!customRouteRedirectName"
|
||||
:searchbar-label="t('account.search')"
|
||||
:searchbar-info="t('account.searchInfo')"
|
||||
:searchbar-props="{
|
||||
redirect: !!customRouteRedirectName,
|
||||
customRouteRedirectName,
|
||||
label: t('account.search'),
|
||||
info: t('account.searchInfo'),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -54,7 +54,7 @@ const hasAccount = ref(false);
|
|||
</template>
|
||||
<template #before>
|
||||
<!-- falla id :id="entityId.value" collection="user" size="160x160" -->
|
||||
<VnImg :id="entityId" collection="user" size="160x160" class="photo">
|
||||
<VnImg :id="entityId" collection="user" resolution="160x160" class="photo">
|
||||
<template #error>
|
||||
<div
|
||||
class="absolute-full picture text-center q-pa-md flex flex-center"
|
||||
|
|
|
@ -8,7 +8,7 @@ import { useRoute } from 'vue-router';
|
|||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
|
||||
import VnConfirm from 'src/components/ui/VnConfirm.vue';
|
||||
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
const quasar = useQuasar();
|
||||
const $props = defineProps({
|
||||
hasAccount: {
|
||||
|
@ -21,7 +21,7 @@ const { t } = useI18n();
|
|||
const { hasAccount } = toRefs($props);
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const route = useRoute();
|
||||
|
||||
const { notify } = useNotify();
|
||||
const account = computed(() => useArrayData('AccountId').store.data[0]);
|
||||
account.value.hasAccount = hasAccount.value;
|
||||
const entityId = computed(() => +route.params.id);
|
||||
|
@ -71,55 +71,17 @@ async function sync() {
|
|||
type: 'positive',
|
||||
});
|
||||
}
|
||||
|
||||
const removeAccount = async () => {
|
||||
try {
|
||||
await axios.delete(`VnUsers/${account.value.id}`);
|
||||
notify(t('Account removed'), 'positive');
|
||||
} catch (error) {
|
||||
console.error('Error deleting user', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<VnConfirm
|
||||
v-model="showSyncDialog"
|
||||
:message="t('account.card.actions.sync.message')"
|
||||
:title="t('account.card.actions.sync.title')"
|
||||
:promise="sync"
|
||||
>
|
||||
<template #customHTML>
|
||||
{{ shouldSyncPassword }}
|
||||
<QCheckbox
|
||||
:label="t('account.card.actions.sync.checkbox')"
|
||||
v-model="shouldSyncPassword"
|
||||
class="full-width"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
>
|
||||
<QIcon style="padding-left: 10px" color="primary" name="info" size="sm">
|
||||
<QTooltip>{{ t('account.card.actions.sync.tooltip') }}</QTooltip>
|
||||
</QIcon></QCheckbox
|
||||
>
|
||||
<QInput
|
||||
v-if="shouldSyncPassword"
|
||||
:label="t('login.password')"
|
||||
v-model="syncPassword"
|
||||
class="full-width"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
type="password"
|
||||
/>
|
||||
</template>
|
||||
</VnConfirm>
|
||||
<QItem v-ripple clickable @click="setPassword">
|
||||
<QItemSection>{{ t('account.card.actions.setPassword') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="!account.hasAccount"
|
||||
v-ripple
|
||||
clickable
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.enableAccount.title'),
|
||||
t('account.card.actions.enableAccount.subtitle'),
|
||||
() => updateStatusAccount(true)
|
||||
)
|
||||
"
|
||||
>
|
||||
<QItemSection>{{ t('account.card.actions.enableAccount.name') }}</QItemSection>
|
||||
</QItem>
|
||||
<QItem
|
||||
v-if="account.hasAccount"
|
||||
v-ripple
|
||||
|
@ -168,20 +130,10 @@ async function sync() {
|
|||
</QItem>
|
||||
|
||||
<QSeparator />
|
||||
<QItem
|
||||
@click="
|
||||
openConfirmationModal(
|
||||
t('account.card.actions.delete.title'),
|
||||
t('account.card.actions.delete.subTitle'),
|
||||
removeAccount
|
||||
)
|
||||
"
|
||||
v-ripple
|
||||
clickable
|
||||
>
|
||||
<!-- <QItem @click="removeAccount(id)" v-ripple clickable>
|
||||
<QItemSection avatar>
|
||||
<QIcon name="delete" />
|
||||
</QItemSection>
|
||||
<QItemSection>{{ t('account.card.actions.delete.name') }}</QItemSection>
|
||||
</QItem>
|
||||
</QItem> -->
|
||||
</template>
|
||||
|
|
|
@ -85,7 +85,7 @@ const fetchMailAliases = async () => {
|
|||
paginateRef.value.fetch();
|
||||
};
|
||||
|
||||
const getAccountData = async () => {
|
||||
const getAccountData = async (reload = true) => {
|
||||
loading.value = true;
|
||||
hasAccount.value = await fetchAccountExistence();
|
||||
if (!hasAccount.value) {
|
||||
|
@ -93,7 +93,7 @@ const getAccountData = async () => {
|
|||
store.data = [];
|
||||
return;
|
||||
}
|
||||
await fetchMailAliases();
|
||||
reload && (await fetchMailAliases());
|
||||
loading.value = false;
|
||||
};
|
||||
|
||||
|
@ -102,13 +102,11 @@ const openCreateMailAliasForm = () => createMailAliasDialogRef.value.show();
|
|||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
store.url = urlPath;
|
||||
store.filter = filter.value;
|
||||
getAccountData();
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(async () => await getAccountData());
|
||||
onMounted(async () => await getAccountData(false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -1,23 +1,17 @@
|
|||
<script setup>
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
|
||||
const props = defineProps({
|
||||
dataKey: { type: String, required: true },
|
||||
});
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const paginateRef = ref(null);
|
||||
|
||||
const arrayData = useArrayData(props.dataKey);
|
||||
const store = arrayData.store;
|
||||
|
||||
const data = computed(() => {
|
||||
const dataCopy = store.data;
|
||||
return dataCopy.sort((a, b) => a.role?.name.localeCompare(b.role?.name));
|
||||
|
@ -37,7 +31,6 @@ const filter = computed(() => ({
|
|||
}));
|
||||
|
||||
const urlPath = 'RoleMappings';
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
name: 'name',
|
||||
|
|
|
@ -1,26 +1,48 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
|
||||
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 RoleSummary from './Card/RoleSummary.vue';
|
||||
import RoleForm from './Card/RoleForm.vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnSearchbar from 'components/ui/VnSearchbar.vue';
|
||||
import AccountRolesFilter from './AccountRolesFilter.vue';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
||||
const roleCreateDialogRef = ref(null);
|
||||
const $props = defineProps({
|
||||
id: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
});
|
||||
const tableRef = ref();
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'id',
|
||||
label: t('id'),
|
||||
isId: true,
|
||||
columnFilter: {
|
||||
inWhere: true,
|
||||
},
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'name',
|
||||
label: t('name'),
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'description',
|
||||
label: t('description'),
|
||||
cardVisible: true,
|
||||
create: true,
|
||||
},
|
||||
]);
|
||||
const exprBuilder = (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
|
@ -37,95 +59,36 @@ const exprBuilder = (param, value) => {
|
|||
return { [param]: { like: `%${value}%` } };
|
||||
}
|
||||
};
|
||||
|
||||
const openCreateModal = () => roleCreateDialogRef.value.show();
|
||||
|
||||
const getApiUrl = () => new URL(window.location).origin;
|
||||
|
||||
const navigate = (event, id) => {
|
||||
if (event.ctrlKey || event.metaKey)
|
||||
return window.open(`${getApiUrl()}/#/account/role/${id}/summary`);
|
||||
router.push({ name: 'RoleSummary', params: { id } });
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
<Teleport to="#searchbar">
|
||||
<VnSearchbar
|
||||
data-key="RolesList"
|
||||
url="VnRoles"
|
||||
data-key="Roles"
|
||||
:expr-builder="exprBuilder"
|
||||
:label="t('role.searchRoles')"
|
||||
:info="t('role.searchInfo')"
|
||||
/>
|
||||
</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">
|
||||
<AccountRolesFilter data-key="RolesList" :expr-builder="exprBuilder" />
|
||||
</QScrollArea>
|
||||
</QDrawer>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate data-key="RolesList" url="VnRoles">
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
:id="row.id"
|
||||
:key="row.id"
|
||||
:title="row.name"
|
||||
@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('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, RoleSummary)"
|
||||
color="primary"
|
||||
style="margin-top: 15px"
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="Roles"
|
||||
:url="`VnRoles`"
|
||||
:create="{
|
||||
urlCreate: 'VnRoles',
|
||||
title: t('Create rol'),
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {
|
||||
editorFk: entityId,
|
||||
},
|
||||
}"
|
||||
order="id ASC"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
redirect="account/role"
|
||||
:is-editable="true"
|
||||
/>
|
||||
</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('role.newRole') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</QPage>
|
||||
</template>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnCard from 'components/common/VnCard.vue';
|
||||
import RoleDescriptor from './RoleDescriptor.vue';
|
||||
|
||||
|
@ -24,9 +23,11 @@ const searchBarDataKeys = {
|
|||
data-key="Role"
|
||||
:descriptor="RoleDescriptor"
|
||||
:search-data-key="searchBarDataKeys[routeName]"
|
||||
:search-custom-route-redirect="customRouteRedirectName"
|
||||
:search-redirect="!!customRouteRedirectName"
|
||||
:searchbar-label="t('role.searchRoles')"
|
||||
:searchbar-info="t('role.searchInfo')"
|
||||
:searchbar-props="{
|
||||
redirect: !!customRouteRedirectName,
|
||||
customRouteRedirectName,
|
||||
label: t('role.searchRoles'),
|
||||
info: t('role.searchInfo'),
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
<script setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
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({
|
||||
|
@ -23,9 +21,6 @@ const $props = defineProps({
|
|||
|
||||
const route = useRoute();
|
||||
|
||||
const quasar = useQuasar();
|
||||
const router = useRouter();
|
||||
|
||||
const { notify } = useNotify();
|
||||
const { t } = useI18n();
|
||||
const entityId = computed(() => {
|
||||
|
@ -36,29 +31,13 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity
|
|||
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 () => {
|
||||
const removeRole = 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');
|
||||
await axios.delete(`VnRoles/${entityId.value}`);
|
||||
notify(t('Role removed'), 'positive');
|
||||
} catch (error) {
|
||||
console.error('Error deleting role', error);
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script setup>
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import FormModelPopup from 'components/FormModelPopup.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
|
|
|
@ -2,17 +2,14 @@
|
|||
import { reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import FormPopup from 'components/FormPopup.vue';
|
||||
|
||||
const emit = defineEmits(['onSubmitCreateSubrole']);
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const subRoleFormData = reactive({
|
||||
inheritsFrom: null,
|
||||
role: route.params.id,
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import VnPaginate from 'components/ui/VnPaginate.vue';
|
||||
import SubRoleCreateForm from './SubRoleCreateForm.vue';
|
||||
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
|
@ -16,10 +14,8 @@ const route = useRoute();
|
|||
const router = useRouter();
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { notify } = useNotify();
|
||||
|
||||
const paginateRef = ref(null);
|
||||
const createSubRoleDialogRef = ref(null);
|
||||
|
||||
const arrayData = useArrayData('SubRoles');
|
||||
const store = arrayData.store;
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ account:
|
|||
delete:
|
||||
name: Delete
|
||||
title: The account will be deleted
|
||||
subtitle: Are you sure you want to continue?
|
||||
subTitle: Are you sure you want to continue?
|
||||
success: ''
|
||||
search: Search user
|
||||
searchInfo: You can search by id, name or nickname
|
||||
|
|
|
@ -67,7 +67,7 @@ account:
|
|||
delete:
|
||||
name: Eliminar
|
||||
title: El usuario será eliminado
|
||||
subtitle: ¿Seguro que quieres continuar?
|
||||
subTitle: ¿Seguro que quieres continuar?
|
||||
success: ''
|
||||
search: Buscar usuario
|
||||
searchInfo: Puedes buscar por id, nombre o usuario
|
||||
|
|
|
@ -10,13 +10,10 @@ import VnInput from 'src/components/common/VnInput.vue';
|
|||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
|
||||
import axios from 'axios';
|
||||
// import { useSession } from 'src/composables/useSession';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
// const { getTokenMultimedia } = useSession();
|
||||
// const token = getTokenMultimedia();
|
||||
|
||||
const claimStates = ref([]);
|
||||
const claimStatesCopy = ref([]);
|
||||
|
@ -94,15 +91,14 @@ const statesFilter = {
|
|||
:rules="validate('claim.claimStateFk')"
|
||||
>
|
||||
<template #before>
|
||||
<QAvatar color="orange">
|
||||
<VnImg
|
||||
v-if="data.workerFk"
|
||||
:size="'160x160'"
|
||||
:id="data.workerFk"
|
||||
collection="user"
|
||||
spinner-color="white"
|
||||
<VnAvatar
|
||||
:worker-id="data.workerFk"
|
||||
size="md"
|
||||
:title="
|
||||
workersOptions.find(({ id }) => id == data.workerFk)?.name
|
||||
"
|
||||
color="primary"
|
||||
/>
|
||||
</QAvatar>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<QSelect
|
||||
|
|
|
@ -11,9 +11,11 @@ import filter from './ClaimFilter.js';
|
|||
:descriptor="ClaimDescriptor"
|
||||
:filter-panel="ClaimFilter"
|
||||
search-data-key="ClaimList"
|
||||
search-url="Claims/filter"
|
||||
searchbar-label="Search claim"
|
||||
searchbar-info="You can search by claim id or customer name"
|
||||
:filter="filter"
|
||||
:searchbar-props="{
|
||||
url: 'Claims/filter',
|
||||
label: 'Search claim',
|
||||
info: 'You can search by claim id or customer name',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -36,8 +36,6 @@ const $props = defineProps({
|
|||
|
||||
const entityId = computed(() => $props.id || route.params.id);
|
||||
const ClaimStates = ref([]);
|
||||
const claimUrl = ref();
|
||||
const salixUrl = ref();
|
||||
const claimDmsRef = ref();
|
||||
const claimDms = ref([]);
|
||||
const multimediaDialog = ref();
|
||||
|
@ -152,11 +150,6 @@ const developmentColumns = ref([
|
|||
},
|
||||
]);
|
||||
|
||||
onMounted(async () => {
|
||||
salixUrl.value = await getUrl('');
|
||||
claimUrl.value = salixUrl.value + `claim/${entityId.value}/`;
|
||||
});
|
||||
|
||||
async function getClaimDms() {
|
||||
claimDmsFilter.value.where = { claimFk: entityId.value };
|
||||
await claimDmsRef.value.fetch();
|
||||
|
@ -177,10 +170,15 @@ function openDialog(dmsId) {
|
|||
multimediaSlide.value = dmsId;
|
||||
multimediaDialog.value = true;
|
||||
}
|
||||
|
||||
async function changeState(value) {
|
||||
await axios.patch(`Claims/updateClaim/${entityId.value}`, { claimStateFk: value });
|
||||
router.go(route.fullPath);
|
||||
}
|
||||
|
||||
function claimUrl(section) {
|
||||
return '#/claim/' + entityId.value + '/' + section;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -234,7 +232,7 @@ async function changeState(value) {
|
|||
<template #body="{ entity: { claim, salesClaimed, developments } }">
|
||||
<QCard class="vn-one" v-if="$route.name != 'ClaimSummary'">
|
||||
<VnTitle
|
||||
:url="`#/claim/${entityId}/basic-data`"
|
||||
:url="claimUrl('basic-data')"
|
||||
:text="t('globals.pageTitles.basicData')"
|
||||
/>
|
||||
<VnLv :label="t('claim.created')" :value="toDate(claim.created)" />
|
||||
|
@ -275,7 +273,7 @@ async function changeState(value) {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-two">
|
||||
<VnTitle :url="`#/claim/${entityId}/notes`" :text="t('claim.notes')" />
|
||||
<VnTitle :url="claimUrl('notes')" :text="t('claim.notes')" />
|
||||
<ClaimNotes
|
||||
:id="entityId"
|
||||
:add-note="false"
|
||||
|
@ -284,7 +282,7 @@ async function changeState(value) {
|
|||
/>
|
||||
</QCard>
|
||||
<QCard class="vn-two" v-if="claimDms?.length">
|
||||
<VnTitle :url="`#/claim/${entityId}/photos`" :text="t('claim.photos')" />
|
||||
<VnTitle :url="claimUrl('photos')" :text="t('claim.photos')" />
|
||||
<div class="container max-container-height" style="overflow: auto">
|
||||
<div
|
||||
class="multimedia-container"
|
||||
|
@ -326,7 +324,7 @@ async function changeState(value) {
|
|||
</div>
|
||||
</QCard>
|
||||
<QCard class="vn-max" v-if="salesClaimed.length > 0">
|
||||
<VnTitle :url="`#/claim/${entityId}/lines`" :text="t('claim.details')" />
|
||||
<VnTitle :url="claimUrl('lines')" :text="t('claim.details')" />
|
||||
<QTable
|
||||
:columns="detailsColumns"
|
||||
:rows="salesClaimed"
|
||||
|
@ -365,7 +363,7 @@ async function changeState(value) {
|
|||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-max" v-if="developments.length > 0">
|
||||
<VnTitle :url="claimUrl + 'development'" :text="t('claim.development')" />
|
||||
<VnTitle :url="claimUrl('development')" :text="t('claim.development')" />
|
||||
<QTable
|
||||
:columns="developmentColumns"
|
||||
:rows="developments"
|
||||
|
@ -390,7 +388,7 @@ async function changeState(value) {
|
|||
</QTable>
|
||||
</QCard>
|
||||
<QCard class="vn-max">
|
||||
<VnTitle :url="claimUrl + 'action'" :text="t('claim.actions')" />
|
||||
<VnTitle :url="claimUrl('action')" :text="t('claim.actions')" />
|
||||
<div id="slider-container" class="q-px-xl q-py-md">
|
||||
<QSlider
|
||||
v-model="claim.responsibility"
|
||||
|
|
|
@ -1,120 +1,171 @@
|
|||
<script setup>
|
||||
import { computed, onBeforeMount, ref, watch } from 'vue';
|
||||
import { computed, onBeforeMount, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useRole } from 'src/composables/useRole';
|
||||
import axios from 'axios';
|
||||
import { QCheckbox, QBtn, useQuasar } from 'quasar';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import { toCurrency, toDate, toDateHourMin } from 'src/filters';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import { useState } from 'composables/useState';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import { usePrintService } from 'src/composables/usePrintService';
|
||||
import { useSession } from 'src/composables/useSession';
|
||||
import { usePrintService } from 'composables/usePrintService';
|
||||
import { useSession } from 'composables/useSession';
|
||||
import { useVnConfirm } from 'composables/useVnConfirm';
|
||||
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnInput from 'components/common/VnInput.vue';
|
||||
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
|
||||
import VnFilter from 'components/VnTable/VnFilter.vue';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue';
|
||||
const session = useSession();
|
||||
|
||||
const tokenMultimedia = session.getTokenMultimedia();
|
||||
|
||||
const { openConfirmationModal } = useVnConfirm();
|
||||
const { sendEmail } = usePrintService();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
const { hasAny } = useRole();
|
||||
|
||||
const session = useSession();
|
||||
const tokenMultimedia = session.getTokenMultimedia();
|
||||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
const state = useState();
|
||||
const stateStore = useStateStore();
|
||||
const user = state.getUser();
|
||||
|
||||
const clientRisks = ref(null);
|
||||
const clientRisksRef = ref(null);
|
||||
const companiesOptions = ref([]);
|
||||
const companyId = ref(null);
|
||||
const receiptsRef = ref(null);
|
||||
const receiptsData = ref([]);
|
||||
|
||||
const filterCompanies = { order: ['code'] };
|
||||
const userParams = {
|
||||
const clientRisk = ref([]);
|
||||
const tableRef = ref();
|
||||
const companyId = ref();
|
||||
const companyLastId = ref(user.value.companyFk);
|
||||
const balances = ref([]);
|
||||
const vnFilterRef = ref({});
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
clientId: route.params.id,
|
||||
companyId: user.value.companyFk,
|
||||
};
|
||||
const filter = {
|
||||
include: { relation: 'company', scope: { fields: ['code'] } },
|
||||
where: { clientFk: route.params.id, companyFk: user.value.companyFk },
|
||||
companyId: companyId.value ?? user.value.companyFk,
|
||||
};
|
||||
});
|
||||
|
||||
const companyFilterColumn = {
|
||||
align: 'left',
|
||||
name: 'companyId',
|
||||
label: t('Company'),
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'Companies',
|
||||
optionLabel: 'code',
|
||||
sortBy: 'code',
|
||||
limit: 0,
|
||||
},
|
||||
columnFilter: {
|
||||
event: {
|
||||
remove: () => (companyId.value = null),
|
||||
'update:modelValue': (newCompanyFk) => {
|
||||
if (!newCompanyFk) return;
|
||||
vnFilterRef.value.addFilter(newCompanyFk);
|
||||
companyLastId.value = newCompanyFk;
|
||||
},
|
||||
blur: () =>
|
||||
!companyId.value &&
|
||||
(companyId.value = companyLastId.value ?? user.value.companyFk),
|
||||
},
|
||||
},
|
||||
visible: false,
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
field: 'payed',
|
||||
format: (value) => toDate(value),
|
||||
name: 'payed',
|
||||
label: t('Date'),
|
||||
name: 'date',
|
||||
format: ({ payed }) => toDate(payed),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'created',
|
||||
format: (value) => toDateHourMin(value),
|
||||
name: 'created',
|
||||
label: t('Creation date'),
|
||||
name: 'creationDate',
|
||||
format: ({ created }) => toDateHourMin(created),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'userName',
|
||||
name: 'workerFk',
|
||||
label: t('Employee'),
|
||||
name: 'employee',
|
||||
columnField: {
|
||||
component: 'userLink',
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
workerId: row.workerFk,
|
||||
name: row.userName,
|
||||
};
|
||||
},
|
||||
},
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'description',
|
||||
name: 'description',
|
||||
label: t('Reference'),
|
||||
name: 'reference',
|
||||
isTitle: true,
|
||||
class: 'extend',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'bankFk',
|
||||
align: 'right',
|
||||
name: 'bankFk',
|
||||
label: t('Bank'),
|
||||
name: 'bank',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: 'debit',
|
||||
format: (value) => value && toCurrency(value),
|
||||
label: t('Debit'),
|
||||
name: 'debit',
|
||||
label: t('Debit'),
|
||||
format: ({ debit }) => debit && toCurrency(debit),
|
||||
isId: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: 'credit',
|
||||
format: (value) => value && toCurrency(value),
|
||||
name: 'credit',
|
||||
label: t('Havings'),
|
||||
name: 'havings',
|
||||
format: ({ credit }) => credit && toCurrency(credit),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
field: 'balance',
|
||||
format: (value) => value && toCurrency(value),
|
||||
label: t('Balance'),
|
||||
name: 'balance',
|
||||
label: t('Balance'),
|
||||
format: ({ balance }) => toCurrency(balance),
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'isConciliate',
|
||||
name: 'isConciliate',
|
||||
label: t('Conciliated'),
|
||||
name: 'conciliated',
|
||||
cardVisible: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'totalWithVat',
|
||||
label: '',
|
||||
name: 'actions',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('globals.downloadPdf'),
|
||||
icon: 'cloud_download',
|
||||
show: (row) => row.isInvoice,
|
||||
action: (row) => showBalancePdf(row),
|
||||
},
|
||||
{
|
||||
title: t('Send compensation'),
|
||||
icon: 'outgoing_mail',
|
||||
show: (row) => !!row.isCompensation,
|
||||
action: ({ id }) =>
|
||||
openConfirmationModal(
|
||||
t('Send compensation'),
|
||||
t('Do you want to report compensation to the client by mail?'),
|
||||
() => sendEmail(`Receipts/${id}/balance-compensation-email`)
|
||||
),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -123,249 +174,124 @@ onBeforeMount(() => {
|
|||
companyId.value = user.value.companyFk;
|
||||
});
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
(newValue) => {
|
||||
if (!newValue) return;
|
||||
userParams.clientId = newValue;
|
||||
filter.where.clientFk = newValue;
|
||||
getData();
|
||||
}
|
||||
);
|
||||
async function getClientRisk() {
|
||||
const { data } = await axios.get(`clientRisks`, {
|
||||
params: {
|
||||
filter: JSON.stringify({
|
||||
include: { relation: 'company', scope: { fields: ['code'] } },
|
||||
where: { clientFk: route.params.id, companyFk: user.value.companyFk },
|
||||
}),
|
||||
},
|
||||
});
|
||||
clientRisk.value = data;
|
||||
return clientRisk.value;
|
||||
}
|
||||
|
||||
const getData = () => {
|
||||
receiptsRef.value?.fetch();
|
||||
clientRisksRef.value?.fetch();
|
||||
};
|
||||
|
||||
const getCurrentBalance = () => {
|
||||
const currentBalance = clientRisks.value.find((balance) => {
|
||||
async function getCurrentBalance() {
|
||||
const currentBalance = (await getClientRisk()).find((balance) => {
|
||||
return balance.companyFk === companyId.value;
|
||||
});
|
||||
return currentBalance && currentBalance.amount;
|
||||
};
|
||||
}
|
||||
|
||||
const onFetch = (balances) => {
|
||||
balances.forEach((balance, index) => {
|
||||
async function onFetch(data) {
|
||||
balances.value = [];
|
||||
for (const [index, balance] of data.entries()) {
|
||||
if (index === 0) {
|
||||
balance.balance = getCurrentBalance();
|
||||
} else {
|
||||
let previousBalance = balances[index - 1];
|
||||
balance.balance =
|
||||
previousBalance.balance -
|
||||
(previousBalance.debit - previousBalance.credit);
|
||||
balance.balance = await getCurrentBalance();
|
||||
continue;
|
||||
}
|
||||
});
|
||||
|
||||
receiptsData.value = balances;
|
||||
};
|
||||
const previousBalance = data[index - 1];
|
||||
balance.balance =
|
||||
previousBalance?.balance - (previousBalance?.debit - previousBalance?.credit);
|
||||
}
|
||||
balances.value = data;
|
||||
}
|
||||
|
||||
const showNewPaymentDialog = () => {
|
||||
quasar.dialog({
|
||||
component: CustomerNewPayment,
|
||||
componentProps: {
|
||||
companyId: companyId.value,
|
||||
totalCredit: clientRisks.value[0]?.amount,
|
||||
promise: getData,
|
||||
totalCredit: clientRisk.value[0]?.amount,
|
||||
promise: () => tableRef.value.reload(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const updateCompanyId = (id) => {
|
||||
if (id) {
|
||||
companyId.value = id;
|
||||
userParams.companyId = id;
|
||||
filter.where.companyFk = id;
|
||||
}
|
||||
getData();
|
||||
};
|
||||
|
||||
const saveFieldValue = async (row) => {
|
||||
try {
|
||||
const payload = { description: row.description };
|
||||
await axios.patch(`Receipts/${row.id}`, payload);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
const sendEmailAction = () => {
|
||||
sendEmail(`Suppliers/${route.params.id}/campaign-metrics-email`);
|
||||
};
|
||||
|
||||
const showBalancePdf = (balance) => {
|
||||
const url = `api/InvoiceOuts/${balance.id}/download?access_token=${tokenMultimedia}`;
|
||||
const showBalancePdf = ({ id }) => {
|
||||
const url = `api/InvoiceOuts/${id}/download?access_token=${tokenMultimedia}`;
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (clientRisks = data)"
|
||||
auto-load
|
||||
ref="clientRisksRef"
|
||||
url="ClientRisks"
|
||||
<VnSubToolbar class="q-mb-md">
|
||||
<template #st-data>
|
||||
<div class="column justify-center q-px-md q-py-sm">
|
||||
<span class="text-bold">{{ t('Total by company') }}</span>
|
||||
<div class="row justify-center" v-if="clientRisk?.length">
|
||||
{{ clientRisk[0].company.code }}:
|
||||
{{ toCurrency(clientRisk[0].amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #st-actions>
|
||||
<div>
|
||||
<VnFilter
|
||||
ref="vnFilterRef"
|
||||
v-model="companyId"
|
||||
data-key="CustomerBalance"
|
||||
:column="companyFilterColumn"
|
||||
search-url="balance"
|
||||
/>
|
||||
<FetchData
|
||||
:filter="filterCompanies"
|
||||
@on-fetch="(data) => (companiesOptions = data)"
|
||||
auto-load
|
||||
url="Companies"
|
||||
/>
|
||||
|
||||
<VnPaginate
|
||||
auto-load
|
||||
</div>
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="CustomerBalance"
|
||||
url="Receipts/filter"
|
||||
:user-params="userParams"
|
||||
ref="receiptsRef"
|
||||
@on-fetch="onFetch"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<QTable
|
||||
search-url="balance"
|
||||
:user-params="filter"
|
||||
:columns="columns"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
:rows-per-page-options="[0]"
|
||||
:rows="rows"
|
||||
class="full-width q-mt-md"
|
||||
row-key="id"
|
||||
:right-search="false"
|
||||
:is-editable="false"
|
||||
:column-search="false"
|
||||
@on-fetch="onFetch"
|
||||
auto-load
|
||||
>
|
||||
<template #body-cell-employee="{ row }">
|
||||
<QTd auto-width @click.stop>
|
||||
<QBtn color="blue" flat no-caps>{{ row.userName }}</QBtn>
|
||||
<WorkerDescriptorProxy :id="row.workerFk" />
|
||||
</QTd>
|
||||
<template #column-balance="{ rowIndex }">
|
||||
{{ toCurrency(balances[rowIndex]?.balance) }}
|
||||
</template>
|
||||
|
||||
<template #body-cell-reference="{ row }">
|
||||
<QTd auto-width @click.stop v-if="row.isInvoice">
|
||||
<QBtn color="blue" dense flat>
|
||||
{{ t('bill', { ref: row.description }) }}
|
||||
</QBtn>
|
||||
<InvoiceOutDescriptorProxy :id="row.id" />
|
||||
</QTd>
|
||||
<QTd v-else>
|
||||
<VnInput
|
||||
@keyup.enter="saveFieldValue(row)"
|
||||
autofocus
|
||||
clearable
|
||||
dense
|
||||
v-model="row.description"
|
||||
/>
|
||||
</QTd>
|
||||
</template>
|
||||
|
||||
<template #body-cell-conciliated="{ row }">
|
||||
<QTd align="center">
|
||||
<QCheckbox :model-value="row.isConciliate === 1" disable />
|
||||
</QTd>
|
||||
</template>
|
||||
|
||||
<template #body-cell-actions="{ row }">
|
||||
<QTd align="center">
|
||||
<QIcon
|
||||
@click.stop="showDialog = true"
|
||||
class="q-ml-md fill-icon"
|
||||
color="primary"
|
||||
name="outgoing_mail"
|
||||
size="sm"
|
||||
v-if="row.isCompensation"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('Send compensation') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
<QIcon
|
||||
@click="showBalancePdf(row)"
|
||||
class="q-ml-md fill-icon"
|
||||
color="primary"
|
||||
name="cloud_download"
|
||||
size="sm"
|
||||
v-if="row.hasPdf"
|
||||
>
|
||||
<QTooltip>
|
||||
{{ t('globals.downloadPdf') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
|
||||
<QDialog v-model="showDialog">
|
||||
<QCard class="q-pa-sm">
|
||||
<QCardSection>
|
||||
<span
|
||||
ref="closeButton"
|
||||
class="flex justify-end color-vn-label"
|
||||
v-close-popup
|
||||
>
|
||||
<QIcon name="close" size="sm" />
|
||||
<template #column-description="{ row }">
|
||||
<div class="link" v-if="row.isInvoice">
|
||||
{{ row.description }}
|
||||
<InvoiceOutDescriptorProxy :id="row.description" />
|
||||
</div>
|
||||
<span v-else class="q-pa-xs dotted rounded-borders" :title="row.description">
|
||||
{{ row.description }}
|
||||
</span>
|
||||
<div class="text-h6">
|
||||
{{ t('Send compensation') }}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardSection>
|
||||
<div>
|
||||
{{
|
||||
t(
|
||||
'Do you want to report compensation to the client by mail?'
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</QCardSection>
|
||||
<QCardActions class="flex justify-end q-mb-sm">
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
color="primary"
|
||||
flat
|
||||
v-close-popup
|
||||
<QPopupEdit
|
||||
v-model="row.description"
|
||||
v-slot="scope"
|
||||
@save="
|
||||
(value) =>
|
||||
value != row.description &&
|
||||
axios.patch(`Receipts/${row.id}`, { description: value })
|
||||
"
|
||||
auto-save
|
||||
>
|
||||
<VnInput
|
||||
v-model="scope.value"
|
||||
:disable="!hasAny(['administrative'])"
|
||||
@keypress.enter="scope.set"
|
||||
autofocus
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.save')"
|
||||
@click="sendEmailAction"
|
||||
class="q-ml-sm"
|
||||
color="primary"
|
||||
/>
|
||||
</QCardActions>
|
||||
</QCard>
|
||||
</QDialog>
|
||||
</QTd>
|
||||
</QPopupEdit>
|
||||
</template>
|
||||
</QTable>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
|
||||
<QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer">
|
||||
<div class="q-mt-xl q-px-md">
|
||||
<VnSelect
|
||||
:label="t('Company')"
|
||||
:options="companiesOptions"
|
||||
@update:model-value="updateCompanyId($event)"
|
||||
hide-selected
|
||||
option-label="code"
|
||||
option-value="id"
|
||||
v-model="companyId"
|
||||
:rules="validate('entry.companyFk')"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<QCard class="q-ma-md q-pa-md q-mt-lg" v-if="receiptsData?.length">
|
||||
<QCardSection>
|
||||
<div class="flex justify-center text-subtitle1 text-bold">
|
||||
{{ t('Total by company') }}
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<div class="q-mr-sm" v-if="clientRisks?.length">
|
||||
{{ clientRisks[0].company.code }}:
|
||||
</div>
|
||||
<div v-if="clientRisks?.length">
|
||||
{{ toCurrency(clientRisks[0].amount) }}
|
||||
</div>
|
||||
</div>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</QDrawer>
|
||||
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
</VnTable>
|
||||
<QPageSticky :offset="[18, 18]" style="z-index: 2">
|
||||
<QBtn @click.stop="showNewPaymentDialog()" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ t('New payment') }}
|
||||
|
@ -393,3 +319,12 @@ es:
|
|||
Send compensation: Enviar compensación
|
||||
Do you want to report compensation to the client by mail?: ¿Desea informar de la compensación al cliente por correo?
|
||||
</i18n>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dotted {
|
||||
border: 1px dotted var(--vn-header-color);
|
||||
}
|
||||
.dotted:hover {
|
||||
border: 1px dotted $primary;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -7,66 +7,29 @@ import FetchData from 'components/FetchData.vue';
|
|||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnImg from 'src/components/ui/VnImg.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnAvatar from 'src/components/ui/VnAvatar.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const workers = ref([]);
|
||||
const workersCopy = ref([]);
|
||||
const businessTypes = ref([]);
|
||||
const contactChannels = ref([]);
|
||||
|
||||
function setWorkers(data) {
|
||||
workers.value = data;
|
||||
workersCopy.value = data;
|
||||
}
|
||||
|
||||
const filterOptions = {
|
||||
options: workers,
|
||||
filterFn: (options, value) => {
|
||||
const search = value.toLowerCase();
|
||||
|
||||
if (value === '') return workersCopy.value;
|
||||
|
||||
return options.value.filter((row) => {
|
||||
const id = row.id;
|
||||
const name = row.name.toLowerCase();
|
||||
|
||||
const idMatches = id === search;
|
||||
const nameMatches = name.indexOf(search) > -1;
|
||||
|
||||
return idMatches || nameMatches;
|
||||
});
|
||||
},
|
||||
};
|
||||
const title = ref();
|
||||
</script>
|
||||
<template>
|
||||
<fetch-data
|
||||
url="Workers/activeWithInheritedRole"
|
||||
:filter="{ where: { role: 'salesPerson' } }"
|
||||
@on-fetch="setWorkers"
|
||||
auto-load
|
||||
/>
|
||||
<fetch-data
|
||||
<FetchData
|
||||
url="ContactChannels"
|
||||
@on-fetch="(data) => (contactChannels = data)"
|
||||
auto-load
|
||||
/>
|
||||
<fetch-data
|
||||
<FetchData
|
||||
url="BusinessTypes"
|
||||
@on-fetch="(data) => (businessTypes = data)"
|
||||
auto-load
|
||||
/>
|
||||
<fetch-data
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (clients = data)"
|
||||
auto-load
|
||||
url="Clients"
|
||||
/>
|
||||
|
||||
<FormModel :url="`Clients/${route.params.id}`" auto-load model="customer">
|
||||
<template #form="{ data, validate, filter }">
|
||||
<template #form="{ data, validate }">
|
||||
<VnRow>
|
||||
<VnInput
|
||||
:label="t('globals.name')"
|
||||
|
@ -75,7 +38,6 @@ const filterOptions = {
|
|||
clearable
|
||||
v-model="data.name"
|
||||
/>
|
||||
|
||||
<QSelect
|
||||
:input-debounce="0"
|
||||
:label="t('customer.basicData.businessType')"
|
||||
|
@ -126,30 +88,25 @@ const filterOptions = {
|
|||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QSelect
|
||||
:input-debounce="0"
|
||||
:label="t('customer.basicData.salesPerson')"
|
||||
:options="workers"
|
||||
:rules="validate('client.salesPersonFk')"
|
||||
@filter="(value, update) => filter(value, update, filterOptions)"
|
||||
emit-value
|
||||
map-options
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
use-input
|
||||
<VnSelect
|
||||
url="Workers/activeWithInheritedRole"
|
||||
:filter="{ where: { role: 'salesPerson' } }"
|
||||
option-filter="firstName"
|
||||
v-model="data.salesPersonFk"
|
||||
:label="t('customer.basicData.salesPerson')"
|
||||
:rules="validate('client.salesPersonFk')"
|
||||
:use-like="false"
|
||||
:emit-value="false"
|
||||
@update:model-value="(val) => (title = val?.nickname)"
|
||||
>
|
||||
<template #prepend>
|
||||
<QAvatar color="orange">
|
||||
<VnImg
|
||||
v-if="data.salesPersonFk"
|
||||
:id="user.id"
|
||||
collection="user"
|
||||
spinner-color="white"
|
||||
<VnAvatar
|
||||
:worker-id="data.salesPersonFk"
|
||||
color="primary"
|
||||
:title="title"
|
||||
/>
|
||||
</QAvatar>
|
||||
</template>
|
||||
</QSelect>
|
||||
</VnSelect>
|
||||
<QSelect
|
||||
v-model="data.contactChannelFk"
|
||||
:options="contactChannels"
|
||||
|
|
|
@ -10,8 +10,10 @@ import CustomerFilter from '../CustomerFilter.vue';
|
|||
:descriptor="CustomerDescriptor"
|
||||
:filter-panel="CustomerFilter"
|
||||
search-data-key="CustomerList"
|
||||
search-url="Clients/extendedListFilter"
|
||||
searchbar-label="Search customer"
|
||||
searchbar-info="You can search by customer id or name"
|
||||
:searchbar-props="{
|
||||
url: 'Clients/extendedListFilter',
|
||||
label: 'Search customer',
|
||||
info: 'You can search by customer id or name',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { QBtn } from 'quasar';
|
||||
import { toCurrency, toDateHourMin } from 'src/filters';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
||||
|
@ -10,7 +9,8 @@ import VnUserLink from 'src/components/ui/VnUserLink.vue';
|
|||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
||||
const filter = {
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
include: [
|
||||
{
|
||||
relation: 'worker',
|
||||
|
@ -21,10 +21,11 @@ const filter = {
|
|||
},
|
||||
],
|
||||
where: { clientFk: +route.params.id },
|
||||
order: ['created DESC'],
|
||||
limit: 20,
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
const tableRef = ref();
|
||||
const tableData = ref([]);
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
|
@ -43,39 +44,44 @@ const columns = computed(() => [
|
|||
name: 'amount',
|
||||
format: ({ amount }) => toCurrency(amount),
|
||||
},
|
||||
{
|
||||
label: t('Credit'),
|
||||
name: 'credit',
|
||||
create: true,
|
||||
visible: false,
|
||||
attrs: {
|
||||
autofocus: true,
|
||||
},
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
<template>
|
||||
<!-- Column titles are missing -->
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="ClientCredit"
|
||||
url="ClientCredits"
|
||||
search-url="credits"
|
||||
:filter="filter"
|
||||
:order="['created DESC']"
|
||||
:columns="columns"
|
||||
default-mode="table"
|
||||
auto-load
|
||||
:right-search="false"
|
||||
:is-editable="false"
|
||||
:use-model="true"
|
||||
:column-search="false"
|
||||
:disable-option="{ card: true }"
|
||||
@on-fetch="(data) => (tableData = data)"
|
||||
:create="{
|
||||
urlUpdate: `Clients/${route.params.id}`,
|
||||
title: t('New credit'),
|
||||
onDataSaved: () => tableRef.reload(),
|
||||
formInitialData: { credit: tableData.at(0)?.amount },
|
||||
}"
|
||||
>
|
||||
<template #column-employee="{ row }">
|
||||
<VnUserLink :name="row?.worker?.user?.name" :worker-id="row.worker?.id" />
|
||||
</template>
|
||||
</VnTable>
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn
|
||||
@click.stop="$router.push({ name: 'CustomerCreditCreate' })"
|
||||
color="primary"
|
||||
fab
|
||||
icon="add"
|
||||
/>
|
||||
<QTooltip>
|
||||
{{ t('New credit') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
|
|
|
@ -96,15 +96,12 @@ function handleLocation(data, location) {
|
|||
:roles-allowed-to-create="['deliveryAssistant']"
|
||||
v-model="data.postcode"
|
||||
@update:model-value="(location) => handleLocation(data, location)"
|
||||
>
|
||||
</VnLocation>
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<QCheckbox :label="t('Active')" v-model="data.isActive" />
|
||||
<QCheckbox :label="t('Frozen')" v-model="data.isFreezed" />
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<QCheckbox :label="t('Has to invoice')" v-model="data.hasToInvoice" />
|
||||
<div>
|
||||
|
|
|
@ -2,20 +2,17 @@
|
|||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { QBtn } from 'quasar';
|
||||
import { useStateStore } from 'src/stores/useStateStore';
|
||||
import { toCurrency } from 'src/filters';
|
||||
import { toDateTimeFormat } from 'src/filters/date';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const stateStore = computed(() => useStateStore());
|
||||
const rows = ref([]);
|
||||
const totalAmount = ref();
|
||||
|
||||
const filter = {
|
||||
const tableRef = ref();
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
include: [
|
||||
{
|
||||
relation: 'greugeType',
|
||||
|
@ -30,73 +27,61 @@ const filter = {
|
|||
},
|
||||
},
|
||||
],
|
||||
order: 'shipped DESC, amount',
|
||||
where: {
|
||||
clientFk: `${route.params.id}`,
|
||||
clientFk: route.params.id,
|
||||
},
|
||||
limit: 20,
|
||||
};
|
||||
|
||||
const tableColumnComponents = {
|
||||
date: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
createdBy: {
|
||||
component: QBtn,
|
||||
props: () => ({ flat: true, color: 'blue', noCaps: true }),
|
||||
event: () => {},
|
||||
},
|
||||
comment: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
type: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
amount: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
field: 'shipped',
|
||||
label: t('Date'),
|
||||
name: 'date',
|
||||
format: (value) => toDateTimeFormat(value),
|
||||
name: 'shipped',
|
||||
format: ({ shipped }) => toDateTimeFormat(shipped),
|
||||
create: true,
|
||||
columnCreate: {
|
||||
component: 'date',
|
||||
autofocus: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: (value) => value?.user?.name,
|
||||
name: 'userFk',
|
||||
label: t('Created by'),
|
||||
name: 'createdBy',
|
||||
component: 'userLink',
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
defaultName: true,
|
||||
workerId: row.user?.id,
|
||||
name: row.user?.name,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'description',
|
||||
name: 'description',
|
||||
label: t('Comment'),
|
||||
name: 'comment',
|
||||
create: true,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: (value) => value?.greugeType?.name,
|
||||
name: 'greugeTypeFk',
|
||||
format: ({ greugeType }) => greugeType?.name,
|
||||
label: t('Type'),
|
||||
name: 'type',
|
||||
create: true,
|
||||
columnCreate: {
|
||||
component: 'select',
|
||||
url: 'greugeTypes',
|
||||
limit: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'amount',
|
||||
label: t('Amount'),
|
||||
name: 'amount',
|
||||
format: (value) => toCurrency(value),
|
||||
label: t('Amount'),
|
||||
format: ({ amount }) => toCurrency(amount),
|
||||
create: true,
|
||||
},
|
||||
]);
|
||||
|
||||
|
@ -107,60 +92,33 @@ const setRows = (data) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData :filter="filter" @on-fetch="setRows" auto-load url="greuges" />
|
||||
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="300" show-if-above>
|
||||
<QCard class="full-width q-pa-sm">
|
||||
<h6 class="flex justify-end q-my-lg q-pr-lg" v-if="totalAmount !== undefined">
|
||||
<span class="color-vn-label q-mr-md">{{ t('Total') }}:</span>
|
||||
{{ toCurrency(totalAmount) }}
|
||||
</h6>
|
||||
<QSkeleton v-else type="QInput" square />
|
||||
</QCard>
|
||||
</QDrawer>
|
||||
<div class="full-width flex justify-center">
|
||||
<QPage class="card-width q-pa-lg">
|
||||
<QCard class="q-pa-sm q-mt-md">
|
||||
<QTable
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="Greuges"
|
||||
url="Greuges"
|
||||
search-url="greuges"
|
||||
:filter="filter"
|
||||
:order="['shipped DESC', 'amount']"
|
||||
:columns="columns"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
:pagination="{ rowsPerPage: 12 }"
|
||||
:rows="rows"
|
||||
class="full-width q-mt-md"
|
||||
row-key="id"
|
||||
:right-search="false"
|
||||
:is-editable="false"
|
||||
:use-model="true"
|
||||
:column-search="false"
|
||||
@on-fetch="(data) => setRows(data)"
|
||||
:create="{
|
||||
urlCreate: `Greuges`,
|
||||
title: t('New credit'),
|
||||
onDataSaved: () => tableRef.reload(),
|
||||
formInitialData: { shipped: new Date(), clientFk: route.params.id },
|
||||
}"
|
||||
auto-load
|
||||
>
|
||||
<template #body-cell="props">
|
||||
<QTd :props="props">
|
||||
<QTr :props="props" class="cursor-pointer">
|
||||
<component
|
||||
:is="tableColumnComponents[props.col.name].component"
|
||||
class="col-content"
|
||||
v-bind="
|
||||
tableColumnComponents[props.col.name].props(props)
|
||||
"
|
||||
@click="
|
||||
tableColumnComponents[props.col.name].event(props)
|
||||
"
|
||||
>
|
||||
{{ props.value }}
|
||||
<WorkerDescriptorProxy
|
||||
:id="props.row.userFk"
|
||||
v-if="props.col.name === 'createdBy'"
|
||||
/>
|
||||
</component>
|
||||
</QTr>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
<template #top-left>
|
||||
<QCard class="q-px-md q-py-sm">
|
||||
{{ t('Total') }}: {{ toCurrency(totalAmount) }}
|
||||
</QCard>
|
||||
</QPage>
|
||||
</div>
|
||||
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn color="primary" fab icon="add" :to="{ name: 'CustomerGreugeCreate' }" />
|
||||
<QTooltip>
|
||||
{{ t('New greuge') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
</template>
|
||||
</VnTable>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -1,262 +1,6 @@
|
|||
<script setup>
|
||||
import { onBeforeMount, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
|
||||
const clientLogs = ref(null);
|
||||
const urlClientLogsEditors = ref(null);
|
||||
const urlClientLogsModels = ref(null);
|
||||
const clientLogsModelsOptions = ref([]);
|
||||
const clientLogsOptions = ref([]);
|
||||
const clientLogsEditorsOptions = ref([]);
|
||||
const radioButtonValue = ref('all');
|
||||
const insert = ref(false);
|
||||
const update = ref(false);
|
||||
const deletes = ref(false);
|
||||
const select = ref(false);
|
||||
const neq = ref(null);
|
||||
const inq = ref([]);
|
||||
|
||||
const filterClientLogs = {
|
||||
fields: [
|
||||
'id',
|
||||
'originFk',
|
||||
'userFk',
|
||||
'action',
|
||||
'changedModel',
|
||||
'oldInstance',
|
||||
'newInstance',
|
||||
'creationDate',
|
||||
'changedModel',
|
||||
'changedModelId',
|
||||
'changedModelValue',
|
||||
'description',
|
||||
],
|
||||
include: [
|
||||
{
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['nickname', 'name', 'image'],
|
||||
include: { relation: 'worker', scope: { fields: ['id'] } },
|
||||
},
|
||||
},
|
||||
],
|
||||
order: ['creationDate DESC', 'id DESC'],
|
||||
limit: 20,
|
||||
};
|
||||
const filterClientLogsEditors = {
|
||||
fields: ['id', 'nickname', 'name', 'image'],
|
||||
order: 'nickname',
|
||||
limit: 30,
|
||||
};
|
||||
const filterClientLogsModels = { order: ['changedModel'] };
|
||||
const urlBase = `ClientLogs/${route.params.id}`;
|
||||
|
||||
onBeforeMount(() => {
|
||||
stateStore.rightDrawer = true;
|
||||
filterClientLogs.where = {
|
||||
and: [
|
||||
{ originFk: `${route.params.id}` },
|
||||
{ userFk: { neq: radioButtonValue.value } },
|
||||
{ action: { inq: inq.value } },
|
||||
],
|
||||
};
|
||||
urlClientLogsEditors.value = `${urlBase}/editors`;
|
||||
urlClientLogsModels.value = `${urlBase}/models`;
|
||||
});
|
||||
|
||||
const getClientLogs = async (value, status) => {
|
||||
if (status === 'neq') {
|
||||
neq.value = value;
|
||||
} else {
|
||||
setInq(value, status);
|
||||
}
|
||||
filterClientLogs.where = {
|
||||
and: [
|
||||
{ originFk: `${route.params.id}` },
|
||||
{ userFk: { neq: neq.value } },
|
||||
{ action: { inq: inq.value } },
|
||||
],
|
||||
};
|
||||
clientLogs.value?.fetch();
|
||||
};
|
||||
|
||||
const setInq = (value, status) => {
|
||||
if (status) {
|
||||
if (!inq.value.includes(value)) {
|
||||
inq.value.push(value);
|
||||
}
|
||||
} else {
|
||||
inq.value = inq.value.filter((item) => item !== value);
|
||||
}
|
||||
};
|
||||
import VnLog from 'src/components/common/VnLog.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:filter="filterClientLogs"
|
||||
@on-fetch="(data) => (clientLogsOptions = data)"
|
||||
auto-load
|
||||
url="ClientLogs"
|
||||
ref="clientLogs"
|
||||
/>
|
||||
<FetchData
|
||||
:filter="filterClientLogsEditors"
|
||||
@on-fetch="(data) => (clientLogsEditorsOptions = data)"
|
||||
auto-load
|
||||
:url="urlClientLogsEditors"
|
||||
/>
|
||||
<FetchData
|
||||
:filter="filterClientLogsModels"
|
||||
@on-fetch="(data) => (clientLogsModelsOptions = data)"
|
||||
auto-load
|
||||
:url="urlClientLogsModels"
|
||||
/>
|
||||
|
||||
<h5 class="flex justify-center color-vn-label">
|
||||
{{ t('globals.noResults') }}
|
||||
</h5>
|
||||
|
||||
<QDrawer :width="256" show-if-above side="right" v-model="stateStore.rightDrawer">
|
||||
<div class="q-mt-sm q-px-md">
|
||||
<VnInput :label="t('Search')" clearable>
|
||||
<template #append>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>
|
||||
{{ t('Search by id or concept') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
<VnSelect
|
||||
:label="t('Entity')"
|
||||
:options="[]"
|
||||
class="q-mt-md"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
|
||||
<div class="q-mt-lg">
|
||||
<QRadio
|
||||
:dark="true"
|
||||
:label="t('All')"
|
||||
@update:model-value="getClientLogs($event, 'neq')"
|
||||
dense
|
||||
v-model="radioButtonValue"
|
||||
val="all"
|
||||
/>
|
||||
</div>
|
||||
<div class="q-mt-md">
|
||||
<QRadio
|
||||
:dark="true"
|
||||
:label="t('User')"
|
||||
@update:model-value="getClientLogs($event, 'neq')"
|
||||
dense
|
||||
v-model="radioButtonValue"
|
||||
val="user"
|
||||
/>
|
||||
</div>
|
||||
<div class="q-mt-md">
|
||||
<QRadio
|
||||
:dark="true"
|
||||
:label="t('System')"
|
||||
@update:model-value="getClientLogs($event, 'neq')"
|
||||
dense
|
||||
v-model="radioButtonValue"
|
||||
val="system"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VnSelect
|
||||
:label="t('User')"
|
||||
:options="[]"
|
||||
class="q-mt-sm"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
/>
|
||||
<VnInput :label="t('Changes')" clearable class="q-mt-sm">
|
||||
<template #append>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>
|
||||
{{ t('Search by changes') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</template>
|
||||
</VnInput>
|
||||
|
||||
<div class="q-mt-md">
|
||||
<QCheckbox
|
||||
:label="t('Creates')"
|
||||
@update:model-value="getClientLogs('insert', $event)"
|
||||
v-model="insert"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<QCheckbox
|
||||
:label="t('Edits')"
|
||||
@update:model-value="getClientLogs('update', $event)"
|
||||
v-model="update"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<QCheckbox
|
||||
:label="t('Deletes')"
|
||||
@update:model-value="getClientLogs('delete', $event)"
|
||||
v-model="deletes"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<QCheckbox
|
||||
:label="t('Accesses')"
|
||||
@update:model-value="getClientLogs('select', $event)"
|
||||
v-model="select"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<VnInputDate :label="t('Date')" class="q-mt-sm" />
|
||||
<VnInput :label="t('To')" clearable class="q-mt-md" />
|
||||
</div>
|
||||
</QDrawer>
|
||||
|
||||
<QPageSticky
|
||||
:offset="[18, 18]"
|
||||
v-if="radioButtonValue !== 'all' || insert || update || deletes || select"
|
||||
>
|
||||
<QBtn color="primary" fab icon="filter_alt_off" />
|
||||
<QTooltip>
|
||||
{{ t('Quit filter') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
<VnLog model="Client" />
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Search: Buscar
|
||||
Search by id or concept: xxx
|
||||
Entity: Entidad
|
||||
All: Todo
|
||||
User: Usuario
|
||||
System: Sistema
|
||||
Changes: Cambios
|
||||
Search by changes: xxx
|
||||
Creates: Crea
|
||||
Edits: Modifica
|
||||
Deletes: Elimina
|
||||
Accesses: Accede
|
||||
Date: Fecha
|
||||
To: Hasta
|
||||
Quit filter: Quitar filtro
|
||||
</i18n>
|
||||
|
|
|
@ -1,83 +1,26 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnNotes from 'src/components/ui/VnNotes.vue';
|
||||
|
||||
import { toDateTimeFormat } from 'src/filters/date';
|
||||
|
||||
import VnPaginate from 'src/components/ui/VnPaginate.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const noteFilter = {
|
||||
const noteFilter = computed(() => {
|
||||
return {
|
||||
order: 'created DESC',
|
||||
where: {
|
||||
clientFk: `${route.params.id}`,
|
||||
},
|
||||
};
|
||||
|
||||
const toCustomerNoteCreate = () => {
|
||||
router.push({ name: 'CustomerNoteCreate' });
|
||||
};
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="full-width flex justify-center">
|
||||
<QCard class="card-width q-pa-lg">
|
||||
<VnPaginate
|
||||
data-key="CustomerNotes"
|
||||
<VnNotes
|
||||
url="clientObservations"
|
||||
auto-load
|
||||
:add-note="true"
|
||||
:filter="noteFilter"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<div v-if="rows.length">
|
||||
<QCard
|
||||
v-for="(item, index) in rows"
|
||||
:key="index"
|
||||
class="q-pa-md q-rounded custom-border"
|
||||
:class="{ 'q-mb-md': index < rows.length - 1 }"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<p class="color-vn-label">
|
||||
{{ item.worker.user.nickname }}
|
||||
</p>
|
||||
<p class="color-vn-label">
|
||||
{{ toDateTimeFormat(item?.created) }}
|
||||
</p>
|
||||
</div>
|
||||
<h6 class="q-mt-xs q-mb-none">{{ item.text }}</h6>
|
||||
</QCard>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<h5 class="flex justify-center color-vn-label">
|
||||
{{ t('globals.noResults') }}
|
||||
</h5>
|
||||
</div>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</QCard>
|
||||
</div>
|
||||
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn @click.stop="toCustomerNoteCreate()" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ t('New note') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
:body="{ clientFk: route.params.id }"
|
||||
style="overflow-y: auto"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.custom-border {
|
||||
border: 2px solid var(--vn-accent-color);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
New note: Nueva nota
|
||||
</i18n>
|
||||
|
|
|
@ -1,146 +1,107 @@
|
|||
<script setup>
|
||||
import axios from 'axios';
|
||||
import { ref, computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { useRoute } from 'vue-router';
|
||||
import { toCurrency, toDate } from 'src/filters';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnTable from 'components/VnTable/VnTable.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const rows = ref([]);
|
||||
|
||||
const filter = {
|
||||
const tableRef = ref();
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
where: { clientFk: route.params.id },
|
||||
order: ['started DESC'],
|
||||
limit: 20,
|
||||
};
|
||||
});
|
||||
const componentColumn = (type) => {
|
||||
return {
|
||||
columnFilter: {
|
||||
component: type,
|
||||
},
|
||||
columnCreate: {
|
||||
component: type,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const tableColumnComponents = {
|
||||
since: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
to: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
amount: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
period: {
|
||||
component: 'span',
|
||||
props: () => {},
|
||||
event: () => {},
|
||||
},
|
||||
};
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
field: 'started',
|
||||
label: t('Since'),
|
||||
name: 'since',
|
||||
format: (value) => toDate(value),
|
||||
label: t('globals.since'),
|
||||
name: 'started',
|
||||
format: ({ started }) => toDate(started),
|
||||
create: true,
|
||||
...componentColumn('date'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'finished',
|
||||
label: t('To'),
|
||||
name: 'to',
|
||||
format: (value) => toDate(value),
|
||||
name: 'finished',
|
||||
label: t('globals.to'),
|
||||
format: ({ finished }) => toDate(finished),
|
||||
create: true,
|
||||
...componentColumn('date'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'amount',
|
||||
label: t('Amount'),
|
||||
name: 'amount',
|
||||
format: (value) => toCurrency(value),
|
||||
label: t('globals.amount'),
|
||||
format: ({ amount }) => toCurrency(amount),
|
||||
create: true,
|
||||
...componentColumn('number'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
field: 'period',
|
||||
label: t('Period'),
|
||||
name: 'period',
|
||||
label: t('Period'),
|
||||
create: true,
|
||||
...componentColumn('number'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('Finish that recovery period'),
|
||||
icon: 'lock',
|
||||
show: (row) => !row.finished,
|
||||
action: ({ id }) => setFinished(id),
|
||||
isPrimary: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const toCustomerRecoverieCreate = () => {
|
||||
router.push({ name: 'CustomerRecoverieCreate' });
|
||||
};
|
||||
function setFinished(id) {
|
||||
axios.patch(`Recoveries/${id}`, { finished: Date.vnNow() });
|
||||
tableRef.value.reload();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:filter="filter"
|
||||
@on-fetch="(data) => (rows = data)"
|
||||
auto-load
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="Recoveries"
|
||||
url="Recoveries"
|
||||
/>
|
||||
|
||||
<div class="full-width flex justify-center">
|
||||
<QPage class="card-width q-pa-lg">
|
||||
<QTable
|
||||
search-url="recoveries"
|
||||
:filter="filter"
|
||||
order="started DESC"
|
||||
:columns="columns"
|
||||
:no-data-label="t('globals.noResults')"
|
||||
:pagination="{ rowsPerPage: 12 }"
|
||||
:rows="rows"
|
||||
class="full-width q-mt-md"
|
||||
row-key="id"
|
||||
>
|
||||
<template #body-cell="props">
|
||||
<QTd :props="props">
|
||||
<QTr :props="props" class="cursor-pointer">
|
||||
<component
|
||||
:is="tableColumnComponents[props.col.name].component"
|
||||
class="col-content"
|
||||
v-bind="
|
||||
tableColumnComponents[props.col.name].props(props)
|
||||
"
|
||||
@click="
|
||||
tableColumnComponents[props.col.name].event(props)
|
||||
"
|
||||
>
|
||||
{{ props.value }}
|
||||
</component>
|
||||
</QTr>
|
||||
</QTd>
|
||||
</template>
|
||||
</QTable>
|
||||
</QPage>
|
||||
</div>
|
||||
|
||||
<QPageSticky :offset="[18, 18]">
|
||||
<QBtn @click.stop="toCustomerRecoverieCreate()" color="primary" fab icon="add" />
|
||||
<QTooltip>
|
||||
{{ t('New recoverie') }}
|
||||
</QTooltip>
|
||||
</QPageSticky>
|
||||
:use-model="true"
|
||||
:right-search="false"
|
||||
:create="{
|
||||
urlCreate: 'Recoveries',
|
||||
title: 'New recovery',
|
||||
onDataSaved: () => tableRef.reload(),
|
||||
formInitialData: { clientFk: route.params.id, started: Date.vnNew() },
|
||||
}"
|
||||
auto-load
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
.consignees-card {
|
||||
border: 2px solid var(--vn-accent-color);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.label-color {
|
||||
color: var(--vn-label-color);
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Since: Desde
|
||||
To: Hasta
|
||||
Amount: Importe
|
||||
Period: Periodo
|
||||
New recoverie: Nuevo recobro
|
||||
New recovery: Nuevo recobro
|
||||
Finish that recovery period: Terminar recobro
|
||||
</i18n>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import VnSms from 'src/components/ui/VnSms.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const id = route.params.id;
|
||||
|
||||
const where = {
|
||||
clientFk: id,
|
||||
const where = computed(() => {
|
||||
return {
|
||||
clientFk: route.params.id,
|
||||
ticketFk: null,
|
||||
};
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="column items-center">
|
||||
<VnSms url="clientSms" :where="where" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -2,164 +2,81 @@
|
|||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import axios from 'axios';
|
||||
import { useQuasar } from 'quasar';
|
||||
|
||||
import { useValidator } from 'src/composables/useValidator';
|
||||
import useNotify from 'src/composables/useNotify';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
|
||||
const { notify } = useNotify();
|
||||
const { t } = useI18n();
|
||||
const { validate } = useValidator();
|
||||
const quasar = useQuasar();
|
||||
const route = useRoute();
|
||||
const stateStore = useStateStore();
|
||||
|
||||
const active = ref(false);
|
||||
const canChangePassword = ref(0);
|
||||
const email = ref(null);
|
||||
const isLoading = ref(false);
|
||||
const name = ref(null);
|
||||
const usersPreviewRef = ref(null);
|
||||
const user = ref([]);
|
||||
|
||||
const dataChanges = computed(() => {
|
||||
return (
|
||||
user.value.active !== active.value ||
|
||||
user.value.email !== email.value ||
|
||||
user.value.name !== name.value
|
||||
);
|
||||
const filter = computed(() => {
|
||||
return {
|
||||
fields: ['active', 'email', 'name'],
|
||||
where: { id: route.params.id },
|
||||
};
|
||||
});
|
||||
|
||||
const filter = { where: { id: `${route.params.id}` } };
|
||||
|
||||
const showChangePasswordDialog = () => {
|
||||
quasar.dialog({
|
||||
component: CustomerChangePassword,
|
||||
componentProps: {
|
||||
id: route.params.id,
|
||||
promise: usersPreviewRef.value.fetch(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setInitialData = () => {
|
||||
if (user.value.length) {
|
||||
active.value = user.value[0].active;
|
||||
email.value = user.value[0].email;
|
||||
name.value = user.value[0].name;
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async () => {
|
||||
isLoading.value = true;
|
||||
|
||||
const payload = {
|
||||
active: active.value,
|
||||
email: email.value,
|
||||
name: name.value,
|
||||
};
|
||||
try {
|
||||
await axios.patch(`Clients/${route.params.id}/updateUser`, payload);
|
||||
notify('globals.dataSaved', 'positive');
|
||||
if (usersPreviewRef.value) usersPreviewRef.value.fetch();
|
||||
} catch (error) {
|
||||
notify('errors.create', 'negative');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
async function hasCustomerRole() {
|
||||
const { data } = await axios(`Clients/${route.params.id}/hasCustomerRole`);
|
||||
canChangePassword.value = data;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
<FormModel
|
||||
url="VnUsers/preview"
|
||||
:url-update="`Clients/${route.params.id}/updateUser`"
|
||||
:filter="filter"
|
||||
@on-fetch="
|
||||
(data) => {
|
||||
user = data;
|
||||
setInitialData();
|
||||
model="webAccess"
|
||||
:mapper="
|
||||
({ active, name, email }) => {
|
||||
return {
|
||||
active,
|
||||
name,
|
||||
email,
|
||||
};
|
||||
}
|
||||
"
|
||||
@on-fetch="hasCustomerRole()"
|
||||
auto-load
|
||||
ref="usersPreviewRef"
|
||||
url="VnUsers/preview"
|
||||
/>
|
||||
<FetchData
|
||||
:url="`Clients/${route.params.id}/hasCustomerRole`"
|
||||
@on-fetch="(data) => (canChangePassword = data)"
|
||||
auto-load
|
||||
/>
|
||||
|
||||
<Teleport to="#st-actions" v-if="stateStore?.isSubToolbarShown()">
|
||||
<QBtnGroup push class="q-gutter-x-sm">
|
||||
<QBtn
|
||||
:disabled="isLoading"
|
||||
:label="t('globals.reset')"
|
||||
:loading="isLoading"
|
||||
@click="setInitialData"
|
||||
color="primary"
|
||||
flat
|
||||
icon="restart_alt"
|
||||
type="reset"
|
||||
/>
|
||||
<QBtn
|
||||
:disabled="isLoading"
|
||||
:label="t('Change password')"
|
||||
:loading="isLoading"
|
||||
@click.stop="showChangePasswordDialog()"
|
||||
color="primary"
|
||||
flat
|
||||
icon="edit"
|
||||
v-if="canChangePassword"
|
||||
/>
|
||||
<QBtn
|
||||
:disabled="isLoading || !dataChanges"
|
||||
:label="t('globals.save')"
|
||||
:loading="isLoading"
|
||||
@click="onSubmit"
|
||||
color="primary"
|
||||
icon="save"
|
||||
/>
|
||||
</QBtnGroup>
|
||||
</Teleport>
|
||||
|
||||
<div class="full-width flex justify-center">
|
||||
<QCard class="card-width q-pa-lg">
|
||||
<QCardSection>
|
||||
<QForm>
|
||||
<QCheckbox :label="t('Enable web access')" v-model="active" />
|
||||
|
||||
<div class="q-px-sm">
|
||||
<VnInput :label="t('User')" clearable v-model="name" />
|
||||
>
|
||||
<template #form="{ data, validate }">
|
||||
<QCheckbox :label="t('Enable web access')" v-model="data.active" />
|
||||
<VnInput :label="t('User')" clearable v-model="data.name" />
|
||||
<VnInput
|
||||
:label="t('Recovery email')"
|
||||
:rules="validate('client.email')"
|
||||
clearable
|
||||
type="email"
|
||||
v-model="email"
|
||||
v-model="data.email"
|
||||
class="q-mt-sm"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="info" class="cursor-pointer">
|
||||
<QTooltip>{{
|
||||
t(
|
||||
'This email is used for user to regain access their account'
|
||||
)
|
||||
}}</QTooltip>
|
||||
</QIcon>
|
||||
:info="t('This email is used for user to regain access their account')"
|
||||
/>
|
||||
</template>
|
||||
</VnInput>
|
||||
</div>
|
||||
</QForm>
|
||||
</QCardSection>
|
||||
</QCard>
|
||||
</div>
|
||||
<template #moreActions>
|
||||
<QBtn
|
||||
:label="t('Change password')"
|
||||
color="primary"
|
||||
icon="edit"
|
||||
:disable="!canChangePassword"
|
||||
@click="showChangePasswordDialog()"
|
||||
/>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
|
|
|
@ -105,9 +105,9 @@ const columns = computed(() => [
|
|||
component: null,
|
||||
after: {
|
||||
component: markRaw(VnLinkPhone),
|
||||
attrs: (prop) => {
|
||||
attrs: ({ model }) => {
|
||||
return {
|
||||
'phone-number': prop,
|
||||
'phone-number': model,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -47,7 +47,11 @@ const props = defineProps({
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnCurrency v-model="params.amount" is-outlined />
|
||||
<VnInputNumber
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const initialData = ref({});
|
||||
|
||||
const setClient = (data) => {
|
||||
initialData.value.credit = data.credit;
|
||||
};
|
||||
|
||||
const toCustomerCredits = () => {
|
||||
router.push({
|
||||
name: 'CustomerCredits',
|
||||
params: {
|
||||
id: route.params.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
:filter="filter"
|
||||
@on-fetch="setClient"
|
||||
auto-load
|
||||
:url="`Clients/${route.params.id}/getCard`"
|
||||
/>
|
||||
|
||||
<FormModel
|
||||
:form-initial-data="initialData"
|
||||
:observe-form-changes="false"
|
||||
:url-update="`/Clients/${route.params.id}`"
|
||||
@on-data-saved="toCustomerCredits()"
|
||||
>
|
||||
<template #moreActions>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
@click="toCustomerCredits"
|
||||
color="primary"
|
||||
flat
|
||||
icon="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #form="{ data }">
|
||||
<QInput
|
||||
:label="t('Credit')"
|
||||
clearable
|
||||
type="number"
|
||||
v-model.number="data.credit"
|
||||
/>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Credit: Crédito
|
||||
</i18n>
|
|
@ -1,97 +0,0 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import FetchData from 'components/FetchData.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';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const initialData = reactive({
|
||||
shipped: '2001-01-01T11:00:00.000Z',
|
||||
});
|
||||
|
||||
const greugeTypes = ref([]);
|
||||
|
||||
onMounted(() => {
|
||||
initialData.clientFk = `${route.params.id}`;
|
||||
});
|
||||
|
||||
const toCustomerGreuges = () => {
|
||||
router.push({
|
||||
name: 'CustomerGreuges',
|
||||
params: {
|
||||
id: route.params.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<fetch-data @on-fetch="(data) => (greugeTypes = data)" auto-load url="greugeTypes" />
|
||||
|
||||
<FormModel
|
||||
:form-initial-data="initialData"
|
||||
:observe-form-changes="false"
|
||||
@on-data-saved="toCustomerGreuges()"
|
||||
model="client"
|
||||
url-create="Greuges"
|
||||
>
|
||||
<template #moreActions>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
@click="toCustomerGreuges"
|
||||
color="primary"
|
||||
flat
|
||||
icon="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnInput
|
||||
:label="t('Amount')"
|
||||
clearable
|
||||
type="number"
|
||||
v-model="data.amount"
|
||||
/>
|
||||
<VnInputDate :label="t('Date')" v-model="data.shipped" />
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<VnInput :label="t('Comment')" clearable v-model="data.description" />
|
||||
<VnSelect
|
||||
:label="t('Type')"
|
||||
:options="greugeTypes"
|
||||
hide-selected
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
v-model="data.greugeTypeFk"
|
||||
/>
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.add-icon {
|
||||
cursor: pointer;
|
||||
background-color: $primary;
|
||||
border-radius: 50px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Amount: Importe
|
||||
Date: Fecha
|
||||
Comment: Comentario
|
||||
Type: Tipo
|
||||
</i18n>
|
|
@ -2,21 +2,25 @@
|
|||
import { onBeforeMount, reactive, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import axios from 'axios';
|
||||
|
||||
import { useDialogPluginComponent } from 'quasar';
|
||||
|
||||
import { usePrintService } from 'composables/usePrintService';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import FormModel from 'components/FormModel.vue';
|
||||
import VnRow from 'components/ui/VnRow.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnInputNumber from 'components/common/VnInputNumber.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import { useState } from 'src/composables/useState';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const { notify } = useNotify();
|
||||
const { sendEmail, openReport } = usePrintService();
|
||||
const { dialogRef } = useDialogPluginComponent();
|
||||
const state = useState();
|
||||
const user = state.getUser();
|
||||
|
||||
const $props = defineProps({
|
||||
companyId: {
|
||||
|
@ -29,7 +33,7 @@ const $props = defineProps({
|
|||
},
|
||||
promise: {
|
||||
type: Function,
|
||||
required: true,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -38,11 +42,12 @@ const urlCreate = ref([]);
|
|||
const companyOptions = ref([]);
|
||||
const bankOptions = ref([]);
|
||||
const clientFindOne = ref([]);
|
||||
const deliveredAmount = ref(null);
|
||||
const amountToReturn = ref(null);
|
||||
const viewRecipt = ref(true);
|
||||
const sendEmail = ref(false);
|
||||
const isLoading = ref(false);
|
||||
const viewReceipt = ref();
|
||||
const shouldSendEmail = ref(false);
|
||||
const maxAmount = ref();
|
||||
const accountingType = ref({});
|
||||
const isCash = ref(false);
|
||||
const formModelRef = ref(false);
|
||||
|
||||
const filterBanks = {
|
||||
fields: ['id', 'bank', 'accountingTypeFk'],
|
||||
|
@ -57,77 +62,135 @@ const filterClientFindOne = {
|
|||
id: route.params.id,
|
||||
},
|
||||
};
|
||||
|
||||
const initialData = reactive({
|
||||
amountPaid: $props.totalCredit,
|
||||
clientFk: route.params.id,
|
||||
companyFk: $props.companyId,
|
||||
email: clientFindOne.value.email,
|
||||
bankFk: user.value.localBankFk,
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
urlCreate.value = `Clients/${route.params.id}/createReceipt`;
|
||||
});
|
||||
|
||||
const setPaymentType = (id) => {
|
||||
initialData.payed = '2001-01-01T11:00:00.000Z';
|
||||
if (id === 1) initialData.description = 'Credit card';
|
||||
if (id === 2) initialData.description = 'Cash';
|
||||
if (id === 3 || id === 3117) initialData.description = '';
|
||||
if (id === 4) initialData.description = 'Transfer';
|
||||
};
|
||||
function setPaymentType(accounting) {
|
||||
if (!accounting) return;
|
||||
accountingType.value = accounting.accountingType;
|
||||
|
||||
initialData.description = [];
|
||||
initialData.payed = Date.vnNew();
|
||||
isCash.value = accountingType.value.code == 'cash';
|
||||
viewReceipt.value = isCash.value;
|
||||
if (accountingType.value.daysInFuture)
|
||||
initialData.payed.setDate(
|
||||
initialData.payed.getDate() + accountingType.value.daysInFuture
|
||||
);
|
||||
maxAmount.value = accountingType.value && accountingType.value.maxAmount;
|
||||
|
||||
if (accountingType.value.code == 'compensation')
|
||||
return (initialData.description = '');
|
||||
if (accountingType.value.receiptDescription)
|
||||
initialData.description.push(accountingType.value.receiptDescription);
|
||||
if (initialData.description) initialData.description.push(initialData.description);
|
||||
|
||||
initialData.description = initialData.description.join(', ');
|
||||
}
|
||||
|
||||
const calculateFromAmount = (event) => {
|
||||
amountToReturn.value = parseFloat(event) * -1 + parseFloat(deliveredAmount.value);
|
||||
initialData.amountToReturn =
|
||||
parseFloat(initialData.deliveredAmount) + parseFloat(event) * -1;
|
||||
};
|
||||
|
||||
const calculateFromDeliveredAmount = (event) => {
|
||||
amountToReturn.value = parseFloat($props.totalCredit) * -1 + parseFloat(event);
|
||||
initialData.amountToReturn = parseFloat(event) - initialData.amountPaid;
|
||||
};
|
||||
|
||||
const setClientEmail = (data) => {
|
||||
initialData.email = data.email;
|
||||
};
|
||||
function onBeforeSave(data) {
|
||||
const exceededAmount = data.amountPaid > maxAmount.value;
|
||||
if (isCash.value && exceededAmount)
|
||||
return notify(t('Amount exceeded', { maxAmount: maxAmount.value }), 'negative');
|
||||
|
||||
const onDataSaved = async () => {
|
||||
isLoading.value = true;
|
||||
if ($props.promise) {
|
||||
if (isCash.value && shouldSendEmail.value && !data.email)
|
||||
return notify(t('There is no assigned email for this client'), 'negative');
|
||||
|
||||
data.bankFk = data.bankFk.id;
|
||||
return data;
|
||||
}
|
||||
|
||||
async function onDataSaved(formData, { id }) {
|
||||
try {
|
||||
await $props.promise();
|
||||
if (shouldSendEmail.value && isCash.value)
|
||||
await sendEmail(`Receipts/${id}/receipt-email`, {
|
||||
recipient: formData.email,
|
||||
});
|
||||
|
||||
if (viewReceipt.value) openReport(`Receipts/${id}/receipt-pdf`);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
if ($props.promise) $props.promise();
|
||||
if (closeButton.value) closeButton.value.click();
|
||||
}
|
||||
}
|
||||
|
||||
async function accountShortToStandard({ target: { value } }) {
|
||||
if (!value) return (initialData.description = '');
|
||||
initialData.compensationAccount = value.replace('.', '0'.repeat(11 - value.length));
|
||||
const params = { bankAccount: initialData.compensationAccount };
|
||||
const { data } = await axios(`Clients/getClientOrSupplierReference`, { params });
|
||||
if (!data.clientId) {
|
||||
initialData.description = t('Supplier Compensation Reference', {
|
||||
supplierId: data.supplierId,
|
||||
supplierName: data.supplierName,
|
||||
});
|
||||
return;
|
||||
}
|
||||
};
|
||||
initialData.description = t('Client Compensation Reference', {
|
||||
clientId: data.clientId,
|
||||
clientName: data.clientName,
|
||||
});
|
||||
}
|
||||
|
||||
async function getAmountPaid() {
|
||||
const filter = {
|
||||
where: {
|
||||
clientFk: route.params.id,
|
||||
companyFk: initialData.companyFk,
|
||||
},
|
||||
};
|
||||
|
||||
const { data } = await axios(`ClientRisks`, {
|
||||
params: { filter: JSON.stringify(filter) },
|
||||
});
|
||||
initialData.amountPaid = (data?.length && data[0].amount) || undefined;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QDialog ref="dialogRef">
|
||||
<fetch-data
|
||||
<QDialog ref="dialogRef" persistent>
|
||||
<FetchData
|
||||
@on-fetch="(data) => (companyOptions = data)"
|
||||
auto-load
|
||||
url="Companies"
|
||||
/>
|
||||
<fetch-data
|
||||
<FetchData
|
||||
:filter="filterBanks"
|
||||
@on-fetch="(data) => (bankOptions = data)"
|
||||
auto-load
|
||||
url="Accountings"
|
||||
/>
|
||||
<fetch-data
|
||||
<FetchData
|
||||
:filter="filterClientFindOne"
|
||||
@on-fetch="setClientEmail"
|
||||
@on-fetch="({ email }) => (initialData.email = email)"
|
||||
auto-load
|
||||
url="Clients/findOne"
|
||||
/>
|
||||
|
||||
<FormModel
|
||||
:default-actions="false"
|
||||
ref="formModelRef"
|
||||
:form-initial-data="initialData"
|
||||
:observe-form-changes="false"
|
||||
:url-create="urlCreate"
|
||||
@on-data-saved="onDataSaved()"
|
||||
:mapper="onBeforeSave"
|
||||
@on-data-saved="onDataSaved"
|
||||
>
|
||||
<template #form="{ data, validate }">
|
||||
<span ref="closeButton" class="row justify-end close-icon" v-close-popup>
|
||||
|
@ -151,19 +214,22 @@ const onDataSaved = async () => {
|
|||
option-label="code"
|
||||
option-value="id"
|
||||
v-model="data.companyFk"
|
||||
@update:model-value="getAmountPaid()"
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('Bank')"
|
||||
:options="bankOptions"
|
||||
:required="true"
|
||||
@update:model-value="setPaymentType($event)"
|
||||
hide-selected
|
||||
option-label="bank"
|
||||
option-value="id"
|
||||
v-model="data.bankFk"
|
||||
url="Accountings"
|
||||
option-label="bank"
|
||||
:include="{ relation: 'accountingType' }"
|
||||
sort-by="id"
|
||||
:limit="0"
|
||||
@update:model-value="
|
||||
(value, options) => setPaymentType(value, options)
|
||||
"
|
||||
:emit-value="false"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
|
@ -180,22 +246,21 @@ const onDataSaved = async () => {
|
|||
:required="true"
|
||||
@update:model-value="calculateFromAmount($event)"
|
||||
clearable
|
||||
type="number"
|
||||
v-model.number="data.amountPaid"
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<div class="text-h6" v-if="data.bankFk === 3 || data.bankFk === 3117">
|
||||
<div v-if="data.bankFk?.accountingType?.code == 'compensation'">
|
||||
<div class="text-h6">
|
||||
{{ t('Compensation') }}
|
||||
</div>
|
||||
|
||||
<VnRow>
|
||||
<div class="col" v-if="data.bankFk === 3 || data.bankFk === 3117">
|
||||
<VnInput
|
||||
:label="t('Compensation account')"
|
||||
clearable
|
||||
v-model="data.compensationAccount"
|
||||
@blur="accountShortToStandard"
|
||||
/>
|
||||
</VnRow>
|
||||
</div>
|
||||
<VnInput
|
||||
:label="t('Reference')"
|
||||
|
@ -203,39 +268,32 @@ const onDataSaved = async () => {
|
|||
clearable
|
||||
v-model="data.description"
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<div class="q-mt-lg" v-if="data.bankFk === 2">
|
||||
<div v-if="data.bankFk?.accountingType?.code == 'cash'">
|
||||
<div class="text-h6">{{ t('Cash') }}</div>
|
||||
|
||||
<VnRow>
|
||||
<VnInput
|
||||
<VnInputNumber
|
||||
:label="t('Delivered amount')"
|
||||
@update:model-value="calculateFromDeliveredAmount($event)"
|
||||
clearable
|
||||
type="number"
|
||||
v-model="deliveredAmount"
|
||||
v-model="data.deliveredAmount"
|
||||
/>
|
||||
<VnInput
|
||||
<VnInputNumber
|
||||
:label="t('Amount to return')"
|
||||
clearable
|
||||
disable
|
||||
type="number"
|
||||
v-model="amountToReturn"
|
||||
v-model="data.amountToReturn"
|
||||
/>
|
||||
</VnRow>
|
||||
|
||||
<VnRow>
|
||||
<QCheckbox v-model="viewRecipt" />
|
||||
<QCheckbox v-model="sendEmail" />
|
||||
<QCheckbox v-model="viewReceipt" :label="t('View recipt')" />
|
||||
<QCheckbox v-model="shouldSendEmail" :label="t('Send email')" />
|
||||
</VnRow>
|
||||
</div>
|
||||
|
||||
<div class="q-mt-lg row justify-end">
|
||||
<QBtn
|
||||
:disabled="isLoading"
|
||||
:disabled="formModelRef.isLoading"
|
||||
:label="t('globals.cancel')"
|
||||
:loading="isLoading"
|
||||
:loading="formModelRef.isLoading"
|
||||
class="q-ml-sm"
|
||||
color="primary"
|
||||
flat
|
||||
|
@ -243,9 +301,9 @@ const onDataSaved = async () => {
|
|||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:disabled="isLoading"
|
||||
:disabled="formModelRef.isLoading"
|
||||
:label="t('globals.save')"
|
||||
:loading="isLoading"
|
||||
:loading="formModelRef.isLoading"
|
||||
color="primary"
|
||||
type="submit"
|
||||
/>
|
||||
|
@ -270,4 +328,8 @@ es:
|
|||
Send email: Enviar correo
|
||||
Compensation: Compensación
|
||||
Compensation account: Cuenta para compensar
|
||||
Supplier Compensation Reference: ({supplierId}) Ntro Proveedor {supplierName}
|
||||
Client Compensation Reference: ({clientId}) Ntro Cliente {clientName}
|
||||
There is no assigned email for this client: No hay correo asignado para este cliente
|
||||
Amount exceeded: Según ley contra el fraude no se puede recibir cobros por importe igual o superior a {maxAmount}
|
||||
</i18n>
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<script setup>
|
||||
import { onMounted, reactive } 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';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
|
||||
const initialData = reactive({});
|
||||
|
||||
onMounted(() => {
|
||||
initialData.clientFk = `${route.params.id}`;
|
||||
});
|
||||
|
||||
const toCustomerNotes = () => {
|
||||
router.push({
|
||||
name: 'CustomerNotes',
|
||||
params: {
|
||||
id: route.params.id,
|
||||
},
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormModel
|
||||
:form-initial-data="initialData"
|
||||
:observe-form-changes="false"
|
||||
@on-data-saved="toCustomerNotes()"
|
||||
url-create="ClientObservations"
|
||||
>
|
||||
<template #moreActions>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
@click="toCustomerNotes"
|
||||
color="primary"
|
||||
flat
|
||||
icon="close"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<QInput :label="t('Note')" type="textarea" v-model="data.text" />
|
||||
</VnRow>
|
||||
</template>
|
||||
</FormModel>
|
||||
</template>
|
||||
|
||||
<i18n>
|
||||
es:
|
||||
Note: Nota
|
||||
</i18n>
|
|
@ -10,8 +10,10 @@ import EntryFilter from '../EntryFilter.vue';
|
|||
:descriptor="EntryDescriptor"
|
||||
:filter-panel="EntryFilter"
|
||||
search-data-key="EntryList"
|
||||
search-url="Entries/filter"
|
||||
searchbar-label="Search entries"
|
||||
searchbar-info="You can search by entry reference"
|
||||
:searchbar-props="{
|
||||
url: 'Entries/filter',
|
||||
label: 'Search entries',
|
||||
info: 'You can search by entry reference',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -18,9 +18,9 @@ const columns = [
|
|||
name: 'itemFk',
|
||||
columnField: {
|
||||
component: VnImg,
|
||||
attrs: (id) => {
|
||||
attrs: ({ row }) => {
|
||||
return {
|
||||
id,
|
||||
id: row.id,
|
||||
size: '50x50',
|
||||
width: '50px',
|
||||
};
|
||||
|
|
|
@ -192,7 +192,7 @@ onMounted(async () => {
|
|||
:filter="entryFilter"
|
||||
:create="{
|
||||
urlCreate: 'Entries',
|
||||
title: 'Create entry',
|
||||
title: t('Create entry'),
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {},
|
||||
}"
|
||||
|
@ -210,4 +210,5 @@ es:
|
|||
Virtual entry: Es una redada
|
||||
Search entries: Buscar entradas
|
||||
You can search by entry reference: Puedes buscar por referencia de la entrada
|
||||
Create entry: Crear entrada
|
||||
</i18n>
|
||||
|
|
|
@ -130,8 +130,6 @@ onBeforeMount(async () => {
|
|||
});
|
||||
|
||||
onBeforeRouteUpdate(async (to, from) => {
|
||||
invoiceInCorrection.correcting.length = 0;
|
||||
invoiceInCorrection.corrected = null;
|
||||
if (to.params.id !== from.params.id) {
|
||||
await setInvoiceCorrection(to.params.id);
|
||||
const { data } = await axios.get(`InvoiceIns/${to.params.id}/getTotals`);
|
||||
|
@ -140,6 +138,8 @@ onBeforeRouteUpdate(async (to, from) => {
|
|||
});
|
||||
|
||||
async function setInvoiceCorrection(id) {
|
||||
invoiceInCorrection.correcting.length = 0;
|
||||
invoiceInCorrection.corrected = null;
|
||||
const { data: correctingData } = await axios.get('InvoiceInCorrections', {
|
||||
params: { filter: { where: { correctingFk: id } } },
|
||||
});
|
||||
|
@ -198,7 +198,6 @@ async function cloneInvoice() {
|
|||
const isAdministrative = () => hasAny(['administrative']);
|
||||
|
||||
const isAgricultural = () => {
|
||||
console.error(config);
|
||||
if (!config.value) return false;
|
||||
return (
|
||||
invoiceIn.value?.supplier?.sageFarmerWithholdingFk ===
|
||||
|
|
|
@ -8,9 +8,10 @@ import { useArrayData } from 'src/composables/useArrayData';
|
|||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import { toCurrency } from 'src/filters';
|
||||
import useNotify from 'src/composables/useNotify.js';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { notify } = useNotify();
|
||||
|
@ -22,9 +23,6 @@ const rowsSelected = ref([]);
|
|||
const banks = ref([]);
|
||||
const invoiceInFormRef = ref();
|
||||
const invoiceId = +route.params.id;
|
||||
|
||||
const placeholder = 'yyyy/mm/dd';
|
||||
|
||||
const filter = { where: { invoiceInFk: invoiceId } };
|
||||
|
||||
const columns = computed(() => [
|
||||
|
@ -104,42 +102,7 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
|
|||
>
|
||||
<template #body-cell-duedate="{ row }">
|
||||
<QTd>
|
||||
<QInput
|
||||
v-model="row.dueDated"
|
||||
mask="date"
|
||||
:placeholder="placeholder"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
cover
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<QDate v-model="row.dueDated" landscape>
|
||||
<div
|
||||
class="row items-center justify-end q-gutter-sm"
|
||||
>
|
||||
<QBtn
|
||||
:label="t('globals.cancel')"
|
||||
color="primary"
|
||||
flat
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.confirm')"
|
||||
color="primary"
|
||||
flat
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</QDate>
|
||||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
<VnInputDate v-model="row.dueDated" />
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-bank="{ row, col }">
|
||||
|
@ -164,7 +127,7 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
|
|||
</template>
|
||||
<template #body-cell-amount="{ row }">
|
||||
<QTd>
|
||||
<VnCurrency
|
||||
<VnInputNumber
|
||||
v-model="row.amount"
|
||||
:is-outlined="false"
|
||||
clearable
|
||||
|
@ -174,7 +137,7 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
|
|||
</template>
|
||||
<template #body-cell-foreignvalue="{ row }">
|
||||
<QTd>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:class="{
|
||||
'no-pointer-events': !isNotEuro(invoiceIn.currency.code),
|
||||
}"
|
||||
|
@ -207,51 +170,11 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
|
|||
<QSeparator />
|
||||
<QList>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnInputDate
|
||||
class="full-width"
|
||||
:label="t('Date')"
|
||||
v-model="props.row.dueDated"
|
||||
mask="date"
|
||||
:placeholder="placeholder"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
>
|
||||
<template #append>
|
||||
<QIcon name="event" class="cursor-pointer">
|
||||
<QPopupProxy
|
||||
cover
|
||||
transition-show="scale"
|
||||
transition-hide="scale"
|
||||
>
|
||||
<QDate
|
||||
v-model="props.row.dueDated"
|
||||
landscape
|
||||
>
|
||||
<div
|
||||
class="row items-center justify-end q-gutter-sm"
|
||||
>
|
||||
<QBtn
|
||||
:label="
|
||||
t('globals.cancel')
|
||||
"
|
||||
color="primary"
|
||||
flat
|
||||
v-close-popup
|
||||
/>
|
||||
<QBtn
|
||||
:label="
|
||||
t('globals.confirm')
|
||||
"
|
||||
color="primary"
|
||||
flat
|
||||
v-close-popup
|
||||
/>
|
||||
</div>
|
||||
</QDate>
|
||||
</QPopupProxy>
|
||||
</QIcon>
|
||||
</template>
|
||||
</QInput>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<VnSelect
|
||||
|
@ -274,16 +197,14 @@ const getTotalAmount = (rows) => rows.reduce((acc, { amount }) => acc + +amount,
|
|||
</VnSelect>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:label="t('Amount')"
|
||||
class="full-width"
|
||||
v-model="props.row.amount"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
/>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:label="t('Foreign value')"
|
||||
class="full-width"
|
||||
:class="{
|
||||
|
|
|
@ -7,6 +7,7 @@ import CrudModel from 'src/components/CrudModel.vue';
|
|||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import { useArrayData } from 'src/composables/useArrayData';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
@ -115,11 +116,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
>
|
||||
<template #body-cell="{ row, col }">
|
||||
<QTd>
|
||||
<QInput
|
||||
v-model="row[col.name]"
|
||||
clearable
|
||||
clear-icon="close"
|
||||
/>
|
||||
<VnInputNumber v-model="row[col.name]" />
|
||||
</QTd>
|
||||
</template>
|
||||
<template #body-cell-code="{ row, col }">
|
||||
|
@ -203,7 +200,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
]"
|
||||
:key="index"
|
||||
>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:label="t(value)"
|
||||
class="full-width"
|
||||
v-model="props.row[value]"
|
||||
|
|
|
@ -120,7 +120,6 @@ const intrastatColumns = ref([
|
|||
},
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
style: 'width: 10px',
|
||||
},
|
||||
{
|
||||
name: 'amount',
|
||||
|
@ -128,7 +127,6 @@ const intrastatColumns = ref([
|
|||
field: (row) => toCurrency(row.amount, currency.value),
|
||||
sortable: true,
|
||||
align: 'left',
|
||||
style: 'width: 10px',
|
||||
},
|
||||
{
|
||||
name: 'net',
|
||||
|
@ -415,6 +413,11 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
|
|||
</QTh>
|
||||
</QTr>
|
||||
</template>
|
||||
<template #body-cell-code="{ value: codeCell }">
|
||||
<QTd :title="codeCell" shrink>
|
||||
{{ codeCell }}
|
||||
</QTd>
|
||||
</template>
|
||||
<template #bottom-row>
|
||||
<QTr class="bg">
|
||||
<QTd></QTd>
|
||||
|
|
|
@ -9,7 +9,8 @@ import { toCurrency } from 'src/filters';
|
|||
import FetchData from 'src/components/FetchData.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import CrudModel from 'src/components/CrudModel.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const quasar = useQuasar();
|
||||
|
@ -205,7 +206,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
:grid="$q.screen.lt.sm"
|
||||
>
|
||||
<template #body-cell-expense="{ row, col }">
|
||||
<QTd auto-width>
|
||||
<QTd>
|
||||
<VnSelect
|
||||
v-model="row[col.model]"
|
||||
:options="col.options"
|
||||
|
@ -223,6 +224,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
name="close"
|
||||
@click.stop="value = null"
|
||||
class="cursor-pointer"
|
||||
size="xs"
|
||||
/>
|
||||
<QIcon
|
||||
@click.stop.prevent="newExpenseRef.show()"
|
||||
|
@ -240,7 +242,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
</template>
|
||||
<template #body-cell-taxablebase="{ row }">
|
||||
<QTd>
|
||||
<VnCurrency
|
||||
<VnInputNumber
|
||||
:class="{
|
||||
'no-pointer-events': isNotEuro(invoiceIn.currency.code),
|
||||
}"
|
||||
|
@ -308,7 +310,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
</template>
|
||||
<template #body-cell-foreignvalue="{ row }">
|
||||
<QTd>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:class="{
|
||||
'no-pointer-events': !isNotEuro(invoiceIn.currency.code),
|
||||
}"
|
||||
|
@ -356,7 +358,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
</VnSelect>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<VnCurrency
|
||||
<VnInputNumber
|
||||
:label="t('Taxable base')"
|
||||
:class="{
|
||||
'no-pointer-events': isNotEuro(
|
||||
|
@ -421,7 +423,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
{{ toCurrency(taxRate(props.row), currency) }}
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnInputNumber
|
||||
:label="t('Foreign value')"
|
||||
class="full-width"
|
||||
:class="{
|
||||
|
@ -453,7 +455,11 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
</QCardSection>
|
||||
<QCardSection class="q-pt-none">
|
||||
<QItem>
|
||||
<QInput :label="`${t('Code')}*`" v-model="newExpense.code" />
|
||||
<VnInput
|
||||
:label="`${t('Code')}*`"
|
||||
v-model="newExpense.code"
|
||||
:required="true"
|
||||
/>
|
||||
<QCheckbox
|
||||
dense
|
||||
size="sm"
|
||||
|
@ -462,7 +468,7 @@ const formatOpt = (row, { model, options }, prop) => {
|
|||
/>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QInput
|
||||
<VnInput
|
||||
:label="`${t('Descripction')}*`"
|
||||
v-model="newExpense.description"
|
||||
/>
|
||||
|
|
|
@ -26,8 +26,7 @@ const newInvoiceIn = reactive({
|
|||
companyFk: user.value.companyFk || null,
|
||||
issued: Date.vnNew(),
|
||||
});
|
||||
const suppliersOptions = ref([]);
|
||||
const companiesOptions = ref([]);
|
||||
const companies = ref([]);
|
||||
|
||||
const redirectToInvoiceInBasicData = (__, { id }) => {
|
||||
router.push({ name: 'InvoiceInBasicData', params: { id } });
|
||||
|
@ -35,19 +34,12 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<FetchData
|
||||
url="Suppliers"
|
||||
:filter="{ fields: ['id', 'nickname'] }"
|
||||
order="nickname"
|
||||
@on-fetch="(data) => (suppliersOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
ref="companiesRef"
|
||||
url="Companies"
|
||||
:filter="{ fields: ['id', 'code'] }"
|
||||
order="code"
|
||||
@on-fetch="(data) => (companiesOptions = data)"
|
||||
@on-fetch="(data) => (companies = data)"
|
||||
auto-load
|
||||
/>
|
||||
<template v-if="stateStore.isHeaderMounted()">
|
||||
|
@ -69,9 +61,10 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
|
|||
<template #form="{ data, validate }">
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
url="Suppliers"
|
||||
:fields="['id', 'nickname']"
|
||||
:label="t('Supplier')"
|
||||
v-model="data.supplierFk"
|
||||
:options="suppliersOptions"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
hide-selected
|
||||
|
@ -98,7 +91,7 @@ const redirectToInvoiceInBasicData = (__, { id }) => {
|
|||
<VnSelect
|
||||
:label="t('Company')"
|
||||
v-model="data.companyFk"
|
||||
:options="companiesOptions"
|
||||
:options="companies"
|
||||
option-value="id"
|
||||
option-label="code"
|
||||
map-options
|
||||
|
|
|
@ -6,8 +6,8 @@ import VnSelect from 'components/common/VnSelect.vue';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import FetchData from 'components/FetchData.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
defineProps({ dataKey: { type: String, required: true } });
|
||||
|
@ -28,6 +28,22 @@ const activities = ref([]);
|
|||
</div>
|
||||
</template>
|
||||
<template #body="{ params, searchFn }">
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
v-model="params.supplierFk"
|
||||
url="Suppliers"
|
||||
:fields="['id', 'nickname']"
|
||||
:label="t('params.supplierFk')"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:filter-options="['id', 'name']"
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
|
@ -50,17 +66,30 @@ const activities = ref([]);
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
v-model="params.supplierFk"
|
||||
url="Suppliers"
|
||||
:fields="['id', 'nickname']"
|
||||
:label="t('params.supplierFk')"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
:filter-options="['id', 'name']"
|
||||
<VnInput
|
||||
:label="t('params.serialNumber')"
|
||||
v-model="params.serialNumber"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('params.serial')"
|
||||
v-model="params.serial"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
:label="t('Issued')"
|
||||
v-model="params.issued"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -76,39 +105,20 @@ const activities = ref([]);
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnCurrency v-model="params.amount" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDate :label="t('From')" v-model="params.from" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDate :label="t('To')" v-model="params.to" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputDate
|
||||
:label="t('Issued')"
|
||||
v-model="params.issued"
|
||||
<VnInput
|
||||
:label="t('params.awb')"
|
||||
v-model="params.awbCode"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnSelect
|
||||
:label="t('params.supplierActivityFk')"
|
||||
v-model="params.supplierActivityFk"
|
||||
dense
|
||||
outlined
|
||||
rounded
|
||||
option-value="code"
|
||||
option-label="name"
|
||||
:options="activities"
|
||||
<VnInputNumber
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
|
@ -133,32 +143,16 @@ const activities = ref([]);
|
|||
<QExpansionItem :label="t('More options')" expand-separator>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('params.serialNumber')"
|
||||
v-model="params.serialNumber"
|
||||
<VnInputDate
|
||||
:label="t('From')"
|
||||
v-model="params.from"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('params.serial')"
|
||||
v-model="params.serial"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
:label="t('params.awb')"
|
||||
v-model="params.awbCode"
|
||||
is-outlined
|
||||
lazy-rules
|
||||
/>
|
||||
<VnInputDate :label="t('To')" v-model="params.to" is-outlined />
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</QExpansionItem>
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
<script setup>
|
||||
import { onMounted, onUnmounted } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStateStore } from 'stores/useStateStore';
|
||||
import { downloadFile } from 'src/composables/downloadFile';
|
||||
import { toDate, toCurrency } from 'src/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 InvoiceInFilter from './InvoiceInFilter.vue';
|
||||
import InvoiceInSummary from './Card/InvoiceInSummary.vue';
|
||||
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
|
||||
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import InvoiceInSearchbar from 'src/pages/InvoiceIn/InvoiceInSearchbar.vue';
|
||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||
import VnSelect from 'src/components/common/VnSelect.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
||||
|
||||
const stateStore = useStateStore();
|
||||
const { viewSummary } = useSummaryDialog();
|
||||
|
@ -20,8 +21,91 @@ const { t } = useI18n();
|
|||
|
||||
onMounted(async () => (stateStore.rightDrawer = true));
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
const tableRef = ref();
|
||||
const cols = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'id',
|
||||
label: 'Id',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'supplierFk',
|
||||
label: t('invoiceIn.list.supplier'),
|
||||
columnFilter: {
|
||||
component: 'select',
|
||||
attrs: {
|
||||
url: 'Suppliers',
|
||||
fields: ['id', 'name'],
|
||||
},
|
||||
},
|
||||
columnClass: 'expand',
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'supplierRef',
|
||||
label: t('invoiceIn.list.supplierRef'),
|
||||
},
|
||||
|
||||
{
|
||||
align: 'left',
|
||||
name: 'serialNumber',
|
||||
label: t('invoiceIn.list.serialNumber'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'serial',
|
||||
label: t('invoiceIn.list.serial'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
label: t('invoiceIn.list.issued'),
|
||||
name: 'issued',
|
||||
component: null,
|
||||
columnFilter: {
|
||||
component: 'date',
|
||||
},
|
||||
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.issued)),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'isBooked',
|
||||
label: t('invoiceIn.list.isBooked'),
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'awbCode',
|
||||
label: t('invoiceIn.list.awb'),
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'amount',
|
||||
label: t('invoiceIn.list.amount'),
|
||||
format: ({ amount }) => toCurrency(amount),
|
||||
},
|
||||
{
|
||||
align: 'right',
|
||||
name: 'tableActions',
|
||||
actions: [
|
||||
{
|
||||
title: t('components.smartCard.openSummary'),
|
||||
icon: 'preview',
|
||||
type: 'submit',
|
||||
action: (row) => viewSummary(row.id, InvoiceInSummary),
|
||||
},
|
||||
{
|
||||
title: t('globals.download'),
|
||||
icon: 'download',
|
||||
type: 'submit',
|
||||
isPrimary: true,
|
||||
action: (row) => downloadFile(row.dmsFk),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
<template>
|
||||
<InvoiceInSearchbar />
|
||||
<RightMenu>
|
||||
|
@ -29,92 +113,63 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
<InvoiceInFilter data-key="InvoiceInList" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
<div class="vn-card-list">
|
||||
<VnPaginate
|
||||
<VnTable
|
||||
ref="tableRef"
|
||||
data-key="InvoiceInList"
|
||||
url="InvoiceIns/filter"
|
||||
order="issued DESC, id DESC"
|
||||
auto-load
|
||||
:order="['issued DESC', 'id DESC']"
|
||||
:create="{
|
||||
urlCreate: 'InvoiceIns',
|
||||
title: t('globals.createInvoiceIn'),
|
||||
onDataSaved: ({ id }) => tableRef.redirect(id),
|
||||
formInitialData: {},
|
||||
}"
|
||||
redirect="invoice-in"
|
||||
:columns="cols"
|
||||
:right-search="false"
|
||||
:disable-option="{ card: true }"
|
||||
:auto-load="!!$route.query.params"
|
||||
>
|
||||
<template #body="{ rows }">
|
||||
<CardList
|
||||
v-for="(row, index) of rows"
|
||||
:key="index"
|
||||
:title="row.supplierRef"
|
||||
@click="$router.push({ path: `/invoice-in/${row.id}` })"
|
||||
:id="row.id"
|
||||
>
|
||||
<template #list-items>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.supplierRef')"
|
||||
:value="row.supplierRef"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.supplier')"
|
||||
:value="row.supplierName"
|
||||
@click.stop
|
||||
>
|
||||
<template #value>
|
||||
<span class="link">
|
||||
<template #column-supplierFk="{ row }">
|
||||
<span class="link" @click.stop>
|
||||
{{ row.supplierName }}
|
||||
<SupplierDescriptorProxy :id="row.supplierFk" />
|
||||
</span>
|
||||
</template>
|
||||
</VnLv>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.serialNumber')"
|
||||
:value="row.serialNumber"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.serial')"
|
||||
:value="row.serial"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.issued')"
|
||||
:value="toDate(row.issued)"
|
||||
/>
|
||||
<VnLv :label="t('invoiceIn.list.awb')" :value="row.awbCode" />
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.amount')"
|
||||
:value="toCurrency(row.amount)"
|
||||
/>
|
||||
<VnLv
|
||||
:label="t('invoiceIn.list.isBooked')"
|
||||
:value="!!row.isBooked"
|
||||
/>
|
||||
<template #more-create-dialog="{ data }">
|
||||
<VnSelect
|
||||
v-model="data.supplierFk"
|
||||
url="Suppliers"
|
||||
:fields="['id', 'nickname']"
|
||||
:label="t('Supplier')"
|
||||
option-value="id"
|
||||
option-label="nickname"
|
||||
:filter-options="['id', 'name']"
|
||||
:required="true"
|
||||
>
|
||||
<template #option="scope">
|
||||
<QItem v-bind="scope.itemProps">
|
||||
<QItemSection>
|
||||
<QItemLabel>{{ scope.opt?.nickname }}</QItemLabel>
|
||||
<QItemLabel caption> #{{ scope.opt?.id }} </QItemLabel>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
<template #actions>
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openCard')"
|
||||
@click.stop="
|
||||
$router.push({ path: `/invoice-in/${row.id}` })
|
||||
"
|
||||
outline
|
||||
type="reset"
|
||||
</VnSelect>
|
||||
<VnInput
|
||||
:label="t('invoiceIn.summary.supplierRef')"
|
||||
v-model="data.supplierRef"
|
||||
/>
|
||||
|
||||
<QBtn
|
||||
:label="t('components.smartCard.openSummary')"
|
||||
@click.stop="viewSummary(row.id, InvoiceInSummary)"
|
||||
color="primary"
|
||||
type="submit"
|
||||
class="q-mt-sm"
|
||||
/>
|
||||
<QBtn
|
||||
:label="t('globals.download')"
|
||||
class="q-mt-sm"
|
||||
@click.stop="downloadFile(row.dmsFk)"
|
||||
type="submit"
|
||||
color="primary"
|
||||
<VnSelect
|
||||
url="Companies"
|
||||
:label="t('Company')"
|
||||
:fields="['id', 'code']"
|
||||
v-model="data.companyFk"
|
||||
option-value="id"
|
||||
option-label="code"
|
||||
:required="true"
|
||||
/>
|
||||
<VnInputDate :label="t('invoiceIn.summary.issued')" v-model="data.issued" />
|
||||
</template>
|
||||
</CardList>
|
||||
</template>
|
||||
</VnPaginate>
|
||||
</div>
|
||||
</QPage>
|
||||
<QPageSticky position="bottom-right" :offset="[20, 20]">
|
||||
<QBtn color="primary" icon="add" size="lg" round :href="`/#/invoice-in/create`" />
|
||||
</QPageSticky>
|
||||
</VnTable>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
<script setup>
|
||||
import { ref, computed, onBeforeMount } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import axios from 'axios';
|
||||
import VnTable from 'src/components/VnTable/VnTable.vue';
|
||||
import RightMenu from 'src/components/common/RightMenu.vue';
|
||||
import InvoiceInSerialFilter from './InvoiceInSerialFilter.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const cols = computed(() => [
|
||||
{
|
||||
align: 'left',
|
||||
name: 'serial',
|
||||
label: t('Serial'),
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'pending',
|
||||
label: t('Pending'),
|
||||
columnFilter: false,
|
||||
},
|
||||
{
|
||||
align: 'left',
|
||||
name: 'total',
|
||||
label: 'Total',
|
||||
columnFilter: false,
|
||||
},
|
||||
]);
|
||||
|
||||
const daysAgo = ref();
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const tableParam = useRoute().query.table;
|
||||
|
||||
if (tableParam) daysAgo.value = JSON.parse(tableParam).daysAgo;
|
||||
else
|
||||
daysAgo.value = (
|
||||
await axios.get('InvoiceInConfigs/findOne', {
|
||||
params: { filter: { fields: ['daysAgo'] } },
|
||||
})
|
||||
).data?.daysAgo;
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<RightMenu>
|
||||
<template #right-panel>
|
||||
<InvoiceInSerialFilter data-key="InvoiceInSerial" />
|
||||
</template>
|
||||
</RightMenu>
|
||||
<VnTable
|
||||
v-if="!isNaN(daysAgo)"
|
||||
data-key="InvoiceInSerial"
|
||||
url="InvoiceIns/getSerial"
|
||||
:columns="cols"
|
||||
:right-search="false"
|
||||
:user-params="{ daysAgo }"
|
||||
:disable-option="{ card: true }"
|
||||
auto-load
|
||||
/>
|
||||
</template>
|
||||
<i18n>
|
||||
es:
|
||||
Serial: Serie
|
||||
Pending: Pendiente
|
||||
</i18n>
|
|
@ -0,0 +1,53 @@
|
|||
<script setup>
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
defineProps({ dataKey: { type: String, required: true } });
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
<template>
|
||||
<VnFilterPanel :data-key="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 }">
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInputNumber
|
||||
v-model="params.daysAgo"
|
||||
:label="t('params.daysAgo')"
|
||||
outlined
|
||||
rounded
|
||||
dense
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnInput
|
||||
v-model="params.serial"
|
||||
:label="t('params.serial')"
|
||||
outlined
|
||||
rounded
|
||||
dense
|
||||
/>
|
||||
</QItemSection>
|
||||
</QItem>
|
||||
</template>
|
||||
</VnFilterPanel>
|
||||
</template>
|
||||
<i18n>
|
||||
en:
|
||||
params:
|
||||
daysAgo: Last days
|
||||
serial: serial
|
||||
es:
|
||||
params:
|
||||
daysAgo: Últimos días
|
||||
serial: serie
|
||||
</i18n>
|
|
@ -10,8 +10,10 @@ import InvoiceOutFilter from '../InvoiceOutFilter.vue';
|
|||
:descriptor="InvoiceOutDescriptor"
|
||||
:filter-panel="InvoiceOutFilter"
|
||||
search-data-key="InvoiceOutList"
|
||||
search-url="InvoiceOuts/filter"
|
||||
searchbar-label="Search invoice"
|
||||
searchbar-info="You can search by invoice reference"
|
||||
:searchbar-props="{
|
||||
url: 'InvoiceOuts/filter',
|
||||
label: 'Search invoice',
|
||||
info: 'You can search by invoice reference',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -6,7 +6,7 @@ import FetchData from 'components/FetchData.vue';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -58,7 +58,7 @@ function setWorkers(data) {
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnCurrency
|
||||
<VnInputNumber
|
||||
:label="t('Amount')"
|
||||
v-model="params.amount"
|
||||
is-outlined
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
|
|||
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
|
||||
import VnInput from 'src/components/common/VnInput.vue';
|
||||
import VnInputDate from 'components/common/VnInputDate.vue';
|
||||
import VnCurrency from 'src/components/common/VnCurrency.vue';
|
||||
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps({
|
||||
|
@ -86,7 +86,7 @@ const props = defineProps({
|
|||
</QItem>
|
||||
<QItem>
|
||||
<QItemSection>
|
||||
<VnCurrency
|
||||
<VnInputNumber
|
||||
v-model="params.amount"
|
||||
:label="t('invoiceOut.negativeBases.amount')"
|
||||
is-outlined
|
||||
|
|
|
@ -36,15 +36,6 @@ const onIntrastatCreated = (response, formData) => {
|
|||
@on-fetch="(data) => (itemTypesOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Items/withName"
|
||||
:filter="{
|
||||
fields: ['id', 'name'],
|
||||
order: 'id DESC',
|
||||
}"
|
||||
@on-fetch="(data) => (itemsWithNameOptions = data)"
|
||||
auto-load
|
||||
/>
|
||||
<FetchData
|
||||
url="Intrastats"
|
||||
:filter="{
|
||||
|
@ -73,7 +64,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
<template #form="{ data }">
|
||||
<VnRow>
|
||||
<VnSelect
|
||||
:label="t('basicData.type')"
|
||||
:label="t('itemBasicData.type')"
|
||||
v-model="data.typeFk"
|
||||
:options="itemTypesOptions"
|
||||
option-value="id"
|
||||
|
@ -92,19 +83,21 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</QItem>
|
||||
</template>
|
||||
</VnSelect>
|
||||
<VnInput :label="t('basicData.reference')" v-model="data.comment" />
|
||||
<VnInput :label="t('basicData.relevancy')" v-model="data.relevancy" />
|
||||
<VnInput :label="t('itemBasicData.reference')" v-model="data.comment" />
|
||||
<VnInput :label="t('itemBasicData.relevancy')" v-model="data.relevancy" />
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInput :label="t('basicData.stems')" v-model="data.stems" />
|
||||
<VnInput :label="t('itemBasicData.stems')" v-model="data.stems" />
|
||||
<VnInput
|
||||
:label="t('basicData.multiplier')"
|
||||
:label="t('itemBasicData.multiplier')"
|
||||
v-model="data.stemMultiplier"
|
||||
/>
|
||||
<VnSelectDialog
|
||||
:label="t('basicData.generic')"
|
||||
:label="t('itemBasicData.generic')"
|
||||
v-model="data.genericFk"
|
||||
:options="itemsWithNameOptions"
|
||||
url="Items/withName"
|
||||
:fields="['id', 'name']"
|
||||
sort-by="id DESC"
|
||||
option-value="id"
|
||||
option-label="name"
|
||||
map-options
|
||||
|
@ -129,7 +122,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</VnRow>
|
||||
<VnRow>
|
||||
<VnSelectDialog
|
||||
:label="t('basicData.intrastat')"
|
||||
:label="t('itemBasicData.intrastat')"
|
||||
v-model="data.intrastatFk"
|
||||
:options="intrastatsOptions"
|
||||
option-value="id"
|
||||
|
@ -156,7 +149,7 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</VnSelectDialog>
|
||||
<div class="col">
|
||||
<VnSelect
|
||||
:label="t('basicData.expense')"
|
||||
:label="t('itemBasicData.expense')"
|
||||
v-model="data.expenseFk"
|
||||
:options="expensesOptions"
|
||||
option-value="id"
|
||||
|
@ -168,61 +161,64 @@ const onIntrastatCreated = (response, formData) => {
|
|||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInput
|
||||
:label="t('basicData.weightByPiece')"
|
||||
:label="t('itemBasicData.weightByPiece')"
|
||||
v-model.number="data.weightByPiece"
|
||||
:min="0"
|
||||
type="number"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('basicData.boxUnits')"
|
||||
:label="t('itemBasicData.boxUnits')"
|
||||
v-model.number="data.packingOut"
|
||||
:min="0"
|
||||
type="number"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('basicData.recycledPlastic')"
|
||||
:label="t('itemBasicData.recycledPlastic')"
|
||||
v-model.number="data.recycledPlastic"
|
||||
:min="0"
|
||||
type="number"
|
||||
/>
|
||||
<VnInput
|
||||
:label="t('basicData.nonRecycledPlastic')"
|
||||
:label="t('itemBasicData.nonRecycledPlastic')"
|
||||
v-model.number="data.nonRecycledPlastic"
|
||||
:min="0"
|
||||
type="number"
|
||||
/>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<QCheckbox v-model="data.isActive" :label="t('basicData.isActive')" />
|
||||
<QCheckbox v-model="data.hasKgPrice" :label="t('basicData.hasKgPrice')" />
|
||||
<QCheckbox v-model="data.isActive" :label="t('itemBasicData.isActive')" />
|
||||
<QCheckbox
|
||||
v-model="data.hasKgPrice"
|
||||
:label="t('itemBasicData.hasKgPrice')"
|
||||
/>
|
||||
<div>
|
||||
<QCheckbox
|
||||
v-model="data.isFragile"
|
||||
:label="t('basicData.isFragile')"
|
||||
:label="t('itemBasicData.isFragile')"
|
||||
class="q-mr-sm"
|
||||
/>
|
||||
<QIcon name="info" class="cursor-pointer" size="xs">
|
||||
<QTooltip max-width="300px">
|
||||
{{ t('basicData.isFragileTooltip') }}
|
||||
{{ t('itemBasicData.isFragileTooltip') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
<div>
|
||||
<QCheckbox
|
||||
v-model="data.isPhotoRequested"
|
||||
:label="t('basicData.isPhotoRequested')"
|
||||
:label="t('itemBasicData.isPhotoRequested')"
|
||||
class="q-mr-sm"
|
||||
/>
|
||||
<QIcon name="info" class="cursor-pointer" size="xs">
|
||||
<QTooltip>
|
||||
{{ t('basicData.isPhotoRequestedTooltip') }}
|
||||
{{ t('itemBasicData.isPhotoRequestedTooltip') }}
|
||||
</QTooltip>
|
||||
</QIcon>
|
||||
</div>
|
||||
</VnRow>
|
||||
<VnRow>
|
||||
<VnInput
|
||||
:label="t('basicData.description')"
|
||||
:label="t('itemBasicData.description')"
|
||||
type="textarea"
|
||||
v-model="data.description"
|
||||
fill-input
|
||||
|
|
|
@ -10,8 +10,10 @@ import ItemListFilter from '../ItemListFilter.vue';
|
|||
:descriptor="ItemDescriptor"
|
||||
:filter-panel="ItemListFilter"
|
||||
search-data-key="ItemList"
|
||||
search-url="Items/filter"
|
||||
searchbar-label="searchbar.label"
|
||||
searchbar-info="searchbar.info"
|
||||
:searchbar-props="{
|
||||
url: 'Items/filter',
|
||||
label: 'searchbar.labelr',
|
||||
info: 'searchbar.info',
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -112,7 +112,7 @@ const openCloneDialog = async () => {
|
|||
.dialog({
|
||||
component: VnConfirm,
|
||||
componentProps: {
|
||||
title: t("All it's properties will be copied"),
|
||||
title: t('All its properties will be copied'),
|
||||
message: t('Do you want to clone this item?'),
|
||||
},
|
||||
})
|
||||
|
@ -215,7 +215,7 @@ const openCloneDialog = async () => {
|
|||
<i18n>
|
||||
es:
|
||||
Regularize stock: Regularizar stock
|
||||
All it's properties will be copied: Todas sus propiedades serán copiadas
|
||||
All its properties will be copied: Todas sus propiedades serán copiadas
|
||||
Do you want to clone this item?: ¿Desea clonar este artículo?
|
||||
</i18n>
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ const handlePhotoUpdated = (evt = false) => {
|
|||
|
||||
<template>
|
||||
<div class="relative-position">
|
||||
<VnImg ref="image" :id="$props.entityId" @refresh="handlePhotoUpdated(true)">
|
||||
<VnImg ref="image" :id="$props.entityId" zoom-resolution="1600x900">
|
||||
<template #error>
|
||||
<div class="absolute-full picture text-center q-pa-md flex flex-center">
|
||||
<div>
|
||||
|
|
|
@ -17,6 +17,7 @@ import { toDateFormat } from 'src/filters/date.js';
|
|||
import { dashIfEmpty } from 'src/filters';
|
||||
import { date } from 'quasar';
|
||||
import { useState } from 'src/composables/useState';
|
||||
import axios from 'axios';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -34,6 +35,7 @@ const itemsBalanceFilter = reactive({
|
|||
const itemBalances = ref([]);
|
||||
const warehouseFk = ref(null);
|
||||
const _showWhatsBeforeInventory = ref(false);
|
||||
const inventoriedDate = ref(null);
|
||||
|
||||
const columns = computed(() => [
|
||||
{
|
||||
|
@ -99,7 +101,7 @@ const showWhatsBeforeInventory = computed({
|
|||
set: (val) => {
|
||||
_showWhatsBeforeInventory.value = val;
|
||||
if (!val) itemsBalanceFilter.where.date = null;
|
||||
else itemsBalanceFilter.where.date = new Date();
|
||||
else itemsBalanceFilter.where.date = inventoriedDate.value ?? new Date();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -161,6 +163,8 @@ onMounted(async () => {
|
|||
if (route.query.warehouseFk) warehouseFk.value = route.query.warehouseFk;
|
||||
else if (user.value) warehouseFk.value = user.value.warehouseFk;
|
||||
itemsBalanceFilter.where.warehouseFk = warehouseFk.value;
|
||||
const { data } = await axios.get('Configs/findOne');
|
||||
inventoriedDate.value = data.inventoried;
|
||||
await fetchItemBalances();
|
||||
await scrollToToday();
|
||||
});
|
||||
|
@ -293,7 +297,7 @@ watch(
|
|||
>
|
||||
{{ row.entityId }}
|
||||
</component>
|
||||
<span class="link">
|
||||
<span :class="{ link: row.entityId }">
|
||||
{{ dashIfEmpty(row.entityName) }}
|
||||
</span>
|
||||
</QBadge>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { onMounted, computed, onUnmounted, reactive, ref } from 'vue';
|
||||
import { onMounted, computed, onUnmounted, ref, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { dateRange } from 'src/filters';
|
||||
|
@ -11,6 +11,7 @@ import { toDateTimeFormat } from 'src/filters/date.js';
|
|||
import { dashIfEmpty } from 'src/filters';
|
||||
import { toCurrency } from 'filters/index';
|
||||
import { useArrayData } from 'composables/useArrayData';
|
||||
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
|
@ -35,26 +36,8 @@ const exprBuilder = (param, value) => {
|
|||
}
|
||||
};
|
||||
|
||||
const datedRange = reactive({
|
||||
from: null,
|
||||
to: null,
|
||||
});
|
||||
|
||||
const from = computed({
|
||||
get: () => datedRange.from,
|
||||
set: (val) => {
|
||||
updateFrom(val);
|
||||
updateFilter();
|
||||
},
|
||||
});
|
||||
|
||||
const to = computed({
|
||||
get: () => datedRange.to,
|
||||
set: (val) => {
|
||||
updateTo(val);
|
||||
updateFilter();
|
||||
},
|
||||
});
|
||||
const from = ref();
|
||||
const to = ref();
|
||||
|
||||
const arrayData = useArrayData('ItemLastEntries', {
|
||||
url: 'Items/lastEntriesFilter',
|
||||
|
@ -162,41 +145,48 @@ const fetchItemLastEntries = async () => {
|
|||
itemLastEntries.value = data;
|
||||
};
|
||||
|
||||
const updateFrom = async (date) => {
|
||||
const getDate = (date, type) => {
|
||||
if (type == 'from') {
|
||||
date.setHours(0, 0, 0, 0);
|
||||
datedRange.from = date.toISOString();
|
||||
};
|
||||
|
||||
const updateTo = async (date) => {
|
||||
date.setHours(23, 59, 59, 59);
|
||||
datedRange.to = date.toISOString();
|
||||
} else if (type == 'to') {
|
||||
date.setHours(23, 59, 59, 999);
|
||||
}
|
||||
return date.toISOString();
|
||||
};
|
||||
|
||||
const updateFilter = async () => {
|
||||
arrayData.store.userFilter.where.landed = {
|
||||
between: [datedRange.from, datedRange.to],
|
||||
};
|
||||
let filter;
|
||||
if (!from.value && to.value) filter = { lte: to.value };
|
||||
else if (from.value && !to.value) filter = { gte: from.value };
|
||||
else if (from.value && to.value) filter = { between: [from.value, to.value] };
|
||||
|
||||
arrayData.store.userFilter.where.landed = filter;
|
||||
await fetchItemLastEntries();
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
const _from = Date.vnNew();
|
||||
_from.setDate(_from.getDate() - 75);
|
||||
updateFrom(_from);
|
||||
|
||||
from.value = getDate(_from, 'from');
|
||||
const _to = Date.vnNew();
|
||||
_to.setDate(_to.getDate() + 10);
|
||||
updateTo(_to);
|
||||
to.value = getDate(Date.vnNew(), 'to');
|
||||
|
||||
updateFilter();
|
||||
|
||||
watch([from, to], ([nFrom, nTo], [oFrom, oTo]) => {
|
||||
if (nFrom && nFrom != oFrom) nFrom = getDate(new Date(nFrom), 'from');
|
||||
if (nTo && nTo != oTo) nTo = getDate(new Date(nTo), 'to');
|
||||
updateFilter();
|
||||
});
|
||||
});
|
||||
|
||||
onUnmounted(() => (stateStore.rightDrawer = false));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<QToolbar class="justify-end">
|
||||
<div id="st-data" class="row">
|
||||
<VnSubToolbar>
|
||||
<template #st-data>
|
||||
<VnInputDate
|
||||
:label="t('lastEntries.since')"
|
||||
dense
|
||||
|
@ -204,11 +194,9 @@ onUnmounted(() => (stateStore.rightDrawer = false));
|
|||
class="q-mr-lg"
|
||||
/>
|
||||
<VnInputDate :label="t('lastEntries.to')" dense v-model="to" />
|
||||
</div>
|
||||
<QSpace />
|
||||
<div id="st-actions"></div>
|
||||
</QToolbar>
|
||||
<QPage class="column items-center q-pa-md">
|
||||
</template>
|
||||
</VnSubToolbar>
|
||||
<QPage class="column items-center q-pa-xd">
|
||||
<QTable
|
||||
:rows="itemLastEntries"
|
||||
:columns="columns"
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue