Bugzilla – Attachment 156658 Details for
Bug 30708
Creation of a new 'Preservation' module
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 30708: Set default values for items added in batch to a train
Bug-30708-Set-default-values-for-items-added-in-ba.patch (text/plain), 22.12 KB, created by
Marcel de Rooy
on 2023-10-07 06:41:10 UTC
(
hide
)
Description:
Bug 30708: Set default values for items added in batch to a train
Filename:
MIME Type:
Creator:
Marcel de Rooy
Created:
2023-10-07 06:41:10 UTC
Size:
22.12 KB
patch
obsolete
>From 461bd5f689f2991611f48d6945e1ce7fe2ed6518 Mon Sep 17 00:00:00 2001 >From: Jonathan Druart <jonathan.druart@bugs.koha-community.org> >Date: Tue, 25 Apr 2023 12:12:01 +0200 >Subject: [PATCH] Bug 30708: Set default values for items added in batch to a > train >Content-Type: text/plain; charset=utf-8 > >Technical notes: Ideally we would have split TrainsFormAddItem to make some part >reusable, but it turned out into a complicated component that would have >been hard to maintain. It seems easier to have two different components. >Ideas to improve this area are welcome! > >Sponsored-by: BULAC - http://www.bulac.fr/ > >Signed-off-by: BULAC - http://www.bulac.fr/ >Signed-off-by: Heather Hernandez <heather_hernandez@nps.gov> >Signed-off-by: Laurence Rault <laurence.rault@biblibre.com> > >Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl> >--- > Koha/Preservation/Train.pm | 4 +- > .../Preservation/TrainsFormAddItems.vue | 285 ++++++++++++++++++ > .../components/Preservation/WaitingList.vue | 41 +-- > .../prog/js/vue/routes/preservation.js | 12 + > t/cypress/integration/Preservation/Trains.ts | 60 ++++ > .../integration/Preservation/WaitingList.ts | 46 --- > 6 files changed, 367 insertions(+), 81 deletions(-) > create mode 100644 koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsFormAddItems.vue > >diff --git a/Koha/Preservation/Train.pm b/Koha/Preservation/Train.pm >index 7ecd7908b4..46abf54682 100644 >--- a/Koha/Preservation/Train.pm >+++ b/Koha/Preservation/Train.pm >@@ -120,7 +120,9 @@ sub add_items { > my @added_items; > for my $train_item (@$train_items) { > try { >- push @added_items, $self->add_item($train_item); >+ my $added_item = $self->add_item($train_item); >+ $added_item->attributes($train_item->{attributes}); >+ push @added_items, $added_item; > } catch { > > # FIXME Do we rollback and raise an error or just skip it? >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsFormAddItems.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsFormAddItems.vue >new file mode 100644 >index 0000000000..814ab7eb9b >--- /dev/null >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsFormAddItems.vue >@@ -0,0 +1,285 @@ >+<template> >+ <div v-if="!initialized">{{ $__("Loading") }}</div> >+ <div v-else id="trains_add_items"> >+ <h2>{{ $__("Add new items to %s").format(train.name) }}</h2> >+ <form @submit="onSubmit($event)"> >+ <fieldset class="rows"> >+ <ol> >+ <li> >+ <label for="itemnumbers" >+ >{{ $__("Itemnumbers") }}:</label >+ > >+ <span>{{ items.map(i => i.item_id).join(", ") }}</span> >+ </li> >+ <li> >+ <label for="processing" >+ >{{ $__("Processing") }}: >+ </label> >+ <v-select >+ id="processing" >+ label="name" >+ v-model="processing_id" >+ @option:selected="refreshAttributes(1)" >+ :reduce="p => p.processing_id" >+ :options="processings" >+ :clearable="false" >+ /> >+ </li> >+ <li >+ class="attribute" >+ v-for="(attribute, counter) in attributes" >+ v-bind:key="counter" >+ > >+ <label :for="`attribute_${counter}`" >+ >{{ attribute.name }}: >+ </label> >+ <span v-if="attribute.type == 'authorised_value'"> >+ <v-select >+ :id="`attribute_${counter}`" >+ v-model="attribute.value" >+ label="description" >+ :reduce="av => av.value" >+ :options="av_options[attribute.option_source]" >+ taggable >+ :create-option=" >+ attribute => ({ >+ value: attribute, >+ description: attribute, >+ }) >+ " >+ /> >+ </span> >+ <span v-else-if="attribute.type == 'free_text'"> >+ <input >+ :id="`attribute_${counter}`" >+ v-model="attribute.value" >+ /> >+ </span> >+ <span v-else-if="attribute.type == 'db_column'"> >+ {{ >+ $__( >+ "Cannot be edited now, the value will be retrieved from %s" >+ ).format(attribute.option_source) >+ }} >+ </span> >+ <a >+ v-if=" >+ attribute.type != 'db_column' && >+ (attributes.length == counter + 1 || >+ attributes[counter + 1] >+ .processing_attribute_id != >+ attribute.processing_attribute_id) >+ " >+ class="btn btn-link" >+ @click=" >+ addAttribute(attribute.processing_attribute_id) >+ " >+ ><font-awesome-icon icon="plus" /> >+ {{ $__("Add") }}</a >+ > >+ <a >+ v-else-if="attribute.type != 'db_column'" >+ class="btn btn-link" >+ @click="removeAttribute(counter)" >+ ><font-awesome-icon icon="minus" /> >+ {{ $__("Remove") }}</a >+ > >+ </li> >+ </ol> >+ </fieldset> >+ <fieldset class="action"> >+ <input type="submit" value="Submit" /> >+ <router-link >+ to="/cgi-bin/koha/preservation/trains" >+ role="button" >+ class="cancel" >+ >{{ $__("Cancel") }}</router-link >+ > >+ </fieldset> >+ </form> >+ </div> >+</template> >+ >+<script> >+import { inject } from "vue" >+import { APIClient } from "../../fetch/api-client" >+ >+export default { >+ setup() { >+ const { setMessage, setWarning, loading, loaded } = inject("mainStore") >+ return { >+ setMessage, >+ setWarning, >+ loading, >+ loaded, >+ api_mappings, >+ } >+ }, >+ data() { >+ return { >+ train: { >+ train_id: null, >+ name: "", >+ description: "", >+ }, >+ items: [], >+ train_items: [], >+ processings: [], >+ processing: null, >+ processing_id: null, >+ initialized: false, >+ av_options: {}, >+ attributes: [], >+ } >+ }, >+ beforeCreate() { >+ const client = APIClient.preservation >+ client.processings >+ .getAll() >+ .then(processings => (this.processings = processings)) >+ }, >+ beforeRouteEnter(to, from, next) { >+ next(vm => { >+ vm.train = vm >+ .getTrain(to.params.train_id) >+ .then(() => >+ vm >+ .getItems(to.params.item_ids.split(",")) >+ .then(() => >+ vm >+ .refreshAttributes() >+ .then(() => (vm.initialized = true)) >+ ) >+ ) >+ }) >+ }, >+ methods: { >+ async getTrain(train_id) { >+ const client = APIClient.preservation >+ await client.trains.get(train_id).then( >+ train => { >+ this.train = train >+ this.processing_id = train.default_processing_id >+ }, >+ error => {} >+ ) >+ }, >+ async getItems(item_ids) { >+ const client = APIClient.item >+ let q = { "me.item_id": item_ids } >+ await client.items >+ .getAll(q, { headers: { "x-koha-embed": "biblio" } }) >+ .then( >+ items => { >+ this.items = items >+ }, >+ error => {} >+ ) >+ }, >+ columnApiMapping(item, db_column) { >+ let table_col = db_column.split(".") >+ let table = table_col[0] >+ let col = table_col[1] >+ let api_attribute = this.api_mappings[table][col] || col >+ return table == "biblio" || table == "biblioitems" >+ ? item.biblio[api_attribute] >+ : item[api_attribute] >+ }, >+ async refreshAttributes() { >+ this.loading() >+ >+ const client = APIClient.preservation >+ await client.processings.get(this.processing_id).then( >+ processing => (this.processing = processing), >+ error => {} >+ ) >+ this.attributes = [] >+ this.processing.attributes.forEach(attribute => { >+ this.attributes.push({ >+ processing_attribute_id: attribute.processing_attribute_id, >+ name: attribute.name, >+ type: attribute.type, >+ option_source: attribute.option_source, >+ value: "", >+ }) >+ }) >+ const client_av = APIClient.authorised_values >+ let av_cat_array = this.processing.attributes >+ .filter(attribute => attribute.type == "authorised_value") >+ .map(attribute => attribute.option_source) >+ >+ client_av.values >+ .getCategoriesWithValues([ >+ ...new Set(av_cat_array.map(av_cat => '"' + av_cat + '"')), >+ ]) // unique >+ .then(av_categories => { >+ av_cat_array.forEach(av_cat => { >+ let av_match = av_categories.find( >+ element => element.category_name == av_cat >+ ) >+ this.av_options[av_cat] = av_match.authorised_values >+ }) >+ }) >+ .then(() => this.loaded()) >+ }, >+ addAttribute(processing_attribute_id) { >+ let last_index = this.attributes.findLastIndex( >+ attribute => >+ attribute.processing_attribute_id == processing_attribute_id >+ ) >+ let new_attribute = (({ value, ...keepAttrs }) => keepAttrs)( >+ this.attributes[last_index] >+ ) >+ this.attributes.splice(last_index + 1, 0, new_attribute) >+ }, >+ removeAttribute(counter) { >+ this.attributes.splice(counter, 1) >+ }, >+ onSubmit(e) { >+ e.preventDefault() >+ >+ let train_items = this.items.map(item => { >+ return { >+ item_id: item.item_id, >+ processing_id: this.processing_id, >+ attributes: this.attributes.map(a => { >+ let value = >+ a.type == "db_column" >+ ? this.columnApiMapping(item, a.option_source) >+ : a.value >+ return { >+ processing_attribute_id: a.processing_attribute_id, >+ value, >+ } >+ }), >+ } >+ }) >+ >+ const client = APIClient.preservation >+ client.train_items.createAll(train_items, this.train.train_id).then( >+ result => { >+ if (result.length) { >+ this.setMessage( >+ this.$__( >+ "%s items have been added to train %s." >+ ).format(result.length, this.train.train_id) >+ ) >+ >+ this.$router.push( >+ "/cgi-bin/koha/preservation/trains/" + >+ this.train.train_id >+ ) >+ } else { >+ this.setMessage( >+ this.$__("No items have been added to the train.") >+ ) >+ } >+ }, >+ error => {} >+ ) >+ }, >+ }, >+ components: {}, >+ name: "TrainsFormAddItems", >+} >+</script> >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/WaitingList.vue b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/WaitingList.vue >index a1d627ecd5..a290ca5615 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/WaitingList.vue >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/WaitingList.vue >@@ -189,40 +189,13 @@ export default { > }, > addItemsToTrain: function (e) { > e.preventDefault() >- this.loading() >- let item_ids = Object.values(this.last_items) >- const client = APIClient.preservation >- client.train_items >- .createAll(item_ids, this.train_id_selected_for_add) >- .then( >- result => { >- if (result.length) { >- this.setMessage( >- this.$__( >- "%s items have been added to train %s." >- ).format( >- result.length, >- this.train_id_selected_for_add >- ), >- true >- ) >- } else { >- this.setMessage( >- this.$__( >- "No items have been added to the train." >- ) >- ) >- } >- >- this.$refs.table.redraw( >- "/api/v1/preservation/waiting-list/items" >- ) >- this.show_modal_add_to_train = false >- this.last_items = [] >- }, >- error => {} >- ) >- .then(() => this.loaded()) >+ let item_ids = this.last_items.map(i => i.item_id) >+ this.$router.push( >+ "/cgi-bin/koha/preservation/trains/" + >+ this.train_id_selected_for_add + >+ "/items/add/" + >+ item_ids.join(",") >+ ) > }, > addItemsToWaitingList: function (e) { > e.preventDefault() >diff --git a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/preservation.js b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/preservation.js >index 55325bdb57..f95d3705e1 100644 >--- a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/preservation.js >+++ b/koha-tmpl/intranet-tmpl/prog/js/vue/routes/preservation.js >@@ -3,6 +3,7 @@ import TrainsList from "../components/Preservation/TrainsList.vue"; > import TrainsShow from "../components/Preservation/TrainsShow.vue"; > import TrainsFormAdd from "../components/Preservation/TrainsFormAdd.vue"; > import TrainsFormAddItem from "../components/Preservation/TrainsFormAddItem.vue"; >+import TrainsFormAddItems from "../components/Preservation/TrainsFormAddItems.vue"; > import WaitingList from "../components/Preservation/WaitingList.vue"; > import Settings from "../components/Preservation/Settings.vue"; > import SettingsProcessingsShow from "../components/Preservation/SettingsProcessingsShow.vue"; >@@ -117,6 +118,17 @@ export const routes = [ > ), > }, > }, >+ { >+ path: "add/:item_ids", >+ component: TrainsFormAddItems, >+ meta: { >+ breadcrumb: () => >+ build_breadcrumb( >+ breadcrumb_paths.trains, >+ "Add items to train" // $t("Add items to train") >+ ), >+ }, >+ }, > { > path: "edit/:train_item_id", > component: TrainsFormAddItem, >diff --git a/t/cypress/integration/Preservation/Trains.ts b/t/cypress/integration/Preservation/Trains.ts >index b7a515c76b..858d523ebd 100644 >--- a/t/cypress/integration/Preservation/Trains.ts >+++ b/t/cypress/integration/Preservation/Trains.ts >@@ -596,4 +596,64 @@ describe("Trains", () => { > }); > }); > }); >+ >+ it("Add to waiting list then add to a train", () => { >+ let train = get_train(); >+ let processing = get_processings()[0]; >+ cy.intercept("GET", "/api/v1/preservation/trains*", [train]); >+ cy.intercept("GET", "/api/v1/preservation/trains/1", train); >+ cy.intercept("GET", "/api/v1/preservation/processings/1", processing); >+ cy.visit("/cgi-bin/koha/preservation/waiting-list"); >+ >+ cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { >+ statusCode: 200, >+ body: get_items(), >+ headers: { >+ "X-Base-Total-Count": "2", >+ "X-Total-Count": "2", >+ }, >+ }).as("get-items"); >+ cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [ >+ { item_id: 1 }, >+ { item_id: 2 }, >+ ]); >+ cy.get("#waiting-list").contains("Add to waiting list").click(); >+ cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3"); >+ cy.contains("Submit").click(); >+ cy.wait("@get-items"); >+ cy.get("main div[class='dialog message']").contains( >+ "2 new items added." >+ ); >+ cy.contains("Add last 2 items to a train").click(); >+ cy.get("#train_id .vs__search").type(train.name + "{enter}"); >+ cy.intercept("GET", "/api/v1/items*", { >+ statusCode: 200, >+ body: get_items().filter( >+ item => item.item_id == 1 || item.item_id == 2 >+ ), >+ headers: { >+ "X-Base-Total-Count": "2", >+ "X-Total-Count": "2", >+ }, >+ }); >+ cy.intercept( >+ "POST", >+ "/api/v1/preservation/trains/" + train.train_id + "/items/batch", >+ req => { >+ req.reply({ >+ statusCode: 201, >+ body: req.body, >+ }); >+ } >+ ); >+ cy.contains("Submit").click(); // Select train >+ train.items = get_train_items().filter( >+ train_item => train_item.item_id == 1 || train_item.item_id == 2 >+ ); >+ cy.intercept("GET", "/api/v1/preservation/trains/1", train); >+ cy.contains("Submit").click(); // Submit add items form >+ cy.get("main div[class='dialog message']").contains( >+ `2 items have been added to train ${train.train_id}.` >+ ); >+ }); > }); >diff --git a/t/cypress/integration/Preservation/WaitingList.ts b/t/cypress/integration/Preservation/WaitingList.ts >index 4e4e1c2b5d..e5c956198f 100644 >--- a/t/cypress/integration/Preservation/WaitingList.ts >+++ b/t/cypress/integration/Preservation/WaitingList.ts >@@ -103,52 +103,6 @@ describe("WaitingList", () => { > ); > }); > >- it("Add to waiting list then add to a train", () => { >- let train = { >- description: "yet another train", >- name: "a train", >- train_id: 1, >- }; >- cy.intercept("GET", "/api/v1/preservation/trains*", [train]); >- cy.visit("/cgi-bin/koha/preservation/waiting-list"); >- >- cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { >- statusCode: 200, >- body: get_items(), >- headers: { >- "X-Base-Total-Count": "2", >- "X-Total-Count": "2", >- }, >- }).as("get-items"); >- cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [ >- { item_id: 1 }, >- { item_id: 3 }, >- ]); >- cy.get("#waiting-list").contains("Add to waiting list").click(); >- cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3"); >- cy.contains("Submit").click(); >- cy.wait("@get-items"); >- cy.get("main div[class='dialog message']").contains( >- "2 new items added." >- ); >- cy.contains("Add last 2 items to a train").click(); >- cy.get("#train_id .vs__search").type(train.name + "{enter}"); >- cy.intercept( >- "POST", >- "/api/v1/preservation/trains/" + train.train_id + "/items/batch", >- req => { >- req.reply({ >- statusCode: 201, >- body: req.body, >- }); >- } >- ); >- cy.contains("Submit").click(); >- cy.get("main div[class='dialog message']").contains( >- `2 items have been added to train ${train.train_id}.` >- ); >- }); >- > it("Remove item from waiting list", () => { > cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", { > statusCode: 200, >-- >2.30.2
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 30708
:
134715
|
134716
|
134732
|
134733
|
134734
|
134736
|
134737
|
134738
|
134782
|
134783
|
134784
|
134785
|
152939
|
152940
|
152941
|
152942
|
152943
|
152944
|
152945
|
152946
|
152947
|
152948
|
152949
|
152950
|
152951
|
152952
|
152953
|
152954
|
152955
|
152956
|
152957
|
152958
|
152959
|
152960
|
152961
|
152962
|
152963
|
152964
|
152965
|
152966
|
152967
|
152968
|
152969
|
152970
|
153102
|
153576
|
154167
|
154168
|
154169
|
154170
|
154171
|
154172
|
154173
|
154174
|
154175
|
154176
|
154177
|
154178
|
154179
|
154180
|
154181
|
154182
|
154183
|
154184
|
154185
|
154186
|
154187
|
154188
|
154189
|
154190
|
154191
|
154192
|
154193
|
154194
|
154195
|
154196
|
154197
|
154198
|
154199
|
154200
|
154201
|
154202
|
154203
|
156592
|
156596
|
156597
|
156636
|
156637
|
156638
|
156639
|
156640
|
156641
|
156642
|
156643
|
156644
|
156645
|
156646
|
156647
|
156648
|
156649
|
156650
|
156651
|
156652
|
156653
|
156654
|
156655
|
156656
|
156657
| 156658 |
156659
|
156660
|
156661
|
156662
|
156663
|
156664
|
156665
|
156666
|
156667
|
156668
|
156669
|
156670
|
156671
|
156672
|
156673
|
156674
|
156675
|
157502
|
157503
|
157506
|
157508
|
157512
|
157514
|
157557