0
0
Fork 0

Merge pull request '#6273 create freelance form option' (!347) from 6273-createFreelanceFormOpt into dev

Reviewed-on: verdnatura/salix-front#347
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
This commit is contained in:
Jorge Penadés 2024-06-10 13:34:59 +00:00
commit 9bcfd5c58a
18 changed files with 214 additions and 124 deletions

View File

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto - (Item) => Se añade la opción de añadir un comentario del motivo de hacer una foto
- (Worker) => Se añade la opción de crear un trabajador ajeno a la empresa
## [2418.01] ## [2418.01]

View File

@ -0,0 +1,6 @@
<script setup>
const model = defineModel({ type: Boolean, required: true });
</script>
<template>
<QRadio v-model="model" v-bind="$attrs" dense :dark="true" class="q-mr-sm" />
</template>

View File

@ -18,7 +18,7 @@ onMounted(() => {
const observer = new MutationObserver( const observer = new MutationObserver(
() => () =>
(hasContent.value = (hasContent.value =
actions.value.childNodes.length + data.value.childNodes.length) actions.value?.childNodes?.length + data.value?.childNodes?.length)
); );
if (actions.value) observer.observe(actions.value, opts); if (actions.value) observer.observe(actions.value, opts);
if (data.value) observer.observe(data.value, opts); if (data.value) observer.observe(data.value, opts);

View File

@ -34,7 +34,7 @@ const entryFilter = {
{ {
relation: 'travel', relation: 'travel',
scope: { scope: {
fields: ['id', 'landed', 'agencyModeFk', 'warehouseOutFk'], fields: ['id', 'landed', 'shipped', 'agencyModeFk', 'warehouseOutFk'],
include: [ include: [
{ {
relation: 'agency', relation: 'agency',
@ -125,10 +125,8 @@ watch;
:label="t('entry.descriptor.agency')" :label="t('entry.descriptor.agency')"
:value="entity.travel?.agency?.name" :value="entity.travel?.agency?.name"
/> />
<VnLv <VnLv :label="t('shipped')" :value="toDate(entity.travel?.shipped)" />
:label="t('entry.descriptor.landed')" <VnLv :label="t('landed')" :value="toDate(entity.travel?.landed)" />
:value="toDate(entity.travel?.landed)"
/>
<VnLv <VnLv
:label="t('entry.descriptor.warehouseOut')" :label="t('entry.descriptor.warehouseOut')"
:value="entity.travel?.warehouseOut?.name" :value="entity.travel?.warehouseOut?.name"

View File

@ -221,10 +221,7 @@ const fetchEntryBuys = async () => {
:value="entry.travel.agency.name" :value="entry.travel.agency.name"
/> />
<VnLv <VnLv :label="t('shipped')" :value="toDate(entry.travel.shipped)" />
:label="t('entry.summary.travelShipped')"
:value="toDate(entry.travel.shipped)"
/>
<VnLv <VnLv
:label="t('entry.summary.travelWarehouseOut')" :label="t('entry.summary.travelWarehouseOut')"
@ -236,10 +233,7 @@ const fetchEntryBuys = async () => {
v-model="entry.travel.isDelivered" v-model="entry.travel.isDelivered"
:disable="true" :disable="true"
/> />
<VnLv <VnLv :label="t('landed')" :value="toDate(entry.travel.landed)" />
:label="t('entry.summary.travelLanded')"
:value="toDate(entry.travel.landed)"
/>
<VnLv <VnLv
:label="t('entry.summary.travelWarehouseIn')" :label="t('entry.summary.travelWarehouseIn')"

View File

@ -78,10 +78,7 @@ onMounted(async () => {
</QIcon> </QIcon>
</template> </template>
<template #list-items> <template #list-items>
<VnLv <VnLv :label="t('landed')" :value="toDate(row.landed)" />
:label="t('entry.list.landed')"
:value="toDate(row.landed)"
/>
<VnLv <VnLv
:label="t('entry.list.booked')" :label="t('entry.list.booked')"
:value="!!row.isBooked" :value="!!row.isBooked"

View File

@ -6,3 +6,5 @@ entryFilter:
filter: filter:
search: General search search: General search
reference: Reference reference: Reference
landed: Landed
shipped: Shipped

View File

@ -8,3 +8,6 @@ entryFilter:
filter: filter:
search: Búsqueda general search: Búsqueda general
reference: Referencia reference: Referencia
landed: F. llegada
shipped: F. salida

View File

@ -19,7 +19,8 @@ const { t } = useI18n();
const dms = ref({}); const dms = ref({});
const route = useRoute(); const route = useRoute();
const editDownloadDisabled = ref(false); const editDownloadDisabled = ref(false);
const invoiceIn = computed(() => useArrayData().store.data); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data);
const userConfig = ref(null); const userConfig = ref(null);
const invoiceId = computed(() => +route.params.id); const invoiceId = computed(() => +route.params.id);

View File

@ -12,7 +12,7 @@ const { push, currentRoute } = useRouter();
const { t } = useI18n(); const { t } = useI18n();
const invoiceId = +currentRoute.value.params.id; const invoiceId = +currentRoute.value.params.id;
const arrayData = useArrayData(currentRoute.value.meta.moduleName); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref(); const invoiceInCorrectionRef = ref();
const filter = { const filter = {

View File

@ -27,9 +27,9 @@ const quasar = useQuasar();
const { hasAny } = useRole(); const { hasAny } = useRole();
const { t } = useI18n(); const { t } = useI18n();
const { openReport, sendEmail } = usePrintService(); const { openReport, sendEmail } = usePrintService();
const { store } = useArrayData(currentRoute.value.meta.moduleName); const arrayData = useArrayData();
const invoiceIn = computed(() => store.data); const invoiceIn = computed(() => arrayData.store.data);
const cardDescriptorRef = ref(); const cardDescriptorRef = ref();
const correctionDialogRef = ref(); const correctionDialogRef = ref();
const entityId = computed(() => $props.id || +currentRoute.value.params.id); const entityId = computed(() => $props.id || +currentRoute.value.params.id);
@ -184,7 +184,7 @@ async function toUnbook() {
: t('isNotLinked', { bookEntry }); : t('isNotLinked', { bookEntry });
quasar.notify({ type, message }); quasar.notify({ type, message });
if (!isLinked) store.data.isBooked = false; if (!isLinked) arrayData.store.data.isBooked = false;
} }
async function deleteInvoice() { async function deleteInvoice() {

View File

@ -11,7 +11,8 @@ import { useArrayData } from 'src/composables/useArrayData';
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const currency = computed(() => useArrayData().store.data?.currency?.code); const arrayData = useArrayData();
const currency = computed(() => arrayData.store.data?.currency?.code);
const invoceInIntrastat = ref([]); const invoceInIntrastat = ref([]);
const rowsSelected = ref([]); const rowsSelected = ref([]);
const countries = ref([]); const countries = ref([]);

View File

@ -14,9 +14,10 @@ import VnTitle from 'src/components/common/VnTitle.vue';
const props = defineProps({ id: { type: [Number, String], default: 0 } }); const props = defineProps({ id: { type: [Number, String], default: 0 } });
const { t } = useI18n(); const { t } = useI18n();
const route = useRoute(); const route = useRoute();
const arrayData = useArrayData();
const entityId = computed(() => props.id || +route.params.id); const entityId = computed(() => props.id || +route.params.id);
const invoiceIn = computed(() => useArrayData().store.data); const invoiceIn = computed(() => arrayData.store.data);
const currency = computed(() => invoiceIn.value?.currency?.code); const currency = computed(() => invoiceIn.value?.currency?.code);
const invoiceInUrl = ref(); const invoiceInUrl = ref();
const amountsNotMatch = ref(null); const amountsNotMatch = ref(null);

View File

@ -1,59 +1,29 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue'; import { onBeforeMount, ref } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import axios from 'axios';
import FetchData from 'components/FetchData.vue'; import { useUserConfig } from 'src/composables/useUserConfig';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue';
import FetchData from 'components/FetchData.vue';
import { useUserConfig } from 'src/composables/useUserConfig'; import FormModel from 'components/FormModel.vue';
import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue';
import VnRadio from 'src/components/common/VnRadio.vue';
const { t } = useI18n(); const { t } = useI18n();
const workerConfigFilter = {
field: ['payMethodFk'],
};
const newWorkerForm = ref({
companyFk: null,
payMethodFk: null,
firstName: null,
lastNames: null,
birth: null,
fi: null,
code: null,
phone: null,
postcode: null,
provinceFk: null,
city: null,
street: null,
name: null,
email: null,
bossFk: null,
iban: null,
bankEntityFk: null,
});
const companiesOptions = ref([]); const companiesOptions = ref([]);
const workersOptions = ref([]); const workersOptions = ref([]);
const payMethodsOptions = ref([]); const payMethodsOptions = ref([]);
const bankEntitiesOptions = ref([]); const bankEntitiesOptions = ref([]);
const postcodesOptions = ref([]); const postcodesOptions = ref([]);
const formData = ref({ isFreelance: false });
const onFetchWorkerConfig = (workerConfig) => { const defaultPayMethod = ref(0);
newWorkerForm.value.payMethodFk = workerConfig.payMethodFk;
};
const onBankEntityCreated = (data) => {
bankEntitiesOptions.value.push(data);
};
function handleLocation(data, location) { function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {}; const { town, code, provinceFk, countryFk } = location ?? {};
@ -63,20 +33,18 @@ function handleLocation(data, location) {
data.countryFk = countryFk; data.countryFk = countryFk;
} }
onMounted(async () => { onBeforeMount(async () => {
const userInfo = await useUserConfig().fetch(); const userInfo = await useUserConfig().fetch();
newWorkerForm.value = { companyFk: userInfo.companyFk }; formData.value.companyFk = userInfo.companyFk;
const { data } = await axios.get('WorkerConfigs/findOne', {
params: { field: ['payMethodFk'] },
});
defaultPayMethod.value = data.payMethodFk;
formData.value.payMethodFk = defaultPayMethod.value;
}); });
</script> </script>
<template> <template>
<FetchData
url="WorkerConfigs/findOne"
@on-fetch="(data) => onFetchWorkerConfig(data)"
:filter="workerConfigFilter"
auto-load
/>
<FetchData <FetchData
url="Companies" url="Companies"
@on-fetch="(data) => (companiesOptions = data)" @on-fetch="(data) => (companiesOptions = data)"
@ -97,16 +65,31 @@ onMounted(async () => {
@on-fetch="(data) => (bankEntitiesOptions = data)" @on-fetch="(data) => (bankEntitiesOptions = data)"
auto-load auto-load
/> />
<QPage> <QPage>
<VnSubToolbar /> <VnSubToolbar>
<template #st-data>
<VnRadio
v-model="formData.isFreelance"
:val="false"
:label="`${t('Internal')}`"
@update:model-value="formData.payMethodFk = defaultPayMethod"
/>
<VnRadio
v-model="formData.isFreelance"
:val="true"
:label="`${t('External')}`"
@update:model-value="delete formData.payMethodFk"
/>
</template>
</VnSubToolbar>
<FormModel <FormModel
url-create="Workers/new" url-create="Workers/new"
model="worker" model="worker"
:form-initial-data="newWorkerForm" :form-initial-data="formData"
@on-data-saved="({ id }) => $router.push({ path: `/worker/${id}` })"
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<VnInput <VnInput
v-model="data.firstName" v-model="data.firstName"
:label="t('worker.create.name')" :label="t('worker.create.name')"
@ -117,47 +100,13 @@ onMounted(async () => {
:label="t('worker.create.lastName')" :label="t('worker.create.lastName')"
:rules="validate('Worker.lastNames')" :rules="validate('Worker.lastNames')"
/> />
<VnInputDate
v-model="data.birth"
:label="t('worker.create.birth')"
:rules="validate('Worker.birth')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
v-model="data.fi"
:label="t('worker.create.fi')"
:rules="validate('Worker.fi')"
/>
<VnInput <VnInput
v-model="data.code" v-model="data.code"
:label="t('worker.create.code')" :label="t('worker.create.code')"
:rules="validate('Worker.code')" :rules="validate('Worker.code')"
/> />
<VnInput
v-model="data.phone"
:label="t('worker.create.phone')"
:rules="validate('Worker.phone')"
/>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
>
</VnLocation>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput
:label="t('worker.create.street')"
v-model="data.street"
:rules="validate('Worker.street')"
/>
</VnRow>
<VnRow class="row q-gutter-md q-mb-md">
<VnInput <VnInput
v-model="data.name" v-model="data.name"
:label="t('worker.create.webUser')" :label="t('worker.create.webUser')"
@ -169,7 +118,7 @@ onMounted(async () => {
:rules="validate('Worker.email')" :rules="validate('Worker.email')"
/> />
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<VnSelect <VnSelect
:label="t('worker.create.company')" :label="t('worker.create.company')"
v-model="data.companyFk" v-model="data.companyFk"
@ -201,7 +150,45 @@ onMounted(async () => {
</template> </template>
</VnSelect> </VnSelect>
</VnRow> </VnRow>
<VnRow class="row q-gutter-md q-mb-md"> <VnRow>
<VnInput
v-model="data.fi"
:label="t('worker.create.fi')"
:rules="validate('Worker.fi')"
/>
<VnInputDate
v-model="data.birth"
:label="t('worker.create.birth')"
:rules="validate('Worker.birth')"
:disable="formData.isFreelance"
/>
<VnInput
v-model="data.phone"
:label="t('worker.create.phone')"
:rules="validate('Worker.phone')"
:disable="formData.isFreelance"
/>
</VnRow>
<VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:roles-allowed-to-create="['deliveryAssistant']"
:options="postcodesOptions"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
:disable="formData.isFreelance"
>
</VnLocation>
</VnRow>
<VnRow>
<VnInput
:label="t('worker.create.street')"
v-model="data.street"
:rules="validate('Worker.street')"
:disable="formData.isFreelance"
/>
</VnRow>
<VnRow>
<VnSelect <VnSelect
:label="t('worker.create.payMethods')" :label="t('worker.create.payMethods')"
v-model="data.payMethodFk" v-model="data.payMethodFk"
@ -211,11 +198,14 @@ onMounted(async () => {
map-options map-options
hide-selected hide-selected
:rules="validate('Worker.payMethodFk')" :rules="validate('Worker.payMethodFk')"
:disable="formData.isFreelance"
@update:model-value="(val) => !val && delete formData.payMethodFk"
/> />
<VnInput <VnInput
v-model="data.iban" v-model="data.iban"
:label="t('worker.create.iban')" :label="t('worker.create.iban')"
:rules="validate('Worker.iban')" :rules="validate('Worker.iban')"
:disable="formData.isFreelance"
> >
<template #append> <template #append>
<QIcon name="info" class="cursor-info"> <QIcon name="info" class="cursor-info">
@ -232,10 +222,11 @@ onMounted(async () => {
hide-selected hide-selected
:roles-allowed-to-create="['salesAssistant', 'hr']" :roles-allowed-to-create="['salesAssistant', 'hr']"
:rules="validate('Worker.bankEntity')" :rules="validate('Worker.bankEntity')"
:disable="formData.isFreelance"
> >
<template #form> <template #form>
<CreateBankEntityForm <CreateBankEntityForm
@on-data-saved="onBankEntityCreated($event)" @on-data-saved="(data) => bankEntitiesOptions.push(data)"
/> />
</template> </template>
<template #option="scope"> <template #option="scope">

View File

@ -1,3 +1,5 @@
Search worker: Buscar trabajador Search worker: Buscar trabajador
You can search by worker id or name: Puedes buscar por id o nombre del trabajador You can search by worker id or name: Puedes buscar por id o nombre del trabajador
Locker: Taquilla Locker: Taquilla
Internal: Interno
External: Externo

View File

@ -0,0 +1,59 @@
describe('WorkerCreate', () => {
const externalRadio = '.q-toolbar .q-radio:nth-child(2)';
const notification = '.q-notification__message';
const developerBossId = 120;
const internal = {
Fi: { val: '78457139E' },
'Web user': { val: 'manolo' },
Name: { val: 'Manolo' },
'Last name': { val: 'Hurtado' },
'Personal email': { val: 'manolo@mydomain.com' },
Street: { val: 'S/ DEFAULTWORKERSTREET' },
Location: { val: 1, type: 'select' },
Phone: { val: '123456789' },
'Worker code': { val: 'DWW' },
Boss: { val: developerBossId, type: 'select' },
Birth: { val: '2022-12-11T23:00:00.000Z', type: 'date', day: 11 },
};
const external = {
Fi: { val: 'Z4531219V' },
'Web user': { val: 'pepe' },
Name: { val: 'PEPE' },
'Last name': { val: 'GARCIA' },
'Personal email': { val: 'pepe@gmail.com' },
'Worker code': { val: 'PG' },
Boss: { val: developerBossId, type: 'select' },
};
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('hr');
cy.visit('/#/worker/create');
});
it('should throw an error if a pay method has not been selected', () => {
cy.fillInForm(internal);
cy.saveCard();
cy.get(notification).should(
'contains.text',
'That payment method requires an IBAN'
);
});
it('should create an internal', () => {
cy.fillInForm({
...internal,
'Pay method': { val: 'PayMethod one', type: 'select' },
});
cy.saveCard();
cy.get(notification).should('contains.text', 'Data created');
});
it('should create an external', () => {
cy.get(externalRadio).click();
cy.fillInForm(external);
cy.saveCard();
cy.get(notification).should('contains.text', 'Data created');
});
});

View File

@ -8,13 +8,13 @@ describe('WorkerList', () => {
}); });
it('should load workers', () => { it('should load workers', () => {
cy.get(workerFieldNames).eq(0).should('have.text', 'jessicajones'); cy.get(workerFieldNames).eq(2).should('have.text', 'jessicajones');
cy.get(workerFieldNames).eq(1).should('have.text', 'brucebanner'); cy.get(workerFieldNames).eq(3).should('have.text', 'brucebanner');
cy.get(workerFieldNames).eq(2).should('have.text', 'charlesxavier'); cy.get(workerFieldNames).eq(4).should('have.text', 'charlesxavier');
}); });
it('should open the worker summary', () => { it('should open the worker summary', () => {
cy.openListSummary(0); cy.openListSummary(2);
cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones'); cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones');
cy.get('.summary .header-link') cy.get('.summary .header-link')
.eq(0) .eq(0)

View File

@ -86,6 +86,36 @@ Cypress.Commands.add('selectOption', (selector, option) => {
cy.get('.q-menu .q-item').contains(option).click(); cy.get('.q-menu .q-item').contains(option).click();
}); });
Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
const days = '.q-date__calendar-days .q-date__calendar-item--in';
cy.waitForElement('.q-form > .q-card');
cy.get(`${form} input`).each(([el]) => {
cy.wrap(el)
.invoke('attr', 'aria-label')
.then((ariaLabel) => {
const field = obj[ariaLabel];
if (!field) return;
const { type, val, day } = field;
switch (type) {
case 'select':
cy.wrap(el).type(val);
cy.get('.q-menu .q-item').contains(val).click();
break;
case 'date':
cy.wrap(el).click();
cy.get(days)
.eq(day ? day - 1 : 0)
.click();
break;
default:
cy.wrap(el).type(val);
break;
}
});
});
});
Cypress.Commands.add('checkOption', (selector) => { Cypress.Commands.add('checkOption', (selector) => {
cy.get(selector).find('.q-checkbox__inner').click(); cy.get(selector).find('.q-checkbox__inner').click();
}); });
@ -198,11 +228,15 @@ Cypress.Commands.add('closeSideMenu', (element) => {
Cypress.Commands.add('clearSearchbar', (element) => { Cypress.Commands.add('clearSearchbar', (element) => {
if (element) cy.waitForElement(element); if (element) cy.waitForElement(element);
cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').clear(); cy.get(
'#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input'
).clear();
}); });
Cypress.Commands.add('writeSearchbar', (value) => { Cypress.Commands.add('writeSearchbar', (value) => {
cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(value); cy.get('#searchbar > form > div:nth-child(1) > label > div:nth-child(1) input').type(
value
);
}); });
Cypress.Commands.add('validateContent', (selector, expectedValue) => { Cypress.Commands.add('validateContent', (selector, expectedValue) => {
cy.get(selector).should('have.text', expectedValue); cy.get(selector).should('have.text', expectedValue);