From fec330a7073a5f02edaac6d40102b85dcf29fa81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Gigandet?= Date: Mon, 19 Dec 2022 16:15:12 +0100 Subject: [PATCH] feat: allow setting the packagings_complete field through API v3 (#7856) We have a field called packagings_complete that takes value 0 or 1 to indicate if the packagings field contains all element of the product. This PR adds it to the API v3 WRITE product. --- .../packagings/packagings_complete.yaml | 7 + docs/reference/schemas/product.yaml | 2 + .../schemas/product_update_api_v3.yaml | 2 + lib/ProductOpener/APIProductWrite.pm | 160 ++++++++++++------ po/common/common.pot | 3 + po/common/en.po | 3 + tests/integration/api_v3_product_write.t | 68 ++++++++ .../patch-packagings-complete-0.json | 39 +++++ .../patch-packagings-complete-1.json | 66 ++++++++ .../patch-packagings-complete-2.json | 82 +++++++++ 10 files changed, 376 insertions(+), 56 deletions(-) create mode 100644 docs/reference/schemas/packagings/packagings_complete.yaml create mode 100644 tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json create mode 100644 tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json create mode 100644 tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json diff --git a/docs/reference/schemas/packagings/packagings_complete.yaml b/docs/reference/schemas/packagings/packagings_complete.yaml new file mode 100644 index 0000000000000..88b4b52ba119e --- /dev/null +++ b/docs/reference/schemas/packagings/packagings_complete.yaml @@ -0,0 +1,7 @@ +title: packagings_complete +x-stoplight: + id: hxnnsy954q1ey +type: integer +minimum: 0 +maximum: 1 +description: Indicate if the packagings array contains all the packaging parts of the product. This field can be set by users when they enter or verify packaging data. Possible values are 0 or 1. diff --git a/docs/reference/schemas/product.yaml b/docs/reference/schemas/product.yaml index 77267de04791b..7331a7b5d683a 100644 --- a/docs/reference/schemas/product.yaml +++ b/docs/reference/schemas/product.yaml @@ -1742,6 +1742,8 @@ properties: like packaging_text_fr (for french). packagings: $ref: ./packagings/packagings.yaml + packagings_complete: + $ref: ./packagings/packagings_complete.yaml photographers_tags: type: array items: diff --git a/docs/reference/schemas/product_update_api_v3.yaml b/docs/reference/schemas/product_update_api_v3.yaml index 7fbb268cca75a..c138a2329ccfb 100644 --- a/docs/reference/schemas/product_update_api_v3.yaml +++ b/docs/reference/schemas/product_update_api_v3.yaml @@ -9,3 +9,5 @@ properties: $ref: ./packagings/packagings-write.yaml packagings_add: $ref: ./packagings/packagings-write.yaml + packagings_complete: + $ref: ./packagings/packagings_complete.yaml diff --git a/lib/ProductOpener/APIProductWrite.pm b/lib/ProductOpener/APIProductWrite.pm index f51cbb6d84e7b..f382c01eda162 100644 --- a/lib/ProductOpener/APIProductWrite.pm +++ b/lib/ProductOpener/APIProductWrite.pm @@ -51,6 +51,103 @@ use ProductOpener::Products qw/:all/; use ProductOpener::API qw/:all/; use ProductOpener::Packaging qw/:all/; +=head2 update_field_with_0_or_1_value($request_ref, $product_ref, $field, $value) + +Update a field that takes only 0 or 1 as a value (e.g. packagings_complete). + +=cut + +sub update_field_with_0_or_1_value ($request_ref, $product_ref, $field, $value) { + + my $response_ref = $request_ref->{api_response}; + + # Check that the value is 0 or 1 + + if (($value != 0) and ($value != 1)) { + + add_error( + $response_ref, + { + message => {id => "invalid_value_must_be_0_or_1"}, + field => {id => $field}, + impact => {id => "field_ignored"}, + } + ); + } + else { + $product_ref->{$field} = $value + 0; # add 0 to make sure the value is stored as a number + } + return; +} + +=head2 update_packagings($request_ref, $product_ref, $field, $is_addition, $value) + +Update packagings. + +=cut + +sub update_packagings ($request_ref, $product_ref, $field, $is_addition, $value) { + + my $request_body_ref = $request_ref->{body_json}; + my $response_ref = $request_ref->{api_response}; + + if (ref($value) ne 'ARRAY') { + add_error( + $response_ref, + { + message => {id => "invalid_type_must_be_array"}, + field => {id => $field}, + impact => {id => "field_ignored"}, + } + ); + } + else { + if (not $is_addition) { + # We will replace the packagings structure if it already exists + $product_ref->{packagings} = []; + } + + foreach my $input_packaging_ref (@{$value}) { + + # Shape, material and recycling + foreach my $property ("shape", "material", "recycling") { + if (defined $input_packaging_ref->{$property}) { + + # the API specifies that the property is a hash with either an id or a lc_name field + # (same structure as when the packagings structure is read) + # both will be treated the same way and be canonicalized + # by get_checked_and_taxonomized_packaging_component_data() + + if (ref($input_packaging_ref->{$property}) eq 'HASH') { + $input_packaging_ref->{$property} = $input_packaging_ref->{$property}{id} + || $input_packaging_ref->{$property}{lc_name}; + } + else { + add_error( + $response_ref, + { + message => {id => "invalid_type_must_be_object"}, + field => {id => $property}, + impact => {id => "field_ignored"}, + } + ); + } + } + } + + # Taxonomize the input packaging component data + my $packaging_ref = get_checked_and_taxonomized_packaging_component_data($request_body_ref->{tags_lc}, + $input_packaging_ref, $response_ref); + + if (defined $packaging_ref) { + # Add or combine with the existing packagings components array + add_or_combine_packaging_component_data($product_ref, $packaging_ref, $response_ref); + } + } + } + return; +} + =head2 update_product_fields ($request_ref, $product_ref) Update product fields based on input product data. @@ -59,7 +156,6 @@ Update product fields based on input product data. sub update_product_fields ($request_ref, $product_ref) { - my $response_ref = $request_ref->{api_response}; my $request_body_ref = $request_ref->{body_json}; $request_ref->{updated_product_fields} = {}; @@ -71,65 +167,17 @@ sub update_product_fields ($request_ref, $product_ref) { my $value = $input_product_ref->{$field}; # Packaging components - if ($field =~ /^(packagings)(_add)?/) { + if ($field =~ /^(packagings)(_add)?$/) { $request_ref->{updated_product_fields}{$1} = 1; my $is_addition = (defined $2) ? 1 : 0; - if (ref($value) ne 'ARRAY') { - add_error( - $response_ref, - { - message => {id => "invalid_type_must_be_array"}, - field => {id => $field}, - impact => {id => "field_ignored"}, - } - ); - } - else { - if (not $is_addition) { - # We will replace the packagings structure if it already exists - $product_ref->{packagings} = []; - } - - foreach my $input_packaging_ref (@{$value}) { - - # Shape, material and recycling - foreach my $property ("shape", "material", "recycling") { - if (defined $input_packaging_ref->{$property}) { - - # the API specifies that the property is a hash with either an id or a lc_name field - # (same structure as when the packagings structure is read) - # both will be treated the same way and be canonicalized - # by get_checked_and_taxonomized_packaging_component_data() - - if (ref($input_packaging_ref->{$property}) eq 'HASH') { - $input_packaging_ref->{$property} = $input_packaging_ref->{$property}{id} - || $input_packaging_ref->{$property}{lc_name}; - } - else { - add_error( - $response_ref, - { - message => {id => "invalid_type_must_be_object"}, - field => {id => $property}, - impact => {id => "field_ignored"}, - } - ); - } - } - } - - # Taxonomize the input packaging component data - my $packaging_ref - = get_checked_and_taxonomized_packaging_component_data($request_body_ref->{tags_lc}, - $input_packaging_ref, $response_ref); + update_packagings($request_ref, $product_ref, $field, $is_addition, $value); + } + # packagings_complete contains 0 or 1 and is used to indicate that all packaging components are listed in the packagings field + elsif ($field eq "packagings_complete") { + $request_ref->{updated_product_fields}{$field} = 1; - if (defined $packaging_ref) { - # Add or combine with the existing packagings components array - add_or_combine_packaging_component_data($product_ref, $packaging_ref, $response_ref); - } - } - } + update_field_with_0_or_1_value($request_ref, $product_ref, $field, $value); } } return; diff --git a/po/common/common.pot b/po/common/common.pot index 0cfda6f49cec4..6f2c762313163 100644 --- a/po/common/common.pot +++ b/po/common/common.pot @@ -6512,3 +6512,6 @@ msgctxt "api_message_invalid_user_id_and_password" msgid "Invalid user id and password" msgstr "Invalid user id and password" +msgctxt "api_message_invalid_value_must_be_0_or_1" +msgid "Invalid value: must be 0 or 1" +msgstr "Invalid value: must be 0 or 1" diff --git a/po/common/en.po b/po/common/en.po index 2f46633ba3907..770201f461a30 100644 --- a/po/common/en.po +++ b/po/common/en.po @@ -6456,3 +6456,6 @@ msgctxt "api_message_invalid_user_id_and_password" msgid "Invalid user id and password" msgstr "Invalid user id and password" +msgctxt "api_message_invalid_value_must_be_0_or_1" +msgid "Invalid value: must be 0 or 1" +msgstr "Invalid value: must be 0 or 1" diff --git a/tests/integration/api_v3_product_write.t b/tests/integration/api_v3_product_write.t index 3c7da823d7998..3fe63b63bb75b 100644 --- a/tests/integration/api_v3_product_write.t +++ b/tests/integration/api_v3_product_write.t @@ -452,6 +452,74 @@ my $tests_ref = [ } }' }, + # Packaging complete + { + test_case => 'patch-packagings-complete-0', + method => 'PATCH', + path => '/api/v3/product/1234567890016', + body => '{ + "fields": "packagings,packagings_complete", + "tags_lc": "en", + "product": { + "packagings": [ + { + "number_of_units": 1, + "shape": {"lc_name": "bottle"}, + "recycling": {"lc_name": "recycle"} + } + ], + "packagings_complete": 0 + } + }' + }, + { + test_case => 'patch-packagings-complete-1', + method => 'PATCH', + path => '/api/v3/product/1234567890016', + body => '{ + "fields": "packagings,packagings_complete", + "tags_lc": "en", + "product": { + "packagings": [ + { + "number_of_units": 1, + "shape": {"lc_name": "bottle"}, + "recycling": {"lc_name": "recycle"} + }, + { + "number_of_units": 1, + "shape": {"lc_name": "lid"}, + "recycling": {"lc_name": "recycle"} + } + ], + "packagings_complete": 1 + } + }' + }, + { + test_case => 'patch-packagings-complete-2', + method => 'PATCH', + path => '/api/v3/product/1234567890016', + body => '{ + "fields": "packagings,packagings_complete", + "tags_lc": "en", + "product": { + "packagings": [ + { + "number_of_units": 1, + "shape": {"lc_name": "bottle"}, + "recycling": {"lc_name": "recycle"} + }, + { + "number_of_units": 1, + "shape": {"lc_name": "lid"}, + "recycling": {"lc_name": "recycle"} + } + ], + "packagings_complete": 2 + } + }' + }, ]; execute_api_tests(__FILE__, $tests_ref); diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json new file mode 100644 index 0000000000000..076905303d149 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-0.json @@ -0,0 +1,39 @@ +{ + "code" : "1234567890016", + "errors" : [], + "product" : { + "packagings" : [ + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:bottle", + "lc_name" : "Bottle" + } + } + ], + "packagings_complete" : 0 + }, + "status" : "success_with_warnings", + "warnings" : [ + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json new file mode 100644 index 0000000000000..401122b8327af --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-1.json @@ -0,0 +1,66 @@ +{ + "code" : "1234567890016", + "errors" : [], + "product" : { + "packagings" : [ + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:bottle", + "lc_name" : "Bottle" + } + }, + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:lid", + "lc_name" : "Lid" + } + } + ], + "packagings_complete" : 1 + }, + "status" : "success_with_warnings", + "warnings" : [ + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +} diff --git a/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json new file mode 100644 index 0000000000000..34409847fca07 --- /dev/null +++ b/tests/integration/expected_test_results/api_v3_product_write/patch-packagings-complete-2.json @@ -0,0 +1,82 @@ +{ + "code" : "1234567890016", + "errors" : [ + { + "field" : { + "id" : "packagings_complete" + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "invalid_value_must_be_0_or_1", + "lc_name" : "Invalid value: must be 0 or 1", + "name" : "Invalid value: must be 0 or 1" + } + } + ], + "product" : { + "packagings" : [ + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:bottle", + "lc_name" : "Bottle" + } + }, + { + "number_of_units" : 1, + "recycling" : { + "id" : "en:recycle", + "lc_name" : "Recycle" + }, + "shape" : { + "id" : "en:lid", + "lc_name" : "Lid" + } + } + ], + "packagings_complete" : 1 + }, + "status" : "success_with_errors", + "warnings" : [ + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + }, + { + "field" : { + "id" : "material", + "value" : null + }, + "impact" : { + "id" : "field_ignored", + "lc_name" : "Field ignored", + "name" : "Field ignored" + }, + "message" : { + "id" : "missing_field", + "lc_name" : "Missing field", + "name" : "Missing field" + } + } + ] +}