View | Details | Raw Unified | Return to bug 30708
Collapse All | Expand All

(-)a/Koha/Preservation/Train.pm (-1 / +3 lines)
Lines 120-126 sub add_items { Link Here
120
    my @added_items;
120
    my @added_items;
121
    for my $train_item (@$train_items) {
121
    for my $train_item (@$train_items) {
122
        try {
122
        try {
123
            push @added_items, $self->add_item($train_item);
123
            my $added_item = $self->add_item($train_item);
124
            $added_item->attributes($train_item->{attributes});
125
            push @added_items, $added_item;
124
        } catch {
126
        } catch {
125
127
126
            # FIXME Do we rollback and raise an error or just skip it?
128
            # FIXME Do we rollback and raise an error or just skip it?
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/TrainsFormAddItems.vue (+285 lines)
Line 0 Link Here
1
<template>
2
    <div v-if="!initialized">{{ $__("Loading") }}</div>
3
    <div v-else id="trains_add_items">
4
        <h2>{{ $__("Add new items to %s").format(train.name) }}</h2>
5
        <form @submit="onSubmit($event)">
6
            <fieldset class="rows">
7
                <ol>
8
                    <li>
9
                        <label for="itemnumbers"
10
                            >{{ $__("Itemnumbers") }}:</label
11
                        >
12
                        <span>{{ items.map(i => i.item_id).join(", ") }}</span>
13
                    </li>
14
                    <li>
15
                        <label for="processing"
16
                            >{{ $__("Processing") }}:
17
                        </label>
18
                        <v-select
19
                            id="processing"
20
                            label="name"
21
                            v-model="processing_id"
22
                            @option:selected="refreshAttributes(1)"
23
                            :reduce="p => p.processing_id"
24
                            :options="processings"
25
                            :clearable="false"
26
                        />
27
                    </li>
28
                    <li
29
                        class="attribute"
30
                        v-for="(attribute, counter) in attributes"
31
                        v-bind:key="counter"
32
                    >
33
                        <label :for="`attribute_${counter}`"
34
                            >{{ attribute.name }}:
35
                        </label>
36
                        <span v-if="attribute.type == 'authorised_value'">
37
                            <v-select
38
                                :id="`attribute_${counter}`"
39
                                v-model="attribute.value"
40
                                label="description"
41
                                :reduce="av => av.value"
42
                                :options="av_options[attribute.option_source]"
43
                                taggable
44
                                :create-option="
45
                                    attribute => ({
46
                                        value: attribute,
47
                                        description: attribute,
48
                                    })
49
                                "
50
                            />
51
                        </span>
52
                        <span v-else-if="attribute.type == 'free_text'">
53
                            <input
54
                                :id="`attribute_${counter}`"
55
                                v-model="attribute.value"
56
                            />
57
                        </span>
58
                        <span v-else-if="attribute.type == 'db_column'">
59
                            {{
60
                                $__(
61
                                    "Cannot be edited now, the value will be retrieved from %s"
62
                                ).format(attribute.option_source)
63
                            }}
64
                        </span>
65
                        <a
66
                            v-if="
67
                                attribute.type != 'db_column' &&
68
                                (attributes.length == counter + 1 ||
69
                                    attributes[counter + 1]
70
                                        .processing_attribute_id !=
71
                                        attribute.processing_attribute_id)
72
                            "
73
                            class="btn btn-link"
74
                            @click="
75
                                addAttribute(attribute.processing_attribute_id)
76
                            "
77
                            ><font-awesome-icon icon="plus" />
78
                            {{ $__("Add") }}</a
79
                        >
80
                        <a
81
                            v-else-if="attribute.type != 'db_column'"
82
                            class="btn btn-link"
83
                            @click="removeAttribute(counter)"
84
                            ><font-awesome-icon icon="minus" />
85
                            {{ $__("Remove") }}</a
86
                        >
87
                    </li>
88
                </ol>
89
            </fieldset>
90
            <fieldset class="action">
91
                <input type="submit" value="Submit" />
92
                <router-link
93
                    to="/cgi-bin/koha/preservation/trains"
94
                    role="button"
95
                    class="cancel"
96
                    >{{ $__("Cancel") }}</router-link
97
                >
98
            </fieldset>
99
        </form>
100
    </div>
101
</template>
102
103
<script>
104
import { inject } from "vue"
105
import { APIClient } from "../../fetch/api-client"
106
107
export default {
108
    setup() {
109
        const { setMessage, setWarning, loading, loaded } = inject("mainStore")
110
        return {
111
            setMessage,
112
            setWarning,
113
            loading,
114
            loaded,
115
            api_mappings,
116
        }
117
    },
118
    data() {
119
        return {
120
            train: {
121
                train_id: null,
122
                name: "",
123
                description: "",
124
            },
125
            items: [],
126
            train_items: [],
127
            processings: [],
128
            processing: null,
129
            processing_id: null,
130
            initialized: false,
131
            av_options: {},
132
            attributes: [],
133
        }
134
    },
135
    beforeCreate() {
136
        const client = APIClient.preservation
137
        client.processings
138
            .getAll()
139
            .then(processings => (this.processings = processings))
140
    },
141
    beforeRouteEnter(to, from, next) {
142
        next(vm => {
143
            vm.train = vm
144
                .getTrain(to.params.train_id)
145
                .then(() =>
146
                    vm
147
                        .getItems(to.params.item_ids.split(","))
148
                        .then(() =>
149
                            vm
150
                                .refreshAttributes()
151
                                .then(() => (vm.initialized = true))
152
                        )
153
                )
154
        })
155
    },
156
    methods: {
157
        async getTrain(train_id) {
158
            const client = APIClient.preservation
159
            await client.trains.get(train_id).then(
160
                train => {
161
                    this.train = train
162
                    this.processing_id = train.default_processing_id
163
                },
164
                error => {}
165
            )
166
        },
167
        async getItems(item_ids) {
168
            const client = APIClient.item
169
            let q = { "me.item_id": item_ids }
170
            await client.items
171
                .getAll(q, { headers: { "x-koha-embed": "biblio" } })
172
                .then(
173
                    items => {
174
                        this.items = items
175
                    },
176
                    error => {}
177
                )
178
        },
179
        columnApiMapping(item, db_column) {
180
            let table_col = db_column.split(".")
181
            let table = table_col[0]
182
            let col = table_col[1]
183
            let api_attribute = this.api_mappings[table][col] || col
184
            return table == "biblio" || table == "biblioitems"
185
                ? item.biblio[api_attribute]
186
                : item[api_attribute]
187
        },
188
        async refreshAttributes() {
189
            this.loading()
190
191
            const client = APIClient.preservation
192
            await client.processings.get(this.processing_id).then(
193
                processing => (this.processing = processing),
194
                error => {}
195
            )
196
            this.attributes = []
197
            this.processing.attributes.forEach(attribute => {
198
                this.attributes.push({
199
                    processing_attribute_id: attribute.processing_attribute_id,
200
                    name: attribute.name,
201
                    type: attribute.type,
202
                    option_source: attribute.option_source,
203
                    value: "",
204
                })
205
            })
206
            const client_av = APIClient.authorised_values
207
            let av_cat_array = this.processing.attributes
208
                .filter(attribute => attribute.type == "authorised_value")
209
                .map(attribute => attribute.option_source)
210
211
            client_av.values
212
                .getCategoriesWithValues([
213
                    ...new Set(av_cat_array.map(av_cat => '"' + av_cat + '"')),
214
                ]) // unique
215
                .then(av_categories => {
216
                    av_cat_array.forEach(av_cat => {
217
                        let av_match = av_categories.find(
218
                            element => element.category_name == av_cat
219
                        )
220
                        this.av_options[av_cat] = av_match.authorised_values
221
                    })
222
                })
223
                .then(() => this.loaded())
224
        },
225
        addAttribute(processing_attribute_id) {
226
            let last_index = this.attributes.findLastIndex(
227
                attribute =>
228
                    attribute.processing_attribute_id == processing_attribute_id
229
            )
230
            let new_attribute = (({ value, ...keepAttrs }) => keepAttrs)(
231
                this.attributes[last_index]
232
            )
233
            this.attributes.splice(last_index + 1, 0, new_attribute)
234
        },
235
        removeAttribute(counter) {
236
            this.attributes.splice(counter, 1)
237
        },
238
        onSubmit(e) {
239
            e.preventDefault()
240
241
            let train_items = this.items.map(item => {
242
                return {
243
                    item_id: item.item_id,
244
                    processing_id: this.processing_id,
245
                    attributes: this.attributes.map(a => {
246
                        let value =
247
                            a.type == "db_column"
248
                                ? this.columnApiMapping(item, a.option_source)
249
                                : a.value
250
                        return {
251
                            processing_attribute_id: a.processing_attribute_id,
252
                            value,
253
                        }
254
                    }),
255
                }
256
            })
257
258
            const client = APIClient.preservation
259
            client.train_items.createAll(train_items, this.train.train_id).then(
260
                result => {
261
                    if (result.length) {
262
                        this.setMessage(
263
                            this.$__(
264
                                "%s items have been added to train %s."
265
                            ).format(result.length, this.train.train_id)
266
                        )
267
268
                        this.$router.push(
269
                            "/cgi-bin/koha/preservation/trains/" +
270
                                this.train.train_id
271
                        )
272
                    } else {
273
                        this.setMessage(
274
                            this.$__("No items have been added to the train.")
275
                        )
276
                    }
277
                },
278
                error => {}
279
            )
280
        },
281
    },
282
    components: {},
283
    name: "TrainsFormAddItems",
284
}
285
</script>
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/components/Preservation/WaitingList.vue (-34 / +7 lines)
Lines 189-228 export default { Link Here
189
        },
189
        },
190
        addItemsToTrain: function (e) {
190
        addItemsToTrain: function (e) {
191
            e.preventDefault()
191
            e.preventDefault()
192
            this.loading()
192
            let item_ids = this.last_items.map(i => i.item_id)
193
            let item_ids = Object.values(this.last_items)
193
            this.$router.push(
194
            const client = APIClient.preservation
194
                "/cgi-bin/koha/preservation/trains/" +
195
            client.train_items
195
                    this.train_id_selected_for_add +
196
                .createAll(item_ids, this.train_id_selected_for_add)
196
                    "/items/add/" +
197
                .then(
197
                    item_ids.join(",")
198
                    result => {
198
            )
199
                        if (result.length) {
200
                            this.setMessage(
201
                                this.$__(
202
                                    "%s items have been added to train %s."
203
                                ).format(
204
                                    result.length,
205
                                    this.train_id_selected_for_add
206
                                ),
207
                                true
208
                            )
209
                        } else {
210
                            this.setMessage(
211
                                this.$__(
212
                                    "No items have been added to the train."
213
                                )
214
                            )
215
                        }
216
217
                        this.$refs.table.redraw(
218
                            "/api/v1/preservation/waiting-list/items"
219
                        )
220
                        this.show_modal_add_to_train = false
221
                        this.last_items = []
222
                    },
223
                    error => {}
224
                )
225
                .then(() => this.loaded())
226
        },
199
        },
227
        addItemsToWaitingList: function (e) {
200
        addItemsToWaitingList: function (e) {
228
            e.preventDefault()
201
            e.preventDefault()
(-)a/koha-tmpl/intranet-tmpl/prog/js/vue/routes/preservation.js (+12 lines)
Lines 3-8 import TrainsList from "../components/Preservation/TrainsList.vue"; Link Here
3
import TrainsShow from "../components/Preservation/TrainsShow.vue";
3
import TrainsShow from "../components/Preservation/TrainsShow.vue";
4
import TrainsFormAdd from "../components/Preservation/TrainsFormAdd.vue";
4
import TrainsFormAdd from "../components/Preservation/TrainsFormAdd.vue";
5
import TrainsFormAddItem from "../components/Preservation/TrainsFormAddItem.vue";
5
import TrainsFormAddItem from "../components/Preservation/TrainsFormAddItem.vue";
6
import TrainsFormAddItems from "../components/Preservation/TrainsFormAddItems.vue";
6
import WaitingList from "../components/Preservation/WaitingList.vue";
7
import WaitingList from "../components/Preservation/WaitingList.vue";
7
import Settings from "../components/Preservation/Settings.vue";
8
import Settings from "../components/Preservation/Settings.vue";
8
import SettingsProcessingsShow from "../components/Preservation/SettingsProcessingsShow.vue";
9
import SettingsProcessingsShow from "../components/Preservation/SettingsProcessingsShow.vue";
Lines 117-122 export const routes = [ Link Here
117
                                        ),
118
                                        ),
118
                                },
119
                                },
119
                            },
120
                            },
121
                            {
122
                                path: "add/:item_ids",
123
                                component: TrainsFormAddItems,
124
                                meta: {
125
                                    breadcrumb: () =>
126
                                        build_breadcrumb(
127
                                            breadcrumb_paths.trains,
128
                                            "Add items to train" // $t("Add items to train")
129
                                        ),
130
                                },
131
                            },
120
                            {
132
                            {
121
                                path: "edit/:train_item_id",
133
                                path: "edit/:train_item_id",
122
                                component: TrainsFormAddItem,
134
                                component: TrainsFormAddItem,
(-)a/t/cypress/integration/Preservation/Trains.ts (+60 lines)
Lines 596-599 describe("Trains", () => { Link Here
596
            });
596
            });
597
        });
