feat: refs #8363 added new clone form, modified list and created new composable

This commit is contained in:
Jon Elias 2025-03-11 15:59:24 +01:00
parent ac12e1454a
commit a38470577c
7 changed files with 223 additions and 155 deletions

View File

@ -0,0 +1,168 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useQuasar } from 'quasar';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'components/ui/VnRow.vue';
const $props = defineProps({
row: {
type: Array,
default: () => [],
},
});
const { t } = useI18n();
const quasar = useQuasar();
const isLoading = ref(false);
const closeButton = ref(null);
const data = $props.row;
const emit = defineEmits(['onDataSaved']);
const onSubmit = async () => {
if (isLoading.value) return;
isLoading.value = true;
const params = data;
await axios.post('FixedPrices', params);
quasar.notify({
type: 'positive',
message: t('globals.dataSaved'),
});
isLoading.value = false;
closeForm();
emit('onDataSaved');
};
const closeForm = () => {
if (closeButton.value) closeButton.value.click();
};
</script>
<template>
<QForm @submit="onSubmit()" class="all-pointer-events">
<QCard class="q-pa-lg">
<span ref="closeButton" class="close-icon" v-close-popup>
<QIcon name="close" size="sm" />
</span>
<div class="q-pb-md">
<span class="title">{{ t('Clone item') }}</span>
</div>
<VnRow>
<VnSelect
url="Items/search"
v-model="data.itemFk"
:label="t('item.fixedPrice.itemName')"
:fields="['id', 'name']"
:filter-options="['id', 'name']"
option-label="name"
option-value="id"
:required="true"
sort-by="name ASC"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.name }}
</QItemLabel>
<QItemLabel caption> #{{ scope.opt.id }} </QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow>
<VnRow>
<VnInput
:label="t('item.fixedPrice.groupingPrice')"
v-model="data.rate2"
/>
<VnInput
:label="t('item.fixedPrice.packingPrice')"
v-model="data.rate3"
/>
</VnRow>
<VnRow>
<VnInputDate
:label="t('item.fixedPrice.started')"
v-model="data.started"
/>
<VnInputDate :label="t('item.fixedPrice.ended')" v-model="data.ended" />
</VnRow>
<VnRow>
<VnSelect
:label="t('globals.warehouse')"
url="Warehouses"
v-model="data.warehouseFk"
:fields="['id', 'name']"
option-label="name"
option-value="id"
hide-selected
:required="true"
sort-by="name ASC"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt.name }}
</QItemLabel>
<QItemLabel caption> #{{ scope.opt.id }} </QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.cancel')"
type="reset"
color="primary"
flat
:disabled="isLoading"
:loading="isLoading"
v-close-popup
/>
<QBtn
:label="t('globals.save')"
type="submit"
color="primary"
class="q-ml-sm"
:disabled="isLoading"
:loading="isLoading"
@click="onSubmit()"
/>
</div>
</QCard>
</QForm>
</template>
<style lang="scss" scoped>
.title {
font-size: 17px;
font-weight: bold;
line-height: 20px;
}
.close-icon {
position: absolute;
top: 20px;
right: 20px;
cursor: pointer;
}
.countLines {
font-size: 24px;
color: $primary;
font-weight: bold;
}
</style>
<i18n>
es:
Clone item: Clonar artículo
Item id: Id artículo
</i18n>

View File

