Compare commits

...

9 Commits

12 changed files with 427 additions and 218 deletions

View File

@ -6,17 +6,25 @@ import { useRequired } from 'src/composables/useRequired';
const $attrs = useAttrs();
const { isRequired, requiredFieldRule } = useRequired($attrs);
const model = defineModel({ type: [String, Date] });
const $props = defineProps({
isOutlined: {
type: Boolean,
default: false,
},
showEvent: {
isPopupOpen: {
type: Boolean,
default: true,
},
multiple: {
type: Boolean,
default: false,
},
});
const model = defineModel({
type: [String, Date, Array],
default: null,
});
const vnInputDateRef = ref(null);
@ -31,10 +39,17 @@ const mixinRules = [requiredFieldRule, ...($attrs.rules ?? [])];
const formattedDate = computed({
get() {
if (!model.value) return model.value;
if ($props.multiple) {
return model.value
.map((d) => date.formatDate(new Date(d), dateFormat))
.join(', ');
}
return date.formatDate(new Date(model.value), dateFormat);
},
set(value) {
if (value == model.value) return;
if ($props.multiple) return; // No permitir edición manual en modo múltiple
let newDate;
if (value) {
// parse input
@ -47,7 +62,7 @@ const formattedDate = computed({
}
const [year, month, day] = value.split('-').map((e) => parseInt(e));
newDate = new Date(year, month - 1, day);
if (model.value) {
if (model.value && !$props.multiple) {
const orgDate =
model.value instanceof Date ? model.value : new Date(model.value);
@ -63,12 +78,19 @@ const formattedDate = computed({
},
});
const popupDate = computed(() =>
model.value ? date.formatDate(new Date(model.value), 'YYYY/MM/DD') : model.value,
);
const popupDate = computed(() => {
if (!model.value) return model.value;
if ($props.multiple) {
return model.value.map((d) => date.formatDate(new Date(d), 'YYYY/MM/DD'));
}
return date.formatDate(new Date(model.value), 'YYYY/MM/DD');
});
onMounted(() => {
// fix quasar bug
mask.value = '##/##/####';
if ($props.multiple && !model.value) {
model.value = [];
}
});
watch(
() => model.value,
@ -86,9 +108,13 @@ const styleAttrs = computed(() => {
: {};
});
const manageDate = (date) => {
formattedDate.value = date;
isPopupOpen.value = false;
const manageDate = (dates) => {
if ($props.multiple) {
model.value = dates.map((d) => new Date(d).toISOString());
} else {
formattedDate.value = dates;
}
if ($props.isPopupOpen) isPopupOpen.value = false;
};
</script>
@ -98,7 +124,7 @@ const manageDate = (date) => {
ref="vnInputDateRef"
v-model="formattedDate"
class="vn-input-date"
:mask="mask"
:mask="$props.multiple ? undefined : mask"
placeholder="dd/mm/aaaa"
v-bind="{ ...$attrs, ...styleAttrs }"
:class="{ required: isRequired }"
@ -136,10 +162,18 @@ const manageDate = (date) => {
:no-focus="true"
:no-parent-event="true"
>
<VnDate v-model="popupDate" @update:model-value="manageDate" />
<VnDate
v-model="popupDate"
@update:model-value="manageDate"
:multiple="multiple"
/>
</QMenu>
<QDialog v-else v-model="isPopupOpen">
<VnDate v-model="popupDate" @update:model-value="manageDate" />
<VnDate
v-model="popupDate"
@update:model-value="manageDate"
:multiple="multiple"
/>
</QDialog>
</QInput>
</div>

View File

@ -1,5 +1,5 @@
<script setup>
import { toPercentage } from 'filters/index';
import { toCurrency, toPercentage } from 'filters/index';
import { computed } from 'vue';
@ -8,6 +8,10 @@ const props = defineProps({
type: Number,
required: true,
},
format: {
type: String,
default: 'percentage', // 'currency'
},
});
const valueClass = computed(() =>
@ -21,7 +25,10 @@ const formattedValue = computed(() => props.value);
<template>
<span :class="valueClass">
<QIcon :name="iconName" size="sm" class="value-icon" />
{{ toPercentage(formattedValue) }}
<span v-if="$props.format === 'percentage'">{{
toPercentage(formattedValue)
}}</span>
<span v-if="$props.format === 'currency'">{{ toCurrency(formattedValue) }}</span>
</span>
</template>

View File

@ -42,7 +42,7 @@ const ticketConfig = ref({});
const proposalTableRef = ref(null);
const sale = computed(() => $props.sales[0]);
const saleFk = computed(() => sale.value.saleFk);
const saleFk = computed(() => sale.value?.saleFk);
const filter = computed(() => ({
where: $props.filter,
@ -56,8 +56,24 @@ const defaultColumnAttrs = {
};
const emit = defineEmits(['onDialogClosed', 'itemReplaced']);
const conditionalValuePrice = (price) =>
price > 1 + ticketConfig.value.lackAlertPrice / 100 ? 'match' : 'not-match';
const priceStatusClass = (proposalPrice) => {
const originalPrice = sale.value?.price;
if (
!originalPrice ||
!ticketConfig.value ||
typeof ticketConfig.value.lackAlertPrice !== 'number'
) {
return 'price-ok';
}
const priceIncreasePercentage =
((proposalPrice - originalPrice) / originalPrice) * 100;
return priceIncreasePercentage > ticketConfig.value.lackAlertPrice
? 'price-alert'
: 'price-ok';
};
const columns = computed(() => [
{
@ -97,7 +113,15 @@ const columns = computed(() => [
{
align: 'left',
sortable: true,
label: t('item.list.color'),
label: t('item.list.producer'),
name: 'subName',
field: 'subName',
columnClass: 'expand',
},
{
align: 'left',
sortable: true,
label: t('proposal.tag5'),
name: 'tag5',
field: 'value5',
columnClass: 'expand',
@ -105,7 +129,7 @@ const columns = computed(() => [
{
align: 'left',
sortable: true,
label: t('item.list.stems'),
label: t('proposal.tag6'),
name: 'tag6',
field: 'value6',
columnClass: 'expand',
@ -113,12 +137,27 @@ const columns = computed(() => [
{
align: 'left',
sortable: true,
label: t('item.list.producer'),
label: t('proposal.tag7'),
name: 'tag7',
field: 'value7',
columnClass: 'expand',
},
{
align: 'left',
sortable: true,
label: t('proposal.tag8'),
name: 'tag8',
field: 'value8',
columnClass: 'expand',
},
{
align: 'left',
sortable: true,
label: t('proposal.advanceable'),
name: 'advanceable',
field: 'advanceable',
columnClass: 'expand',
},
{
...defaultColumnAttrs,
label: t('proposal.price2'),
@ -169,14 +208,14 @@ function extractMatchValues(obj) {
.filter((key) => key.startsWith(MATCH))
.map((key) => parseInt(key.replace(MATCH, ''), 10));
}
const gradientStyle = (value) => {
const gradientStyleClass = (row) => {
let color = 'white';
const perc = parseFloat(value);
const value = parseFloat(row);
switch (true) {
case perc >= 0 && perc < 33:
case value >= 0 && value < 33:
color = 'primary';
break;
case perc >= 33 && perc < 66:
case value >= 33 && value < 66:
color = 'warning';
break;
@ -193,34 +232,49 @@ const statusConditionalValue = (row) => {
};
const isSelectionAvailable = (itemProposal) => {
const { price2 } = itemProposal;
const salePrice = sale.value.price;
const byPrice = (100 * price2) / salePrice > ticketConfig.value.lackAlertPrice;
if (byPrice) {
return byPrice;
const { price2, available } = itemProposal;
const originalPrice = sale.value?.price;
const lackQuantity = $props.itemLack?.lack;
if (
!originalPrice ||
!lackQuantity ||
!ticketConfig.value ||
typeof ticketConfig.value.lackAlertPrice !== 'number'
) {
return false;
}
const byQuantity =
(100 * itemProposal.available) / Math.abs($props.itemLack.lack) <
ticketConfig.value.lackAlertPrice;
return byQuantity;
const priceIncreasePercentage = ((price2 - originalPrice) / originalPrice) * 100;
const isPriceTooHigh = priceIncreasePercentage > ticketConfig.value.lackAlertPrice;
if (isPriceTooHigh) {
return false;
}
const availablePercentage = (available / Math.abs(lackQuantity)) * 100;
const hasEnoughQuantity = availablePercentage >= ticketConfig.value.lackAlertPrice;
return hasEnoughQuantity;
};
async function change({ itemFk: substitutionFk }) {
try {
let body;
const promises = $props.sales.map(({ saleFk, quantity }) => {
const params = {
body = {
saleFk,
substitutionFk,
quantity,
};
return axios.post('Sales/replaceItem', params);
return axios.post('Sales/replaceItem', body);
});
const results = await Promise.allSettled(promises);
notifyResults(results, 'saleFk');
emit('itemReplaced', {
...body,
type: 'refresh',
quantity: quantity.value,
itemProposal: proposalSelected.value[0],
});
proposalSelected.value = [];
@ -232,6 +286,13 @@ async function change({ itemFk: substitutionFk }) {
async function handleTicketConfig(data) {
ticketConfig.value = data[0];
}
function filterRows(data) {
const filteredRows = data.sort(
(a, b) => isSelectionAvailable(b) - isSelectionAvailable(a),
);
proposalTableRef.value.CrudModelRef.formData = filteredRows;
}
</script>
<template>
<FetchData
@ -251,13 +312,22 @@ async function handleTicketConfig(data) {
:user-filter="filter"
:columns="columns"
class="full-width q-mt-md"
@on-fetch="filterRows"
row-key="id"
:row-click="change"
:is-editable="false"
:right-search="false"
:without-header="true"
:disable-option="{ card: true, table: true }"
>
<template #top-right>
<QBtn
flat
class="q-mr-sm"
color="primary"
icon="refresh"
@click="proposalTableRef.reload()"
/>
</template>
<template #column-longName="{ row }">
<QTd
class="flex"
@ -265,15 +335,17 @@ async function handleTicketConfig(data) {
>
<div
class="middle full-width"
:class="[`proposal-${gradientStyle(statusConditionalValue(row))}`]"
:class="[
`proposal-${gradientStyleClass(statusConditionalValue(row))}`,
]"
>
<QTooltip> {{ statusConditionalValue(row) }}% </QTooltip>
</div>
<div style="flex: 2 0 100%; align-content: center">
<div>
<span class="link">{{ row.longName }}</span>
<span class="link" @click.stop>
{{ row.longName }}
<ItemDescriptorProxy :id="row.id" />
</div>
</span>
</div>
</QTd>
</template>
@ -286,6 +358,9 @@ async function handleTicketConfig(data) {
<template #column-tag7="{ row }">
<span :class="{ match: !row.match7 }">{{ row.value7 }}</span>
</template>
<template #column-tag8="{ row }">
<span :class="{ match: !row.match8 }">{{ row.value8 }}</span>
</template>
<template #column-counter="{ row }">
<span
:class="{
@ -300,8 +375,17 @@ async function handleTicketConfig(data) {
</template>
<template #column-price2="{ row }">
<div class="flex column items-center content-center">
<VnStockValueDisplay :value="(sales[0].price - row.price2) / 100" />
<span :class="[conditionalValuePrice(row.price2)]">{{
<!-- Use class binding for tooltip background -->
<QTooltip :offset="[0, 5]" anchor="top middle" self="bottom middle">
<div>{{ $t('proposal.price2') }}: {{ toCurrency(row.price2) }}</div>
<div>
{{ $t('proposal.itemOldPrice') }}:
{{ toCurrency(sales[0]?.price) }}
</div>
</QTooltip>
<VnStockValueDisplay :format="'currency'" :value="-row.price2 / 100" />
<!-- Use class binding for text color -->
<span :class="[priceStatusClass(row.price2)]">{{
toCurrency(row.price2)
}}</span>
</div>
@ -315,12 +399,37 @@ async function handleTicketConfig(data) {
margin-right: 2px;
flex: 2 0 5px;
}
.match {
/* Removed old .match / .not-match specific to price */
/* .match {
color: $negative;
}
.not-match {
color: inherit;
} */
/* Added classes for price status */
.price-alert {
color: $negative; /* Alert text color */
&.q-tooltip {
background-color: $negative; /* Alert tooltip background */
color: white; /* Ensure tooltip text is readable */
}
}
.price-ok {
color: inherit; /* Default text color */
&.q-tooltip {
/* Keep default tooltip background or set a specific 'ok' color */
background-color: $positive; /* Example: green background for OK price */
color: white; /* Ensure tooltip text is readable */
}
}
.match {
color: $negative;
}
.proposal-warning {
background-color: $warning;
}

View File

@ -23,33 +23,32 @@ const $props = defineProps({
default: () => [],
},
});
const { dialogRef } = useDialogPluginComponent();
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } =
useDialogPluginComponent();
const emit = defineEmits([
'onDialogClosed',
'onDialogOk',
'itemReplaced',
...useDialogPluginComponent.emits,
]);
defineExpose({ show: () => dialogRef.value.show(), hide: () => dialogRef.value.hide() });
const itemReplaced = (data) => {
onDialogOK(data);
dialogRef.value.hide();
};
</script>
<template>
<QDialog ref="dialogRef" transition-show="scale" transition-hide="scale">
<QCard class="dialog-width">
<QCardSection class="row items-center q-pb-none">
<span class="text-h6 text-grey">{{ $t('itemProposal') }}</span>
<span class="text-h6 text-grey" v-text="$t('itemProposal')" />
<QSpace />
<QBtn icon="close" flat round dense v-close-popup />
</QCardSection>
<QCardSection>
<ItemProposal
v-bind="$props"
@item-replaced="
(data) => {
emit('itemReplaced', data);
dialogRef.hide();
}
"
></ItemProposal
></QCardSection>
<ItemProposal v-bind="$props" @item-replaced="itemReplaced"
/></QCardSection>
</QCard>
</QDialog>
</template>

View File

@ -231,6 +231,11 @@ proposal:
value6: value6
value7: value7
value8: value8
tag5: Tag5
tag6: Tag6
tag7: Tag7
tag8: Tag8
advanceable: Advanceable
available: Available
minQuantity: minQuantity
price2: Price

View File

@ -237,11 +237,16 @@ proposal:
value6: value6
value7: value7
value8: value8
tag5: Tag5
tag6: Tag6
tag7: Tag7
tag8: Tag8
available: Disponible
minQuantity: Min. cantidad
price2: Precio
located: Ubicado
counter: Contador
advanceable: Adelantable
difference: Diferencial
groupingPrice: Precio Grouping
itemOldPrice: Precio itemOld

View File

@ -7,6 +7,8 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDateTime from 'src/components/common/VnInputDateTime.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
@ -67,7 +69,6 @@ const setUserParams = (params) => {
:data-key="props.dataKey"
:search-button="true"
@set-user-params="setUserParams"
:unremovable-params="['warehouseFk']"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
@ -85,7 +86,7 @@ const setUserParams = (params) => {
dense
filled
@update:model-value="
(value) => {
() => {
setUserParams(params);
}
"
@ -120,8 +121,21 @@ const setUserParams = (params) => {
dense
filled
/>
</QItemSection> </QItem
><QItem>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.excludedDates"
filled
:label="t('negative.excludedDates')"
:multiple="true"
:is-popup-open="false"
>
</VnInputDate>
</QItemSection>
</QItem>
<QItem>
<QItemSection v-if="categoriesOptions">
<VnSelect
:label="t('negative.categoryFk')"

View File

@ -7,6 +7,7 @@ import { onBeforeMount } from 'vue';
import { dashIfEmpty, toDate, toHour } from 'src/filters';
import { useRouter } from 'vue-router';
import { useState } from 'src/composables/useState';
import EntryDescriptorProxy from 'src/pages/Entry/Card/EntryDescriptorProxy.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import TicketLackFilter from './TicketLackFilter.vue';
@ -45,10 +46,10 @@ const columns = computed(() => [
},
{
columnClass: 'shrink',
name: 'timed',
name: 'minTimed',
align: 'center',
label: t('negative.timed'),
format: ({ timed }) => toHour(timed),
format: ({ minTimed }) => toHour(minTimed),
sortable: true,
cardVisible: true,
columnFilter: {
@ -64,9 +65,25 @@ const columns = computed(() => [
columnFilter: {
component: 'input',
type: 'number',
inWhere: false,
columnClass: 'shrink',
},
},
{
name: 'nextEntryFk',
align: 'center',
label: t('negative.nextEntryFk'),
format: ({ nextEntryFk }) => nextEntryFk,
sortable: false,
columnFilter: false,
},
{
name: 'nextEntryLanded',
align: 'center',
label: t('negative.nextEntryLanded'),
format: ({ nextEntryLanded }) => toDate(nextEntryLanded),
sortable: false,
columnFilter: false,
},
{
name: 'longName',
align: 'left',
@ -194,6 +211,12 @@ const setUserParams = (params) => {
<span @click.stop>{{ row.itemFk }}</span>
</div>
</template>
<template #column-nextEntryFk="{ row }">
<span class="link" @click.stop>
{{ row.nextEntryFk }}
<EntryDescriptorProxy :id="row.nextEntryFk" />
</span>
</template>
<template #column-longName="{ row }">
<span class="link" @click.stop>
{{ row.longName }}

View File

@ -206,7 +206,6 @@ ticketList:
toLines: Go to lines
addressNickname: Address nickname
ref: Reference
hour: Hour
rounding: Rounding
noVerifiedData: No verified data
purchaseRequest: Purchase request
@ -214,6 +213,8 @@ ticketList:
clientFrozen: Client frozen
componentLack: Component lack
negative:
nextEntryFk: Next entry
nextEntryLanded: Next entry landed
hour: Hour
id: Id Article
longName: Article
@ -224,6 +225,7 @@ negative:
value: Negative
itemFk: Article
producer: Producer
excludedDates: Excluded dates
warehouse: Warehouse
warehouseFk: Warehouse
category: Category

View File

@ -215,6 +215,8 @@ ticketList:
addressNickname: Alias consignatario
ref: Referencia
negative:
nextEntryLanded: F. Entrada
nextEntryFk: Entrada
hour: Hora
id: Id Articulo
longName: Artículo
@ -225,7 +227,8 @@ negative:
origen: Origen
value: Negativo
warehouseFk: Almacen
producer: Producer
producer: Productor
excludedDates: Fechas excluidas
category: Categoría
categoryFk: Familia
typeFk: Familia

View File

@ -1,147 +1,173 @@
/// <reference types="cypress" />
describe.skip('Ticket Lack detail', () => {
const firstRow = 'tr.cursor-pointer > :nth-child(1)';
const ticketId = 1000000;
const findTr = (index) => `tbody > tr > :nth-child(${index}) > div`;
const clickNotificationAction = () => {
const notification = '.q-notification';
cy.waitForElement(notification);
cy.get(notification).should('be.visible');
cy.get(notification).find('.q-btn').click();
cy.get(notification).should('not.be.visible');
};
describe('Ticket Lack detail', () => {
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.intercept('GET', /\/api\/Tickets\/itemLack\/5.*$/, {
statusCode: 200,
body: [
{
saleFk: 33,
code: 'OK',
ticketFk: 142,
nickname: 'Malibu Point',
shipped: '2000-12-31T23:00:00.000Z',
hour: 0,
quantity: 50,
agName: 'Super-Man delivery',
alertLevel: 0,
stateName: 'OK',
stateId: 3,
itemFk: 5,
price: 1.79,
alertLevelCode: 'FREE',
zoneFk: 9,
zoneName: 'Zone superMan',
theoreticalhour: '2011-11-01T22:59:00.000Z',
isRookie: 1,
turno: 1,
peticionCompra: 1,
hasObservation: 1,
hasToIgnore: 1,
isBasket: 1,
minTimed: 0,
customerId: 1104,
customerName: 'Tony Stark',
observationTypeCode: 'administrative',
},
],
}).as('getItemLack');
cy.intercept('GET', /\/api\/Tickets\/itemLack\/88.*$/).as('getItemLack');
cy.visit('/#/ticket/negative/88');
cy.visit('/#/ticket/negative/5', false);
cy.wait('@getItemLack');
cy.wait('@getItemLack').then((interception) => {
const { query } = interception.request;
const filter = JSON.parse(query.filter);
expect(filter).to.have.property('where');
expect(filter.where).to.have.property('alertLevelCode', 'FREE');
});
cy.domContentLoad();
cy.waitForElement('.q-table');
});
describe('Table actions', () => {
describe('Table detail', () => {
it('should open descriptors', () => {
cy.get('.q-table').should('be.visible');
cy.colField('zoneName').click();
cy.dataCy('zoneDescriptorProxy').should('be.visible');
cy.get('.q-item > .q-item__label').should('have.text', ' #1');
cy.colField('ticketFk').click();
cy.dataCy('ticketDescriptorProxy').should('be.visible');
cy.get('.q-item > .q-item__label').should('have.text', ` #${ticketId}`);
cy.colField('nickname').find('.link').click();
cy.waitForElement('[data-cy="customerDescriptorProxy"]');
cy.dataCy('customerDescriptorProxy').should('be.visible');
cy.get('.q-item > .q-item__label').should('have.text', ' #1');
});
it('should display only one row in the lack list', () => {
cy.location('href').should('contain', '#/ticket/negative/5');
cy.get('[data-cy="changeItem"]').should('be.disabled');
cy.get('[data-cy="changeState"]').should('be.disabled');
cy.get('[data-cy="changeQuantity"]').should('be.disabled');
cy.get('[data-cy="itemProposal"]').should('be.disabled');
cy.get('[data-cy="transferLines"]').should('be.disabled');
cy.dataCy('changeItem').should('be.disabled');
cy.dataCy('changeState').should('be.disabled');
cy.dataCy('changeQuantity').should('be.disabled');
cy.dataCy('itemProposal').should('be.disabled');
cy.dataCy('transferLines').should('be.disabled');
cy.get('tr.cursor-pointer > :nth-child(1)').click();
cy.get('[data-cy="changeItem"]').should('be.enabled');
cy.get('[data-cy="changeState"]').should('be.enabled');
cy.get('[data-cy="changeQuantity"]').should('be.enabled');
cy.get('[data-cy="itemProposal"]').should('be.enabled');
cy.get('[data-cy="transferLines"]').should('be.enabled');
cy.dataCy('changeItem').should('be.enabled');
cy.dataCy('changeState').should('be.enabled');
cy.dataCy('changeQuantity').should('be.enabled');
cy.dataCy('itemProposal').should('be.enabled');
cy.dataCy('transferLines').should('be.enabled');
});
});
describe('Split', () => {
beforeEach(() => {
cy.get(firstRow).click();
cy.dataCy('transferLines').click();
});
it('Split', () => {
cy.dataCy('ticketTransferPopup').find('.flex > .q-btn').click();
clickNotificationAction();
cy.dataCy('HandleLackDialog').should('be.visible');
cy.dataCy('HandleLackDialog').find('tbody > tr > :nth-child(1) > .q-icon');
cy.dataCy('HandleLackDialog').find(findTr(2)).should('have.class', 'link');
cy.dataCy('HandleLackDialog')
.find(`${findTr(2)}.link > div > span`)
.should('have.text', `${ticketId} `);
cy.dataCy('HandleLackDialog').find(findTr(3)).should('have.text', 'noSplit');
});
});
describe('Change Item', () => {
beforeEach(() => {
cy.get(firstRow).click();
cy.dataCy('changeItem').click();
});
it('Change failed', () => {
cy.dataCy('New item_select').should('be.visible').click();
cy.get('.q-item').contains('Palito rojo').click();
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
clickNotificationAction();
cy.dataCy('HandleLackDialog').should('be.visible');
cy.dataCy('HandleLackDialog').find('tbody > tr > :nth-child(1) > .q-icon');
cy.dataCy('HandleLackDialog').find(findTr(2)).should('have.class', 'link');
cy.dataCy('HandleLackDialog')
.find(`${findTr(2)}.link > span`)
.should('have.text', `${ticketId}`);
cy.dataCy('HandleLackDialog')
.find(findTr(3))
.should('have.text', 'price retrieval failed');
});
});
describe('Change state', () => {
beforeEach(() => {
cy.get(firstRow).click();
cy.dataCy('changeState').click();
});
it('Change success', () => {
cy.dataCy('New state_select').should('be.visible').click();
cy.get('.q-item').contains('OK').click();
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
clickNotificationAction();
cy.dataCy('HandleLackDialog')
.should('be.visible')
.find('tbody > tr > :nth-child(1) > .q-icon');
cy.dataCy('HandleLackDialog').find(findTr(2)).should('have.class', 'link');
cy.dataCy('HandleLackDialog')
.find(`${findTr(2)}.link > div > span`)
.should('have.text', `${ticketId} `);
cy.dataCy('HandleLackDialog').find(findTr(3)).should('have.text', 'OK');
});
});
describe('change quantity', () => {
beforeEach(() => {
cy.get(firstRow).click();
cy.dataCy('changeQuantity').click();
});
it('Change success', () => {
cy.dataCy('New quantity_input').type(10);
cy.get('.q-btn--unelevated > .q-btn__content > .block').click();
clickNotificationAction();
cy.dataCy('HandleLackDialog')
.should('be.visible')
.find('tbody > tr > :nth-child(1) > .q-icon');
cy.dataCy('HandleLackDialog').find(findTr(2)).should('have.class', 'link');
cy.dataCy('HandleLackDialog')
.find(`${findTr(2)}.link > div > span`)
.should('have.text', `${ticketId} `);
cy.dataCy('HandleLackDialog').find(findTr(3)).should('have.text', '10');
});
});
describe('Item proposal', () => {
beforeEach(() => {
cy.get('tr.cursor-pointer > :nth-child(1)').click();
cy.intercept('GET', /\/api\/Items\/getSimilar\?.*$/, {
statusCode: 200,
body: [
{
id: 1,
longName: 'Ranged weapon longbow 50cm',
subName: 'Stark Industries',
tag5: 'Color',
value5: 'Brown',
match5: 0,
match6: 0,
match7: 0,
match8: 1,
tag6: 'Categoria',
value6: '+1 precission',
tag7: 'Tallos',
value7: '1',
tag8: null,
value8: null,
available: 20,
calc_id: 6,
counter: 0,
minQuantity: 1,
visible: null,
price2: 1,
},
{
id: 2,
longName: 'Ranged weapon longbow 100cm',
subName: 'Stark Industries',
tag5: 'Color',
value5: 'Brown',
match5: 0,
match6: 1,
match7: 0,
match8: 1,
tag6: 'Categoria',
value6: '+1 precission',
tag7: 'Tallos',
value7: '1',
tag8: null,
value8: null,
available: 50,
calc_id: 6,
counter: 1,
minQuantity: 5,
visible: null,
price2: 10,
},
{
id: 3,
longName: 'Ranged weapon longbow 200cm',
subName: 'Stark Industries',
tag5: 'Color',
value5: 'Brown',
match5: 1,
match6: 1,
match7: 1,
match8: 1,
tag6: 'Categoria',
value6: '+1 precission',
tag7: 'Tallos',
value7: '1',
tag8: null,
value8: null,
available: 185,
calc_id: 6,
counter: 10,
minQuantity: 10,
visible: null,
price2: 100,
},
],
}).as('getItemGetSimilar');
cy.get('[data-cy="itemProposal"]').click();
cy.wait('@getItemGetSimilar');
cy.get(firstRow).click();
cy.dataCy('itemProposal').click();
});
describe.skip('Replace item if', () => {
it('Quantity is less than available', () => {
describe('Replace item if', () => {
it.skip('Quantity is less than available', () => {
cy.get(':nth-child(1) > .text-right > .q-btn').click();
});
it('item proposal cells', () => {
cy.get('[data-col-field="longName"] .link').first().click();
cy.dataCy('itemDescriptorProxy').should('be.visible');
cy.colField('longName', 2)
.find('.no-padding > .q-td > .middle')
.should('have.class', 'proposal-primary');
cy.colField('tag5', 2)
.find('.no-padding > .match')
.should('have.class', 'match');
cy.colField('tag6', 2)
.find('.no-padding > .match')
.should('have.class', 'match');
cy.colField('tag7', 2).click();
});
});
});
});

View File

@ -1,34 +1,16 @@
/// <reference types="cypress" />
describe('Ticket Lack list', () => {
beforeEach(() => {
cy.login('developer');
cy.intercept('GET', /Tickets\/itemLack\?.*$/, {
statusCode: 200,
body: [
{
itemFk: 5,
longName: 'Ranged weapon pistol 9mm',
warehouseFk: 1,
producer: null,
size: 15,
category: null,
warehouse: 'Warehouse One',
lack: -50,
inkFk: 'SLV',
timed: '2025-01-25T22:59:00.000Z',
minTimed: '23:59',
originFk: 'Holand',
},
],
}).as('getLack');
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('/#/ticket/negative');
});
describe('Table actions', () => {
it('should display only one row in the lack list', () => {
cy.wait('@getLack', { timeout: 10000 });
cy.get('[data-col-field="longName"]').first().click();
cy.dataCy('itemDescriptorProxy').should('be.visible');
cy.get('.q-virtual-scroll__content > :nth-child(1) > .sticky').click();
cy.location('href').should('contain', '#/ticket/negative/5');
});