597
        });
598
    });
598
    });
599
600
    it("Add to waiting list then add to a train", () => {
601
        let train = get_train();
602
        let processing = get_processings()[0];
603
        cy.intercept("GET", "/api/v1/preservation/trains*", [train]);
604
        cy.intercept("GET", "/api/v1/preservation/trains/1", train);
605
        cy.intercept("GET", "/api/v1/preservation/processings/1", processing);
606
        cy.visit("/cgi-bin/koha/preservation/waiting-list");
607
608
        cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", {
609
            statusCode: 200,
610
            body: get_items(),
611
            headers: {
612
                "X-Base-Total-Count": "2",
613
                "X-Total-Count": "2",
614
            },
615
        }).as("get-items");
616
        cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [
617
            { item_id: 1 },
618
            { item_id: 2 },
619
        ]);
620
        cy.get("#waiting-list").contains("Add to waiting list").click();
621
        cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3");
622
        cy.contains("Submit").click();
623
        cy.wait("@get-items");
624
        cy.get("main div[class='dialog message']").contains(
625
            "2 new items added."
626
        );
627
        cy.contains("Add last 2 items to a train").click();
628
        cy.get("#train_id .vs__search").type(train.name + "{enter}");
629
        cy.intercept("GET", "/api/v1/items*", {
630
            statusCode: 200,
631
            body: get_items().filter(
632
                item => item.item_id == 1 || item.item_id == 2
633
            ),
634
            headers: {
635
                "X-Base-Total-Count": "2",
636
                "X-Total-Count": "2",
637
            },
638
        });