@ -28,6 +28,7 @@ const $props = defineProps({
});
const { t } = useI18n();
const emit = defineEmits(['onDataSaved']);
const inputs = {
input: markRaw(VnInput),
@ -47,6 +48,7 @@ const onSubmit = async () => {
$props.rows.forEach((row) => {
row[selectedField.value.name] = newValue.value;
});
emit('onDataSaved', $props.rows);
closeForm();
};

View File

@ -1,5 +1,5 @@
import { createWrapper, axios } from 'app/test/vitest/helper';
import EditForm from 'components/EditTableCellValueForm.vue';
import EditForm from 'src/components/EditFixedPriceForm.vue';
import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest';
const fieldA = 'fieldA';

View File

@ -1,14 +1,12 @@
import axios from 'axios';
export async function beforeSave(data, getChanges, modelOrigin) {
console.log('data: ', data);
try {
const changes = data.updates;
if (!changes) return data;
const patchPromises = [];
for (const change of changes) {
console.log('change: ', change);
let patchData = {};
if ('hasMinPrice' in change.data) {

View File

@ -1,35 +1,30 @@
<script setup>
import { onMounted, ref, onUnmounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { useQuasar } from 'quasar';
import { useStateStore } from 'stores/useStateStore';
import useNotify from 'src/composables/useNotify.js';
import { beforeSave } from 'src/composables/updateMinPriceBeforeSave';
import FetchedTags from 'components/ui/FetchedTags.vue';
import VnConfirm from 'components/ui/VnConfirm.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import EditTableCellValueForm from 'src/components/EditTableCellValueForm.vue';
import EditFixedPriceForm from 'src/components/EditFixedPriceForm.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnColor from 'src/components/common/VnColor.vue';
import VnImg from 'src/components/ui/VnImg.vue';
import { toDate } from 'src/filters';
import { isLower, isBigger } from 'src/filters/date.js';
import ItemFixedPriceFilter from './ItemFixedPriceFilter.vue';
import ItemDescriptorProxy from './Card/ItemDescriptorProxy.vue';
import CloneFixedPriceForm from 'src/components/CloneFixedPriceForm.vue';
const quasar = useQuasar();
const stateStore = useStateStore();
const { t } = useI18n();
const { notify } = useNotify();
const tableRef = ref();
const editTableCellDialogRef = ref(null);
const fixedPrices = ref([]);
const editFixedPriceForm = ref(null);
const cloneFixedPriceForm = ref(null);
const cloneRow = ref({});
const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const dateColor = 'var(--vn-label-text-color)';
@ -39,21 +34,6 @@ onMounted(async () => {
onUnmounted(() => (stateStore.rightDrawer = false));
const columns = computed(() => [
{
align: 'center',
label: t('lines.image'),
name: 'image',
columnField: {
component: VnImg,
attrs: ({ row }) => {
return {
id: row.itemFk,
};
},
},
width: '50px',
columnFilter: false,
},
{
name: 'itemFk',
label: t('item.fixedPrice.itemFk'),
@ -63,7 +43,7 @@ const columns = computed(() => [
columnFilter: {
inWhere: true,
},
width: '60px',
width: '55px',
},
{
labelAbbreviation: '',
@ -71,7 +51,7 @@ const columns = computed(() => [
name: 'hex',
columnSearch: false,
isEditable: false,
width: '12px',
width: '10px',
component: 'select',
attrs: {
url: 'Inks',
@ -101,22 +81,22 @@ const columns = computed(() => [
component: 'number',
create: true,
createOrder: 3,
width: '55px',
width: '40px',
},
{
label: t('item.fixedPrice.packingPrice'),
labelAbbreviation: 'pack.',
labelAbbreviation: 'Pack.',
toolTip: t('item.fixedPrice.packingPrice'),
name: 'rate3',
component: 'number',
create: true,
createOrder: 4,
width: '55px',
width: '40px',
},
{
name: 'hasMinPrice',
label: t('item.fixedPrice.hasMinPrice'),
labelAbbreviation: 'MP',
labelAbbreviation: t('item.fixedPrice.MP'),
toolTip: t('item.fixedPrice.hasMinPrice'),
label: t('Has min price'),
component: 'checkbox',
@ -131,7 +111,7 @@ const columns = computed(() => [
toolTip: t('item.fixedPrice.minPrice'),
name: 'minPrice',
component: 'number',
width: '55px',
width: '45px',
style: (row) => {
if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' };
},
@ -146,7 +126,7 @@ const columns = computed(() => [
},
create: true,
createOrder: 5,
width: '70px',
width: '55px',
},
{
label: t('item.fixedPrice.ended'),
@ -157,7 +137,7 @@ const columns = computed(() => [
},
create: true,
createOrder: 6,
width: '70px',
width: '55px',
},
{
align: 'center',
@ -172,122 +152,45 @@ const columns = computed(() => [
},
create: true,
format: (row, dashIfEmpty) => dashIfEmpty(row.warehouseName),
width: '80px',
},
{
align: 'right',
name: 'tableActions',
actions: [
{
title: t('delete'),
icon: 'delete',
action: (row) => confirmRemove(row),
title: t('globals.clone'),
icon: 'vn:clone',
action: (row) => openCloneFixedPriceForm(row),
isPrimary: true,
},
],
},
]);
const updateMinPrice = async (value, props) => {
props.row.hasMinPrice = value;
await upsertPrice({
row: props.row,
col: { field: 'hasMinPrice' },
rowIndex: props.rowIndex,
});
const openEditFixedPriceForm = () => {
editFixedPriceForm.value.show();
};
const validations = ({ row }) => {
const requiredFields = [
'itemFk',
'started',
'ended',
'rate2',
'rate3',
'warehouseFk',
];
const isValid = requiredFields.every(
(field) => row[field] !== null && row[field] !== undefined,
);
return isValid;
};
const upsertPrice = async (props, resetMinPrice = false) => {
const isValid = validations({ ...props });
if (!isValid) {
return;
}
const { row } = props;
const changes = tableRef.value.CrudModelRef.getChanges();
if (changes?.updates?.length > 0) {
if (resetMinPrice) row.hasMinPrice = 0;
}
if (!changes.updates && !changes.creates) return;
const data = await upsertFixedPrice(row);
Object.assign(tableRef.value.CrudModelRef.formData[props.rowIndex], data);
notify(t('globals.dataSaved'), 'positive');
tableRef.value.reload();
const openCloneFixedPriceForm = (row) => {
cloneRow.value = (({ itemFk, rate2, rate3, started, ended, warehouseFk }) => ({
itemFk,
rate2,
rate3,
started,
ended,
warehouseFk,
}))(JSON.parse(JSON.stringify(row)));
cloneFixedPriceForm.value.show();
};
async function upsertFixedPrice(row) {
const { data } = await axios.patch('FixedPrices/upsertFixedPrice', row);
data.hasMinPrice = data.hasMinPrice ? 1 : 0;
return data;
}
const openEditTableCellDialog = () => {
editTableCellDialogRef.value.show();
};
const onEditCellDataSaved = async () => {
selectedRows.value = [];
tableRef.value.reload();
};
const removeFuturePrice = async () => {
selectedRows.value.forEach(({ id }) => {
const rowIndex = fixedPrices.value.findIndex(({ id }) => id === id);
removePrice(id, rowIndex);
});
};
function confirmRemove(item, isFuture) {
const promise = async () =>
isFuture ? removeFuturePrice(item.id) : removePrice(item.id);
quasar.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.rowWillBeRemoved'),
message: t('globals.confirmDeletion'),
promise,
},
});
}
const removePrice = async (id) => {
await axios.delete(`FixedPrices/${id}`);
notify(t('globals.dataSaved'), 'positive');
tableRef.value.reload({});
};
const dateStyle = (date) =>
date
? {
color: 'var(--vn-black-text-color)',
}
: { color: dateColor, 'background-color': 'transparent' };
async function cloneFixedPrice(rows) {
for (let row of rows) {
let clonedLine = {
itemFk: row.itemFk,
warehouseFk: row.warehouseFk,
rate2: row.rate2,
rate3: row.rate3,
started: row.started,
ended: row.ended,
};
console.log('clonedLine: ', clonedLine);
await axios.post('FixedPrices', clonedLine);
}
}
</script>
<template>
@ -301,7 +204,7 @@ async function cloneFixedPrice(rows) {
<QBtnGroup push style="column-gap: 10px">
<QBtn
:disable="!hasSelectedRows"
@click="openEditTableCellDialog()"
@click="openEditFixedPriceForm()"
color="primary"
icon="vn:wand"
flat
@ -312,19 +215,6 @@ async function cloneFixedPrice(rows) {
{{ t('Edit fixed price(s)') }}
</QTooltip>
</QBtn>
<QBtn
:disable="!hasSelectedRows"
@click="cloneFixedPrice(selectedRows)"
color="primary"
icon="vn:clone"
flat
:label="t('globals.clone')"
data-cy="FixedPriceToolbarCloneBtn"
>
<QTooltip>
{{ t('Clone fixed price(s)') }}
</QTooltip>
</QBtn>
</QBtnGroup>
</Teleport>
<VnTable
@ -352,11 +242,6 @@ async function cloneFixedPrice(rows) {
auto-load
:beforeSaveFn="(data, getChanges) => beforeSave(data, getChanges, 'FixedPrices')"
>
<template #column-image="{ row }">
<div class="image-wrapper">
<VnImg :id="row.itemFk" class="rounded" />
</div>
</template>
<template #column-hex="{ row }">
<VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" />
</template>
@ -370,14 +255,14 @@ async function cloneFixedPrice(rows) {
</template>
<template #column-started="{ row }">
<div class="editable-text q-pb-xxs">
<QBadge :style="dateStyle(isLower(row?.ended))">
<QBadge class="badge" :style="dateStyle(isLower(row?.ended))">
{{ toDate(row?.started) }}
</QBadge>
</div>
</template>
<template #column-ended="{ row }">
<div class="editable-text q-pb-xxs">
<QBadge :style="dateStyle(isBigger(row?.ended))">
<QBadge class="badge" :style="dateStyle(isBigger(row?.ended))">
{{ toDate(row?.ended) }}
</QBadge>
</div>
@ -386,7 +271,7 @@ async function cloneFixedPrice(rows) {
<VnSelect
url="Items/search"
v-model="data.itemFk"
:label="t('Article')"
:label="t('item.fixedPrice.itemName')"
:fields="['id', 'name']"
:filter-options="['id', 'name']"
option-label="name"
@ -434,8 +319,8 @@ async function cloneFixedPrice(rows) {
</template>
</VnTable>
<QDialog ref="editTableCellDialogRef">
<EditTableCellValueForm
<QDialog ref="editFixedPriceForm">
<EditFixedPriceForm
edit-url="FixedPrices/editFixedPrice"
:rows="selectedRows"
:fields-options="
@ -445,8 +330,12 @@ async function cloneFixedPrice(rows) {
)
"
:beforeSave="beforeSave"
@on-data-saved="tableRef.CrudModelRef.saveChanges()"
/>
</QDialog>
<QDialog ref="cloneFixedPriceForm">
<CloneFixedPriceForm :row="cloneRow" @on-data-saved="tableRef.reload()" />
</QDialog>
</template>
<style lang="scss">
.q-table th,
@ -499,6 +388,13 @@ tbody tr.highlight .q-td {
color: var(--vn-label-color);
}
</style>
<style lang="scss" scoped>
.badge {
background-color: $warning;
}
</style>
<i18n>
es:
Add fixed price: Añadir precio fijado

View File

@ -167,6 +167,8 @@ item:
started: Started
ended: Ended
warehouse: Warehouse
MP: MP
itemName: Item
create:
name: Name
tag: Tag

View File

@ -173,6 +173,8 @@ item:
started: Inicio
ended: Fin
warehouse: Almacén
MP: PM
itemName: Nombre
create:
name: Nombre
tag: Etiqueta