355 lines
11 KiB
Vue
355 lines
11 KiB
Vue
<script setup>
|
|
import { onMounted, computed, 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 { toDateFormat } from 'src/filters/date.js';
|
|
import { dashIfEmpty } from 'src/filters';
|
|
import { date } from 'quasar';
|
|
import { useState } from 'src/composables/useState';
|
|
import { useArrayData } from 'src/composables/useArrayData';
|
|
import axios from 'axios';
|
|
import VnSubToolbar from 'components/ui/VnSubToolbar.vue';
|
|
|
|
const { t } = useI18n();
|
|
const route = useRoute();
|
|
const router = useRouter();
|
|
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 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,
|
|
},
|
|
};
|
|
|
|
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('itemBasicData.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',
|
|
},
|
|
]);
|
|
|
|
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();
|
|
},
|
|
});
|
|
|
|
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();
|
|
await updateWarehouse(warehouseFk.value);
|
|
});
|
|
|
|
watch(
|
|
() => router.currentRoute.value.params.id,
|
|
(newId) => {
|
|
itemsBalanceFilter.where.itemFk = newId;
|
|
itemBalancesRef.value.fetch();
|
|
}
|
|
);
|
|
|
|
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 todayCell = document.querySelector(`td[data-date="${today.toISOString()}"]`);
|
|
if (todayCell) {
|
|
todayCell.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
}
|
|
};
|
|
|
|
const formatDateForAttribute = (dateValue) => {
|
|
if (dateValue instanceof Date) return date.formatDate(dateValue, 'YYYY-MM-DD');
|
|
return dateValue;
|
|
};
|
|
|
|
async function updateWarehouse(warehouseFk) {
|
|
const stock = useArrayData('descriptorStock', {
|
|
userParams: {
|
|
warehouseFk,
|
|
},
|
|
});
|
|
await stock.fetch({});
|
|
stock.store.data.itemFk = route.params.id;
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<FetchData
|
|
ref="itemBalancesRef"
|
|
url="Items/getBalance"
|
|
:filter="itemsBalanceFilter"
|
|
@on-fetch="(data) => (itemBalances = data)"
|
|
/>
|
|
<FetchData
|
|
url="Warehouses"
|
|
:filter="{ fields: ['id', 'name'], order: 'name ASC' }"
|
|
auto-load
|
|
@on-fetch="(data) => (warehousesOptions = data)"
|
|
/>
|
|
<VnSubToolbar class="q-mb-md">
|
|
<template #st-data>
|
|
<div 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="
|
|
(value) => fetchItemBalances() && updateWarehouse(value)
|
|
"
|
|
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>
|
|
</template>
|
|
</VnSubToolbar>
|
|
<QPage class="column items-center">
|
|
<QTable
|
|
:rows="itemBalances"
|
|
:columns="columns"
|
|
class="full-width q-mt-md"
|
|
:no-data-label="t('globals.noResults')"
|
|
>
|
|
<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 :data-date="formatDateForAttribute(row.shipped)">
|
|
<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>
|