639
        cy.intercept(
640
            "POST",
641
            "/api/v1/preservation/trains/" + train.train_id + "/items/batch",
642
            req => {
643
                req.reply({
644
                    statusCode: 201,
645
                    body: req.body,
646
                });
647
            }
648
        );
649
        cy.contains("Submit").click(); // Select train
650
        train.items = get_train_items().filter(
651
            train_item => train_item.item_id == 1 || train_item.item_id == 2
652
        );
653
        cy.intercept("GET", "/api/v1/preservation/trains/1", train);
654
        cy.contains("Submit").click(); // Submit add items form
655
        cy.get("main div[class='dialog message']").contains(
656
            `2 items have been added to train ${train.train_id}.`
657
        );
658
    });
599
});
659
});
(-)a/t/cypress/integration/Preservation/WaitingList.ts (-47 lines)
Lines 103-154 describe("WaitingList", () => { Link Here
103
        );
103
        );
104
    });
104
    });
105
105
106
    it("Add to waiting list then add to a train", () => {
107
        let train = {
108
            description: "yet another train",
109
            name: "a train",
110
            train_id: 1,
111
        };
112
        cy.intercept("GET", "/api/v1/preservation/trains*", [train]);
113
        cy.visit("/cgi-bin/koha/preservation/waiting-list");
114
115
        cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", {
116
            statusCode: 200,
117
            body: get_items(),
118
            headers: {
119
                "X-Base-Total-Count": "2",
120
                "X-Total-Count": "2",
121
            },
122
        }).as("get-items");
