Bug 38201: Migrate vendors to the framework

Signed-off-by: Michaela Sieber <michaela.sieber@kit.edu>
Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Matt Blenkinsop 2025-06-18 16:38:46 +01:00 committed by Lucas Gass
parent 5f198fb3e9
commit 0b1d416b49
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
10 changed files with 881 additions and 318 deletions

View File

@ -1,10 +1,11 @@
<template>
<input
:id="id"
type="number"
inputmode="numeric"
v-model="model"
:placeholder="placeholder"
:required="required"
:size="size"
/>
</template>
@ -16,6 +17,7 @@ export default {
modelValue: Number,
placeholder: String,
required: Boolean,
size: Number | null,
},
emits: ["update:modelValue"],
setup(props, { emit }) {

View File

@ -12,6 +12,7 @@
v-model="resource[attr.name]"
:placeholder="attr.placeholder || attr.label"
:required="attr.required ? true : false"
:size="attr.size"
/>
</template>
<template v-else-if="attr.type == 'text'">

View File

@ -17,27 +17,26 @@
</template>
<script>
import { inject, onBeforeMount, ref } from "vue";
import { inject, onBeforeMount } from "vue";
import NavigationItem from "./NavigationItem.vue";
import VendorMenu from "./Islands/VendorMenu.vue";
import AcquisitionsMenu from "./Islands/AcquisitionsMenu.vue";
import { storeToRefs } from "pinia";
export default {
name: "LeftMenu",
setup(props) {
const navigationStore = inject("navigationStore");
const { leftNavigation } = navigationStore;
const { leftNavigation } = storeToRefs(navigationStore);
const navigationTree = ref(leftNavigation);
onBeforeMount(async () => {
if (props.condition)
navigationTree.value = await props.condition(
navigationTree.value
leftNavigation.value = await props.condition(
leftNavigation.value
);
});
return {
leftNavigation,
navigationTree,
};
},
props: {

View File

@ -48,7 +48,7 @@
</template>
<script>
import { onBeforeMount, ref } from "vue";
import { onBeforeMount, provide, ref } from "vue";
import FormElement from "./FormElement.vue";
export default {
@ -59,6 +59,8 @@ export default {
const resourceRelationshipCount = ref(null);
const options = ref(null);
provide("resourceRelationships", props.resourceRelationships);
const addResourceRelationship = () => {
props.resourceRelationships.push({
...props.newRelationshipDefaultAttrs,

View File

@ -174,10 +174,7 @@ export default {
if (valueKey?.includes(".")) {
return baseElement.accessNestedProperty(valueKey, resource);
}
const displayValue = attr.format(
resource[valueKey] ? resource[valueKey] : valueKey,
resource
);
const displayValue = attr.format(resource[valueKey], resource);
if (displayValue == "Invalid Date") {
return "";
}

View File

@ -25,7 +25,7 @@
</template>
<script>
import { inject } from "vue";
import { inject, onBeforeMount, ref } from "vue";
import Breadcrumbs from "../Breadcrumbs.vue";
import Help from "../Help.vue";
import LeftMenu from "../LeftMenu.vue";
@ -43,8 +43,32 @@ export default {
const mainStore = inject("mainStore");
const { loading, loaded, setError } = mainStore;
const permissionsStore = inject("permissionsStore");
const { userPermissions } = storeToRefs(permissionsStore);
const initialized = ref(false);
onBeforeMount(() => {
loading();
loadAuthorisedValues(authorisedValues.value, vendorStore).then(
() => {
const client = APIClient.acquisition;
client.config.get("vendors").then(result => {
userPermissions.value = result.permissions;
config.value.settings.edifact = result.edifact;
config.value.settings.marcOrderAutomation =
result.marcOrderAutomation;
vendorStore.currencies = result.currencies;
vendorStore.gstValues = result.gst_values.map(gv => {
return {
label: `${Number(gv.option * 100).format_price()}%`,
value: gv.option,
};
});
loaded();
initialized.value = true;
});
}
);
});
return {
vendorStore,
@ -55,38 +79,9 @@ export default {
config,
loadAuthorisedValues,
authorisedValues,
initialized,
};
},
beforeCreate() {
this.loading();
this.loadAuthorisedValues(this.authorisedValues, this.vendorStore).then(
() => {
const client = APIClient.acquisition;
client.config.get("vendors").then(config => {
this.userPermissions = config.permissions;
this.config.settings.edifact = config.edifact;
this.config.settings.marcOrderAutomation =
config.marcOrderAutomation;
this.vendorStore.currencies = config.currencies;
this.vendorStore.gstValues = config.gst_values.map(gv => {
return {
label: `${Number(gv.option * 100).format_price()}%`,
value: gv.option,
};
});
this.loaded();
this.initialized = true;
});
}
);
},
data() {
return {
initialized: false,
};
},
methods: {},
components: {
Breadcrumbs,
Dialog,

View File

@ -1,286 +1,115 @@
<template>
<fieldset class="rows" v-if="display">
<h2>
{{ $__("Contact") }}
</h2>
<span v-if="vendor.contacts.length === 0">{{
$__("No contacts")
}}</span>
<ol v-for="contact in vendor.contacts" :key="contact.id">
<legend>{{ contact.name }}</legend>
<li>
<label>{{ $__("Position") }}:</label>
<span>{{ contact.position }}</span>
</li>
<li>
<label>{{ $__("Phone") }}:</label>
<span>{{ contact.phone }}</span>
</li>
<li>
<label>{{ $__("Alternative phone") }}:</label>
<span>{{ contact.altphone }}</span>
</li>
<li>
<label>{{ $__("Fax") }}:</label>
<span>{{ contact.fax }}</span>
</li>
<li v-if="contact.email">
<label>{{ $__("Email") }}:</label>
<span>
<a :href="`mailto:${contact.email}`" target="_blank">{{
contact.email
}}</a>
</span>
</li>
<li v-if="contact.notes">
<label>{{ $__("Notes") }}:</label>
<span>{{ contact.notes }}</span>
</li>
<li
v-if="
contact.acqprimary ||
contact.orderacquisition ||
contact.claimacquisition
"
>
<span class="label">{{ $__("Acquisitions options") }}:</span>
<ol>
<li v-if="contact.acqprimary">
<span class="contact_details"
><i class="fa fa-check"></i>
{{ $__("Primary acquisitions contact") }}</span
>
</li>
<li v-if="contact.orderacquisition">
<span class="contact_details"
><i class="fa fa-check"></i>
{{ $__("Receives orders") }}</span
>
</li>
<li v-if="contact.claimacquisition">
<span class="contact_details"
><i class="fa fa-check"></i>
{{ $__("Receives claims for late orders") }}</span
>
</li>
</ol>
</li>
<li v-if="contact.serialsprimary || contact.claimissues">
<span class="label">{{ $__("Serials options") }}:</span>
<ol>
<li v-if="contact.serialsprimary">
<span class="contact_details"
><i class="fa fa-check"></i>
{{ $__("Primary serials contact") }}</span
>
</li>
<li v-if="contact.claimissues">
<span class="contact_details"
><i class="fa fa-check"></i>
{{ $__("Receives claims for late issues") }}</span
>
</li>
</ol>
</li>
</ol>
</fieldset>
<fieldset class="rows" v-else>
<legend>{{ $__("Contacts") }}</legend>
<fieldset
class="rows"
v-for="(contact, i) in vendor.contacts"
v-bind:key="i"
:id="`contact_${i}`"
>
<legend>{{ $__("Contact details") }}</legend>
<ol>
<li>
<label :for="`contact_${i}_name`"
>{{ $__("Contact name") }}:</label
>
<input :id="`contact_${i}_name`" v-model="contact.name" />
</li>
<li>
<label :for="`contact_${i}_position`"
>{{ $__("Position") }}:</label
>
<input
:id="`contact_${i}_position`"
v-model="contact.position"
/>
</li>
<li>
<label :for="`contact_${i}_phone`"
>{{ $__("Phone") }}:</label
>
<input :id="`contact_${i}_phone`" v-model="contact.phone" />
</li>
<li>
<label :for="`contact_${i}_altphone`"
>{{ $__("Alternative phone") }}:</label
>
<input
:id="`contact_${i}_altphone`"
v-model="contact.altphone"
/>
</li>
<li>
<label :for="`contact_${i}_fax`">{{ $__("Fax") }}:</label>
<input :id="`contact_${i}_fax`" v-model="contact.fax" />
</li>
<li>
<label :for="`contact_${i}_email`"
>{{ $__("Email") }}:</label
>
<input
:id="`contact_${i}_email`"
v-model="contact.email"
type="email"
/>
</li>
<li>
<label :for="`contact_${i}_notes`"
>{{ $__("Notes") }}:</label
>
<textarea
:id="`contact_${i}_notes`"
v-model="contact.notes"
cols="40"
rows="3"
/>
</li>
</ol>
<div style="display: flex">
<div>
<fieldset class="rows">
<legend>{{ $__("Acquisitions options") }}</legend>
<ol class="radio">
<li>
<label>
<input
type="checkbox"
:id="`contact_acqprimary_${i}`"
class="contact_acqprimary"
v-model="contact.acqprimary"
@change="
handlePrimaryContact(
'acqprimary',
i
)
"
/>
{{ $__("Primary acquisitions contact") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_orderacquisition_${i}`"
class="contact_orderacquisition"
v-model="contact.orderacquisition"
/>
{{ $__("Contact when ordering") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_claimacquisition_${i}`"
class="contact_claimacquisition"
v-model="contact.claimacquisition"
/>
{{ $__("Contact about late orders") }}
</label>
</li>
</ol>
</fieldset>
</div>
<div>
<fieldset class="rows">
<legend>{{ $__("Serials options") }}</legend>
<ol class="radio">
<li>
<label>
<input
type="checkbox"
:id="`contact_serialsprimary_${i}`"
class="contact_serialsprimary"
v-model="contact.serialsprimary"
@change="
handlePrimaryContact(
'serialsprimary',
i
)
"
/>
{{ $__("Primary serials contact") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_claimissues_${i}`"
class="contact_claimissues"
v-model="contact.claimissues"
/>
{{ $__("Contact about late issues") }}
</label>
</li>
</ol>
</fieldset>
</div>
<fieldset class="rows" style="margin-top: 10px">
<div style="display: flex">
<div>
<fieldset class="rows">
<legend>{{ $__("Acquisitions options") }}</legend>
<ol class="radio">
<li>
<label>
<input
type="checkbox"
:id="`contact_acqprimary_${index}`"
class="contact_acqprimary"
v-model="contact.acqprimary"
@change="
handlePrimaryContact(
'acqprimary',
index
)
"
/>
{{ $__("Primary acquisitions contact") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_orderacquisition_${index}`"
class="contact_orderacquisition"
v-model="contact.orderacquisition"
/>
{{ $__("Contact when ordering") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_claimacquisition_${index}`"
class="contact_claimacquisition"
v-model="contact.claimacquisition"
/>
{{ $__("Contact about late orders") }}
</label>
</li>
</ol>
</fieldset>
</div>
<span class="btn btn-default" @click.prevent="deleteContact(i)"
><font-awesome-icon icon="trash" />
{{ $__("Delete contact") }}</span
>
</fieldset>
<span class="btn btn-default" @click="addContact"
><font-awesome-icon icon="plus" />
{{ $__("Add new contact") }}</span
>
<div>
<fieldset class="rows">
<legend>{{ $__("Serials options") }}</legend>
<ol class="radio">
<li>
<label>
<input
type="checkbox"
:id="`contact_serialsprimary_${index}`"
class="contact_serialsprimary"
v-model="contact.serialsprimary"
@change="
handlePrimaryContact(
'serialsprimary',
index
)
"
/>
{{ $__("Primary serials contact") }}
</label>
</li>
<li>
<label>
<input
type="checkbox"
:id="`contact_claimissues_${index}`"
class="contact_claimissues"
v-model="contact.claimissues"
/>
{{ $__("Contact about late issues") }}
</label>
</li>
</ol>
</fieldset>
</div>
</div>
</fieldset>
</template>
<script>
import { inject } from "vue";
export default {
props: {
vendor: Object,
display: Boolean,
contact: Object,
index: Number | null,
},
methods: {
addContact() {
this.vendor.contacts.push({
name: "",
position: "",
email: "",
phone: "",
notes: "",
altphone: "",
fax: "",
acqprimary: false,
orderacquisition: false,
claimacquisition: false,
serialsprimary: false,
claimissues: false,
});
},
handlePrimaryContact(type, i) {
const contact = this.vendor.contacts[i];
setup(props) {
const resourceRelationships = inject("resourceRelationships");
const handlePrimaryContact = (type, index) => {
const contact = resourceRelationships[index];
if (contact[type]) {
this.vendor.contacts.forEach((contact, j) => {
if (j !== i) {
resourceRelationships.forEach((contact, j) => {
if (j !== index) {
contact[type] = false;
}
});
}
},
deleteContact(i) {
this.vendor.contacts.splice(i, 1);
},
};
return {
handlePrimaryContact,
};
},
};
</script>

View File

@ -0,0 +1,733 @@
<template>
<BaseResource
:routeAction="routeAction"
:instancedResource="this"
></BaseResource>
</template>
<script>
import { inject } from "vue";
import BaseResource from "../BaseResource.vue";
import { useBaseResource } from "../../composables/base-resource.js";
import { storeToRefs } from "pinia";
import { APIClient } from "../../fetch/api-client.js";
import { $__ } from "../../i18n";
export default {
props: {
routeAction: String,
},
setup(props, { emit }) {
const vendorStore = inject("vendorStore");
const { currencies, gstValues } = storeToRefs(vendorStore);
const handleContactOptions = value => {
return value ? "Yes" : "No";
};
const baseResource = useBaseResource({
resourceName: "vendor",
nameAttr: "name",
idAttr: "id",
showComponent: "VendorShow",
listComponent: "VendorList",
addComponent: "VendorFormAdd",
editComponent: "VendorFormAddEdit",
apiClient: APIClient.acquisition.vendors,
resourceTableUrl:
APIClient.acquisition.httpClient._baseURL + "vendors",
i18n: {
deleteConfirmationMessage: __(
"Are you sure you want to remove this vendor?"
),
deleteSuccessMessage: __("Vendor %s deleted"),
displayName: __("Vendor"),
editLabel: __("Edit vendor #%s"),
emptyListMessage: __("There are no vendors defined"),
newLabel: __("New vendor"),
},
props,
moduleStore: "vendorStore",
resourceAttrs: [
{
name: "id",
group: $__("Details"),
label: __("ID"),
type: "text",
hideIn: ["Form", "Show"],
},
{
name: "name",
group: $__("Details"),
required: true,
type: "text",
label: __("Vendor name"),
},
{
name: "postal",
group: $__("Details"),
type: "textarea",
label: __("Postal address"),
textAreaRows: 3,
hideIn: ["List"],
},
{
name: "physical",
group: $__("Details"),
type: "textarea",
label: __("Physical address"),
textAreaRows: 3,
hideIn: ["List"],
},
{
name: "phone",
group: $__("Details"),
type: "text",
label: __("Phone"),
hideIn: ["List"],
},
{
name: "fax",
group: $__("Details"),
type: "text",
label: __("Fax"),
hideIn: ["List"],
},
{
name: "url",
group: $__("Details"),
type: "text",
label: __("Website"),
hideIn: ["List"],
},
{
name: "accountnumber",
group: $__("Details"),
type: "text",
label: __("Account number"),
hideIn: ["List"],
},
{
name: "type",
group: $__("Details"),
type: "select",
label: $__("Vendor type"),
avCat: "av_vendor_types",
},
{
name: "aliases",
type: "relationshipWidget",
group: $__("Aliases"),
showElement: {
type: "table",
columnData: "aliases",
hidden: vendor => !!vendor.aliases?.length,
columns: [
{
name: $__("Alias"),
value: "alias",
},
],
},
componentProps: {
resourceRelationships: {
resourceProperty: "aliases",
},
relationshipStrings: {
nameLowerCase: __("alias"),
nameUpperCase: __("Alias"),
namePlural: __("aliases"),
},
newRelationshipDefaultAttrs: {
type: "object",
value: {
alias: "",
},
},
},
relationshipFields: [
{
name: "alias",
type: "text",
label: __("Alias"),
indexRequired: true,
},
],
hideIn: ["List"],
},
{
name: "contacts",
type: "relationshipWidget",
group: $__("Contacts"),
showElement: {
type: "table",
columnData: "contacts",
hidden: vendor => !!vendor.contacts?.length,
columns: [
{
name: $__("Name"),
value: "name",
},
{
name: $__("Position"),
value: "role",
},
{
name: $__("Phone"),
value: "phone",
},
{
name: $__("Alternative phone"),
value: "altphone",
},
{
name: $__("Fax"),
value: "fax",
},
{
name: $__("Email"),
value: "email",
},
{
name: $__("Notes"),
value: "notes",
},
{
name: $__("Primary acquisitions contact"),
value: "acqprimary",
format: handleContactOptions,
},
{
name: $__("Contact when ordering"),
value: "orderacquisition",
format: handleContactOptions,
},
{
name: $__("Contact about late orders"),
value: "claimacquisition",
format: handleContactOptions,
},
{
name: $__("Primary serials contact"),
value: "serialsprimary",
format: handleContactOptions,
},
{
name: $__("Contact about late issues"),
value: "claimissues",
format: handleContactOptions,
},
],
},
componentProps: {
resourceRelationships: {
resourceProperty: "contacts",
},
relationshipStrings: {
nameLowerCase: __("contact"),
nameUpperCase: __("Contact"),
namePlural: __("contacts"),
},
newRelationshipDefaultAttrs: {
type: "object",
value: {
name: "",
position: "",
email: "",
phone: "",
notes: "",
altphone: "",
fax: "",
acqprimary: false,
orderacquisition: false,
claimacquisition: false,
serialsprimary: false,
claimissues: false,
},
},
},
relationshipFields: [
{
name: "name",
type: "text",
label: __("Name"),
indexRequired: true,
},
{
name: "position",
type: "text",
label: __("Position"),
indexRequired: true,
},
{
name: "phone",
type: "text",
label: __("Phone"),
indexRequired: true,
},
{
name: "altphone",
type: "text",
label: __("Alternative phone"),
indexRequired: true,
},
{
name: "fax",
type: "text",
label: __("Fax"),
indexRequired: true,
},
{
name: "email",
type: "text",
label: __("Email"),
indexRequired: true,
},
{
name: "notes",
type: "text",
label: __("Notes"),
indexRequired: true,
},
{
type: "component",
// label: __("User"),
componentPath: "./Vendors/VendorContacts.vue",
indexRequired: true,
componentProps: {
contact: {
type: "resource",
value: null,
},
},
hideIn: ["List", "Show"],
},
],
hideIn: ["List"],
},
{
name: "interfaces",
type: "relationshipWidget",
group: __("Interfaces"),
showElement: {
type: "table",
columnData: "interfaces",
hidden: vendor => !!vendor.interfaces?.length,
columns: [
{
name: $__("Type"),
value: "type",
av: "av_vendor_interface_types",
},
{
name: $__("Name"),
value: "name",
},
{
name: $__("URI"),
value: "uri",
},
{
name: $__("Password"),
value: "password",
},
{
name: $__("Email"),
value: "account_email",
},
{
name: $__("Notes"),
value: "notes",
},
],
},
componentProps: {
resourceRelationships: {
resourceProperty: "interfaces",
},
relationshipStrings: {
nameLowerCase: __("interface"),
nameUpperCase: __("Interface"),
namePlural: __("interfaces"),
},
newRelationshipDefaultAttrs: {
type: "object",
value: {
type: "",
name: "",
uri: "",
login: "",
password: "",
account_email: "",
notes: "",
},
},
},
relationshipFields: [
{
name: "name",
type: "text",
label: __("Name"),
indexRequired: true,
},
{
name: "type",
type: "select",
label: $__("Type"),
avCat: "av_vendor_interface_types",
indexRequired: true,
},
{
name: "uri",
type: "text",
label: __("URI"),
indexRequired: true,
},
{
name: "login",
type: "text",
label: __("Login"),
indexRequired: true,
},
{
name: "password",
type: "text",
label: __("Password"),
indexRequired: true,
},
{
name: "account_email",
type: "text",
label: __("Account email"),
indexRequired: true,
},
{
name: "notes",
type: "text",
label: __("Notes"),
indexRequired: true,
},
],
hideIn: ["List"],
},
{
name: "active",
group: $__("Ordering information"),
label: $__("Vendor is"),
type: "radio",
options: [
{ value: true, description: $__("Active") },
{ value: false, description: $__("Inactive") },
],
value: true,
hideIn: ["List"],
},
{
name: "list_currency",
group: $__("Ordering information"),
type: "select",
label: __("List prices are"),
selectLabel: "currency",
requiredKey: "currency",
options: currencies.value,
defaultValue: null,
hideIn: ["List"],
},
{
name: "invoice_currency",
group: $__("Ordering information"),
type: "select",
selectLabel: "currency",
requiredKey: "currency",
label: __("Invoice prices are"),
options: currencies.value,
defaultValue: null,
},
{
name: "gst",
group: $__("Ordering information"),
label: $__("Tax number registered"),
type: "radio",
options: [
{ value: true, description: $__("Yes") },
{ value: false, description: $__("No") },
],
value: true,
hideIn: ["List"],
},
{
name: "invoice_includes_gst",
group: $__("Ordering information"),
label: $__("Invoice prices"),
type: "radio",
options: [
{ value: true, description: $__("Include tax") },
{ value: false, description: $__("Don't include tax") },
],
value: true,
hideIn: ["List"],
},
{
name: "list_includes_gst",
group: $__("Ordering information"),
label: $__("List prices"),
type: "radio",
options: [
{ value: true, description: $__("Include tax") },
{ value: false, description: $__("Don't include tax") },
],
value: true,
hideIn: ["List"],
},
{
name: "tax_rate",
group: $__("Ordering information"),
type: "select",
label: __("Tax rate"),
options: gstValues.value,
defaultValue: null,
hideIn: ["List"],
},
{
name: "discount",
group: $__("Ordering information"),
type: "number",
label: __("Discount"),
defaultValue: null,
size: 6,
hideIn: ["List"],
},
{
name: "deliverytime",
group: $__("Ordering information"),
type: "number",
label: __("Delivery time (days)"),
hideIn: ["List"],
},
{
name: "notes",
group: $__("Ordering information"),
type: "textarea",
label: __("Notes"),
hideIn: ["List"],
},
{
name: "baskets",
tableColumnDefinition: {
title: $__("Baskets"),
data: "baskets_count",
searchable: false,
orderable: false,
render(data, type, row, meta) {
return row.baskets_count
? '<a href="/cgi-bin/koha/acqui/booksellers.pl?booksellerid=' +
row.id +
'" class="show">' +
escape_str(
__("%s basket(s)").format(
row.baskets_count
)
) +
"</a>"
: escape_str(__("No baskets"));
},
},
hideIn: ["Show", "Form"],
},
{
name: "subscriptions",
tableColumnDefinition: {
title: $__("Subscriptions"),
data: "subscriptions_count",
searchable: false,
orderable: false,
render(data, type, row, meta) {
return row.subscriptions_count
? '<a href="/cgi-bin/koha/serials/serials-search.pl?bookseller_filter=' +
escape_str(row.name) +
"&searched=1" +
'" class="show">' +
escape_str(
__("%s subscription(s)").format(
row.subscriptions_count
)
) +
"</a>"
: escape_str(__("No subscriptions"));
},
},
hideIn: ["Show", "Form"],
},
],
});
const tableOptions = {
options: {
embed: "aliases,baskets+count,subscriptions+count,invoices+count",
},
url: baseResource.getResourceTableUrl(),
table_settings: baseResource.vendorTableSettings,
add_filters: true,
filters_options: {
1: [
{ _id: 0, _str: baseResource.$__("Inactive") },
{ _id: 1, _str: baseResource.$__("Active") },
],
...(baseResource.map_av_dt_filter("av_vendor_types").length && {
2: () => baseResource.map_av_dt_filter("av_vendor_types"),
}),
},
actions: {
0: ["show"],
"-1": [
"edit",
{
delete: {
text: baseResource.$__("Delete"),
icon: "fa fa-trash",
should_display: row =>
row.baskets_count === 0 &&
row.subscriptions_count === 0 &&
row.invoices_count === 0,
},
},
{
receive: {
text: baseResource.$__("Receive shipments"),
icon: "fa fa-inbox",
should_display: row =>
row.active &&
row.baskets_count > 0 &&
// baseResource.isUserPermitted(
// "CAN_user_acquisition_order_receive"
// ),
true,
},
},
],
},
};
const checkContactOrInterface = array => {
return array.reduce((acc, curr) => {
const atLeastOneFieldFilled = Object.keys(curr).some(
key => curr[key]
);
if (atLeastOneFieldFilled) {
acc.push(curr);
}
return acc;
}, []);
};
const verifyDiscountValue = discount => {
return /^[\-]?\d{0,2}(\.\d{0,3})*$/.test(discount);
};
const onSubmit = (e, vendorToSave) => {
e.preventDefault();
const errors = [];
const vendor = JSON.parse(JSON.stringify(vendorToSave));
const vendorId = vendor.id;
delete vendor.id;
if (vendor.physical) {
const addressLines = vendor.physical.split("\n");
if (addressLines.length > 4) {
addressLines.length = 4;
}
addressLines.forEach((line, i) => {
vendor[`address${i + 1}`] = line;
});
}
delete vendor.physical;
delete vendor.subscriptions_count;
delete vendor.baskets;
delete vendor.baskets_count;
delete vendor.subscriptions;
delete vendor.contracts;
delete vendor.invoices_count;
if (vendor.discount && !verifyDiscountValue(vendor.discount))
errors.push(baseResource.$__("Invalid discount value"));
vendor.contacts = checkContactOrInterface(
vendor.contacts.map(
({ id, booksellerid, ...requiredProperties }) =>
requiredProperties
)
);
vendor.interfaces = checkContactOrInterface(
vendor.interfaces.map(
({
vendor_interface_id,
vendor_id,
...requiredProperties
}) => requiredProperties
)
);
baseResource.setWarning(errors.join("<br>"));
if (errors.length) return false;
if (vendorId) {
baseResource.apiClient.update(vendor, vendorId).then(
success => {
baseResource.setMessage(
baseResource.$__("Vendor updated")
);
baseResource.router.push({ name: "VendorList" });
},
error => {}
);
} else {
baseResource.apiClient.create(vendor).then(
success => {
baseResource.setMessage(
baseResource.$__("Vendor created")
);
baseResource.router.push({ name: "VendorList" });
},
error => {}
);
}
};
const afterResourceFetch = (componentData, resource, caller) => {
if (caller === "form") {
let physical = "";
resource.address1 && (physical += resource.address1 + "\n");
resource.address2 && (physical += resource.address2 + "\n");
resource.address3 && (physical += resource.address3 + "\n");
resource.address4 && (physical += resource.address4 + "\n");
componentData.resource.value.physical = physical;
if (!componentData.resource.value.discount)
componentData.resource.value.discount = 0.0;
const decimalPlaces =
componentData.resource.value.discount
.toString()
.split(".")[1]?.length || 0;
if (!decimalPlaces) {
componentData.resource.value.discount =
componentData.resource.value.discount.toFixed(1);
}
}
};
baseResource.created();
return {
...baseResource,
tableOptions,
onSubmit,
afterResourceFetch,
currencies,
};
},
name: "VendorResource",
emits: ["select-resource"],
components: {
BaseResource,
},
};
</script>

View File

@ -122,6 +122,9 @@ export function useBaseElement(instancedElement) {
}
});
}
if (instancedElement.attr.indexRequired) {
acc.index = instancedElement.index;
}
return acc;
}, {});
const attr = show

View File

@ -4,6 +4,7 @@ import Home from "../components/Vendors/Home.vue";
import VendorList from "../components/Vendors/VendorList.vue";
import VendorShow from "../components/Vendors/VendorShow.vue";
import VendorFormAdd from "../components/Vendors/VendorFormAdd.vue";
import ResourceWrapper from "../components/ResourceWrapper.vue";
import { $__ } from "../i18n";
@ -33,31 +34,32 @@ export const routes = [
icon: "fa fa-shopping-cart",
is_end_node: true,
breadcrumbFormat: vendorSearchBreadcrumb,
resource: "Vendors/VendorResource.vue",
children: [
{
path: "",
name: "VendorList",
component: markRaw(VendorList),
component: markRaw(ResourceWrapper),
alternateLeftMenu: "AcquisitionsMenu",
},
{
path: ":id",
name: "VendorShow",
component: markRaw(VendorShow),
component: markRaw(ResourceWrapper),
title: $__("Show vendor"),
alternateLeftMenu: "VendorMenu",
},
{
path: "add",
name: "VendorFormAdd",
component: markRaw(VendorFormAdd),
component: markRaw(ResourceWrapper),
title: $__("Add vendor"),
alternateLeftMenu: "none",
},
{
path: "edit/:id",
name: "VendorFormAddEdit",
component: markRaw(VendorFormAdd),
component: markRaw(ResourceWrapper),
title: $__("Edit vendor"),
alternateLeftMenu: "VendorMenu",
},