379 lines
12 KiB
Vue
379 lines
12 KiB
Vue
<script setup>
|
|
import { onMounted, computed, onUnmounted, reactive, ref, nextTick, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useRoute, useRouter } from 'vue-router';
|
|
|
|
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
|
|
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
|
|
import OrderDescriptorProxy from 'src/pages/Order/Card/OrderDescriptorProxy.vue';
|
|
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
|
|
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
|
|
import FetchData from 'components/FetchData.vue';
|
|
import VnSelect from 'src/components/common/VnSelect.vue';
|
|
import VnInputDate from 'src/components/common/VnInputDate.vue';
|
|
|
|
import { useStateStore } from 'stores/useStateStore';
|
|
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();
|
|
const router = useRouter();
|
|
const stateStore = useStateStore();
|
|
const state = useState();
|
|
|
|
const user = state.getUser();
|
|
const today = ref(Date.vnNew());
|
|
const warehousesOptions = ref([]);
|
|
const itemBalancesRef = ref(null);
|
|
const itemsBalanceFilter = reactive({
|
|
where: { itemFk: route.params.id, warehouseFk: null, date: null },
|
|
});
|
|
const itemBalances = ref([]);
|
|
const warehouseFk = ref(null);
|
|
const _showWhatsBeforeInventory = ref(false);
|
|
const inventoriedDate = ref(null);
|
|
const pageSize = 50;
|
|
const lastPage = ref(0);
|
|
const loading = ref(false);
|
|
const pagination = {
|
|
rowsPerPage: 0,
|
|
};
|
|
|
|
const nextPage = ref(1);
|
|
|
|
const rows = ref([]);
|
|
const tableRef = ref(null);
|
|
|
|
const columns = computed(() => [
|
|
{
|
|
name: 'claim',
|
|
align: 'left',
|
|
field: 'itemFk',
|
|
},
|
|
{
|
|
label: t('itemDiary.date'),
|
|
name: 'date',
|
|
field: 'shipped',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('itemDiary.origin'),
|
|
name: 'originId',
|
|
align: 'left',
|
|
},
|
|
{
|
|
label: t('itemDiary.state'),
|
|
field: 'stateName',
|
|
name: 'state',
|
|
align: 'left',
|
|
format: (val) => dashIfEmpty(val),
|
|
},
|
|
{
|
|
label: t('itemDiary.reference'),
|
|
field: 'reference',
|
|
name: 'reference',
|
|
align: 'left',
|
|
format: (val) => dashIfEmpty(val),
|
|
},
|
|
|
|
{
|
|
label: t('itemDiary.entity'),
|
|
name: 'entityId',
|
|
align: 'left',
|
|
format: (val) => dashIfEmpty(val),
|
|
},
|
|
{
|
|
label: t('itemDiary.in'),
|
|
field: 'invalue',
|
|
name: 'in',
|
|
align: 'left',
|
|
format: (val) => dashIfEmpty(val),
|
|
},
|
|
{
|
|
label: t('itemDiary.out'),
|
|
field: 'out',
|
|
name: 'out',
|
|
align: 'left',
|
|
format: (val) => dashIfEmpty(val),
|
|
},
|
|
{
|
|
label: t('itemDiary.balance'),
|
|
name: 'balance',
|
|
align: 'left',
|
|
},
|
|
]);
|
|
|
|
function onScroll(data) {
|
|
const { to, ref } = data;
|
|
if (!tableRef.value) tableRef.value = ref;
|
|
const lastIndex = rows.value.length - 1;
|
|
if (loading.value !== true && nextPage.value < lastPage.value && to === lastIndex) {
|
|
loading.value = true;
|
|
nextPage.value++;
|
|
sliceRows();
|
|
nextTick(() => {
|
|
ref.refresh();
|
|
loading.value = false;
|
|
});
|
|
}
|
|
}
|
|
function sliceRows() {
|
|
rows.value = itemBalances.value.slice(0, pageSize * nextPage.value);
|
|
}
|
|
|
|
const showWhatsBeforeInventory = computed({
|
|
get: () => _showWhatsBeforeInventory.value,
|
|
set: (val) => {
|
|
_showWhatsBeforeInventory.value = val;
|
|
if (!val) itemsBalanceFilter.where.date = null;
|
|
else itemsBalanceFilter.where.date = inventoriedDate.value ?? new Date();
|
|
},
|
|
});
|
|
function handleOnFetch(data) {
|
|
itemBalances.value = data;
|
|
if (data.length < 1) return;
|
|
lastPage.value = Math.ceil(data.length / pageSize);
|
|
sliceRows();
|
|
}
|
|
|
|
const fetchItemBalances = async () => await itemBalancesRef.value.fetch();
|
|
|
|
const getBadgeAttrs = (_date) => {
|
|
const isSameDate = date.isSameDate(today.value, _date);
|
|
const attrs = {
|
|
'text-color': isSameDate ? 'black' : 'white',
|
|
color: isSameDate ? 'warning' : 'transparent',
|
|
};
|
|
return attrs;
|
|
};
|
|
|
|
const scrollToToday = async () => {
|
|
await nextTick();
|
|
const today = Date.vnNew();
|
|
today.setHours(0, 0, 0, 0);
|
|
const todayIndex = itemBalances.value.findIndex((item) =>
|
|
date.isSameDate(today.toISOString(), item.shipped)
|
|
);
|
|
if (todayIndex > -1) tableRef.value.scrollTo(todayIndex);
|
|
};
|
|
|
|
const originTypeMap = {
|
|
entry: {
|
|
descriptor: EntryDescriptorProxy,
|
|
icon: 'vn:entry',
|
|
color: 'green',
|
|
},
|
|
ticket: {
|
|
descriptor: TicketDescriptorProxy,
|
|
icon: 'vn:ticket',
|
|
color: 'red',
|
|
},
|
|
order: {
|
|
descriptor: OrderDescriptorProxy,
|
|
icon: 'vn:basket',
|
|
color: 'yellow',
|
|
},
|
|
};
|
|
|
|
const entityTypeMap = {
|
|
client: {
|
|
descriptor: CustomerDescriptorProxy,
|
|
},
|
|
supplier: {
|
|
descriptor: SupplierDescriptorProxy,
|
|
},
|
|
};
|
|
|
|
onMounted(async () => {
|
|
today.value.setHours(0, 0, 0, 0);
|
|
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();
|
|
});
|
|
|
|
onUnmounted(() => (stateStore.rightDrawer = false));
|
|
|
|
watch(
|
|
() => router.currentRoute.value.params.id,
|
|
(newId) => {
|
|
itemsBalanceFilter.where.itemFk = newId;
|
|
itemBalancesRef.value.fetch();
|
|
}
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<FetchData
|
|
ref="itemBalancesRef"
|
|
url="Items/getBalance"
|
|
:filter="itemsBalanceFilter"
|
|
@on-fetch="handleOnFetch"
|
|
/>
|
|
<FetchData
|
|
url="Warehouses"
|
|
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
|
auto-load
|
|
@on-fetch="(data) => (warehousesOptions = data)"
|
|
/>
|
|
<QToolbar class="justify-end">
|
|
<div id="st-data" class="row">
|
|
<VnSelect
|
|
:label="t('itemDiary.warehouse')"
|
|
:options="warehousesOptions"
|
|
hide-selected
|
|
option-label="name"
|
|
option-value="id"
|
|
dense
|
|
v-model="itemsBalanceFilter.where.warehouseFk"
|
|
@update:model-value="fetchItemBalances"
|
|
class="q-mr-lg"
|
|
/>
|
|
<QCheckbox
|
|
:label="t('itemDiary.showBefore')"
|
|
v-model="showWhatsBeforeInventory"
|
|
@update:model-value="fetchItemBalances"
|
|
class="q-mr-lg"
|
|
/>
|
|
<VnInputDate
|
|
v-if="showWhatsBeforeInventory"
|
|
:label="t('itemDiary.since')"
|
|
dense
|
|
v-model="itemsBalanceFilter.where.date"
|
|
@update:model-value="fetchItemBalances"
|
|
/>
|
|
</div>
|
|
<QSpace />
|
|
<div id="st-actions"></div>
|
|
</QToolbar>
|
|
<QPage class="column items-center q-pa-md">
|
|
<QTable
|
|
:rows="rows"
|
|
:columns="columns"
|
|
class="full-width q-mt-md"
|
|
:no-data-label="t('globals.noResults')"
|
|
:loading="loading"
|
|
virtual-scroll
|
|
:pagination="pagination"
|
|
@virtual-scroll="onScroll"
|
|
>
|
|
<template #body-cell-claim="{ row }">
|
|
<QTd @click.stop>
|
|
<QBtn
|
|
v-show="row.claimFk"
|
|
flat
|
|
color="primary"
|
|
:to="{ name: 'ClaimSummary', params: { id: row.claimFk } }"
|
|
icon="vn:claims"
|
|
dense
|
|
>
|
|
<QTooltip>
|
|
{{ t('itemDiary.claim') }}: {{ row.claimFk }}
|
|
</QTooltip>
|
|
</QBtn>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-date="{ row }">
|
|
<QTd @click.stop>
|
|
<QBadge
|
|
v-bind="getBadgeAttrs(row.shipped)"
|
|
class="q-ma-none"
|
|
dense
|
|
style="font-size: 14px"
|
|
>
|
|
{{ toDateFormat(row.shipped) }}
|
|
</QBadge>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-originId="{ row }">
|
|
<QTd @click.stop>
|
|
<component
|
|
:is="originTypeMap[row.originType]?.descriptor"
|
|
:id="row.originId"
|
|
class="q-ma-none"
|
|
dense
|
|
style="font-size: 14px"
|
|
>
|
|
{{ row.originId }}
|
|
</component>
|
|
<QIcon
|
|
:name="originTypeMap[row.originType]?.icon"
|
|
class="fill-icon q-mr-sm"
|
|
size="sm"
|
|
:color="originTypeMap[row.originType]?.color"
|
|
/>
|
|
<span class="link">
|
|
{{ row.originId }}
|
|
</span>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-entityId="{ row }">
|
|
<QTd @click.stop>
|
|
<QBadge
|
|
:color="row.highlighted ? 'warning' : 'transparent'"
|
|
:text-color="row.highlighted ? 'black' : 'white'"
|
|
dense
|
|
style="font-size: 14px"
|
|
>
|
|
<component
|
|
:is="entityTypeMap[row.entityType]?.descriptor"
|
|
:id="row.entityId"
|
|
class="q-ma-none"
|
|
dense
|
|
style="font-size: 14px"
|
|
>
|
|
{{ row.entityId }}
|
|
</component>
|
|
<span :class="{ link: row.entityId }">
|
|
{{ dashIfEmpty(row.entityName) }}
|
|
</span>
|
|
</QBadge>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-in="{ row }">
|
|
<QTd @click.stop>
|
|
<span :class="{ 'is-in': row.invalue }">
|
|
{{ dashIfEmpty(row.invalue) }}
|
|
</span>
|
|
</QTd>
|
|
</template>
|
|
<template #body-cell-balance="{ row }">
|
|
<QTd @click.stop>
|
|
<QBadge
|
|
class="balance-negative"
|
|
:color="
|
|
row.lineFk == row.lastPreparedLineFk
|
|
? 'grey-13'
|
|
: 'transparent'
|
|
"
|
|
:text-color="
|
|
row.lineFk == row.lastPreparedLineFk
|
|
? 'black'
|
|
: row.balance < 0
|
|
? 'negative'
|
|
: ''
|
|
"
|
|
dense
|
|
style="font-size: 14px"
|
|
>
|
|
<span>{{ dashIfEmpty(row.balance) }}</span>
|
|
</QBadge>
|
|
</QTd>
|
|
</template>
|
|
</QTable>
|
|
</QPage>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.is-in {
|
|
color: $positive;
|
|
}
|
|
</style>
|