123
        cy.intercept("POST", "/api/v1/preservation/waiting-list/items", [
124
            { item_id: 1 },
125
            { item_id: 3 },
126
        ]);
127
        cy.get("#waiting-list").contains("Add to waiting list").click();
128
        cy.get("#barcode_list").type("bc_1\nbc_2\nbc_3");
129
        cy.contains("Submit").click();
130
        cy.wait("@get-items");
131
        cy.get("main div[class='dialog message']").contains(
132
            "2 new items added."
133
        );
134
        cy.contains("Add last 2 items to a train").click();
135
        cy.get("#train_id .vs__search").type(train.name + "{enter}");
136
        cy.intercept(
137
            "POST",
138
            "/api/v1/preservation/trains/" + train.train_id + "/items/batch",
139
            req => {
140
                req.reply({
141
                    statusCode: 201,
142
                    body: req.body,
143
                });
144
            }
145
        );
146
        cy.contains("Submit").click();
147
        cy.get("main div[class='dialog message']").contains(
148
            `2 items have been added to train ${train.train_id}.`
149
        );
150
    });
151
152
    it("Remove item from waiting list", () => {
106
    it("Remove item from waiting list", () => {
153
        cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", {
107
        cy.intercept("GET", "/api/v1/preservation/waiting-list/items*", {
154
            statusCode: 200,
108
            statusCode: 200,
155
- 

Return to bug 30708