diff --git a/cgi/generate_sample_import_file.pl b/cgi/generate_sample_import_file.pl index fed0069a5f411..925bfb46825f8 100755 --- a/cgi/generate_sample_import_file.pl +++ b/cgi/generate_sample_import_file.pl @@ -28,7 +28,7 @@ use CGI::Carp qw(fatalsToBrowser); use ProductOpener::Config qw/:all/; -use ProductOpener::Display qw/init_request/; +use ProductOpener::Display qw/init_request $admin/; use ProductOpener::Users qw/:all/; use ProductOpener::Lang qw/lang/; use ProductOpener::Mail qw/:all/; @@ -37,6 +37,7 @@ use ProductOpener::Food qw/default_unit_for_nid/; use ProductOpener::TaxonomySuggestions qw/:all/; use ProductOpener::Paths qw/%BASE_DIRS/; +use ProductOpener::CRM qw/update_template_download_date/; use Apache2::RequestRec (); use Apache2::Const (); @@ -47,6 +48,11 @@ my $request_ref = ProductOpener::Display::init_request(); +# sync CRM +if (defined $Org_id and not $admin and not $User{moderator} and not $User{pro_moderator}) { + update_template_download_date($Org_id); +} + my $r = Apache2::RequestUtil->request(); $r->headers_out->set("Content-type" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); diff --git a/cgi/import_file_process.pl b/cgi/import_file_process.pl index a445a91e2493f..36af7860402be 100755 --- a/cgi/import_file_process.pl +++ b/cgi/import_file_process.pl @@ -36,7 +36,7 @@ use ProductOpener::Lang qw/$lc lang/; use ProductOpener::Mail qw/:all/; use ProductOpener::Producers qw/convert_file get_minion load_csv_or_excel_file normalize_column_name/; -use ProductOpener::CRM qw/update_last_import_date/; +use ProductOpener::CRM qw/update_company_last_import_type/; use Apache2::RequestRec (); use Apache2::Const (); @@ -202,6 +202,8 @@ store("$BASE_DIRS{IMPORT_FILES}/${Owner_id}/import_files.sto", $import_files_ref); +update_company_last_import_type($Org_id, 'CSV'); + $template_data_ref->{process_file_id} = $file_id; $template_data_ref->{process_import_id} = $import_id; $template_data_ref->{link} = "/cgi/import_file_job_status.pl?file_id=$file_id&import_id=$import_id"; diff --git a/cgi/product_multilingual.pl b/cgi/product_multilingual.pl index b44c201fee545..6316980bfcbce 100755 --- a/cgi/product_multilingual.pl +++ b/cgi/product_multilingual.pl @@ -56,6 +56,7 @@ use ProductOpener::APIProductWrite qw/skip_protected_field/; use ProductOpener::ProductsFeatures qw/feature_enabled/; use ProductOpener::Orgs qw/update_import_date/; +use ProductOpener::CRM qw/update_company_last_import_type/; use Apache2::RequestRec (); use Apache2::Const (); @@ -276,6 +277,7 @@ ($product_ref) # sync crm if (defined $Org_id) { update_import_date($Org_id, $product_ref->{created_t}); + update_company_last_import_type($Org_id, 'Manual import'); } $type = 'add'; diff --git a/lib/ProductOpener/CRM.pm b/lib/ProductOpener/CRM.pm index df52043f1eb3a..04c3eee7ecc8c 100644 --- a/lib/ProductOpener/CRM.pm +++ b/lib/ProductOpener/CRM.pm @@ -61,6 +61,11 @@ BEGIN { &change_company_main_contact &update_last_import_date &update_last_export_date + &update_company_last_logged_in_contact + &update_company_last_import_type + &add_category_to_company + &update_template_download_date + &update_contact_last_login &get_company_url &get_contact_url ); @@ -83,7 +88,7 @@ my $crm_data; my @required_tag_labels = qw(onboarding); # Category (res.partner.category) must be defined in Odoo : # Contact > contact (individual or company) form > Tags field > "Search More" -my @required_category_labels = qw(Producer); +my @required_category_labels = ('Producer', 'AGENA3000', 'EQUADIS', 'CSV', 'Manual import',); # special commands to manipulate Odoo relation One2Many and Many2Many # see https://www.odoo.com/documentation/15.0/developer/reference/backend/orm.html#odoo.fields.Command @@ -568,25 +573,84 @@ sub change_company_main_contact($org_ref, $user_id) { } sub update_last_import_date($org_id, $time) { - return update_company_last_action_date($org_id, $time, 'x_off_last_import_date'); + return _update_partner_field(retrieve_org($org_id), 'x_off_last_import_date', _time_to_odoo_date_str($time)); } sub update_last_export_date($org_id, $time) { - return update_company_last_action_date($org_id, $time, 'x_off_last_export_date'); + return _update_partner_field(retrieve_org($org_id), 'x_off_last_export_date', _time_to_odoo_date_str($time)); } -sub update_company_last_action_date($org_id, $time, $field) { +sub update_contact_last_login ($user_ref) { + return if not defined $user_ref->{crm_user_id}; + return _update_partner_field($user_ref, 'x_off_user_login_date', _time_to_odoo_date_str($user_ref->{last_login_t})); +} + +sub update_template_download_date ($org_id) { my $org_ref = retrieve_org($org_id); - if (not defined $org_ref->{crm_org_id}) { - return; - } + return _update_partner_field($org_ref, 'x_off_last_template_download_date', _time_to_odoo_date_str(time())); +} + +sub update_company_last_logged_in_contact($org_ref, $user_ref) { + return _update_partner_field($org_ref, 'x_off_last_logged_org_contact', $user_ref->{crm_user_id}); +} + +sub _update_partner_field($user_or_org, $field, $value) { + my $partner_id = $user_or_org->{crm_user_id} // $user_or_org->{crm_org_id}; + return if not defined $partner_id; + + $log->debug("update_partner_field", {partner_id => $partner_id, field => $field, value => $value}) + if $log->is_debug(); + + return make_odoo_request('res.partner', 'write', [[$partner_id], {$field => $value}]); +} + +sub _time_to_odoo_date_str($time) { my ($sec, $min, $hour, $mday, $mon, $year) = localtime($time); $year += 1900; $mon += 1; - my $date_string = sprintf("%04d-%02d-%02d", $year, $mon, $mday); - $log->debug("update_last_company_action_date", {org_id => $org_id, date => $date_string, field => $field}) + return sprintf("%04d-%02d-%02d", $year, $mon, $mday); +} + +=head2 add_category_to_company ($org_id, $label) + +Add a category to a company in Odoo + +=head3 Arguments + +=head4 $org_id + +=head4 $label + +=head3 Return values + +1 if success, undef otherwise + +=cut + +sub add_category_to_company($org_id, $label) { + my $org_ref = retrieve_org($org_id); + return if not defined $org_ref->{crm_org_id}; + + my $category_id = $crm_data->{category}{$label}; + return if not defined $category_id; + if (not defined $category_id) { + $log->debug("add_category_to_company", {error => "Category `$label` not found in CRM"}) + if $log->is_debug(); + return; + } + $log->debug("add_category_to_company", {org_id => $org_id, label => $label, category_id => $category_id}) if $log->is_debug(); - return make_odoo_request('res.partner', 'write', [[$org_ref->{crm_org_id}], {$field => $date_string}]); + return make_odoo_request('res.partner', 'write', + [[$org_ref->{crm_org_id}], {category_id => [[$commands{link}, $category_id]]}]); +} + +sub update_company_last_import_type($org_id, $label) { + my $org_ref = retrieve_org($org_id); + return if not defined $org_ref->{crm_org_id}; + my $category_id = $crm_data->{category}{$label}; + add_category_to_company($org_id, $label); + return make_odoo_request('res.partner', 'write', + [[$org_ref->{crm_org_id}], {x_off_last_import_type => $category_id}]); } =head2 get_company_url ($org_ref) @@ -649,7 +713,10 @@ sub make_odoo_request(@params) { } my $uid; - eval {$uid = $xmlrpc->call('authenticate', $db, $username, $pwd, {});}; + eval { + $uid = $xmlrpc->call('authenticate', $db, $username, $pwd, {}); + die "uid from Odoo auth. is 0, your credentials may be wrong." if $uid eq '0'; + }; if ($@) { $log->error("Odoo", {error => $@, reason => "Could not authenticate to Odoo CRM"}) if $log->is_error(); $xmlrpc = undef; @@ -685,7 +752,8 @@ It is called by lib/startup_apache.pl startup script =head4 die if CRM data cannot be loaded -# Die if CRM environment variables are set and required data cannot be loaded from cache or fetched from CRM +Die if CRM environment variables are set but required +data can't be fetched from CRM nor be loaded from cache =cut @@ -708,8 +776,8 @@ sub init_crm_data() { $crm_data = $tmp_crm_data; } or do { print STDERR "Failed to load CRM data from Odoo: $@\n"; - die "Could not load CRM data from cache" if not defined $crm_data; - print STDERR "CRM data loaded from cache"; + die "Could not load CRM data from cache\n" if not defined $crm_data; + print STDERR "CRM data loaded from cache\n"; }; store("$BASE_DIRS{CACHE_TMP}/crm_data.sto", $crm_data); diff --git a/lib/ProductOpener/Import.pm b/lib/ProductOpener/Import.pm index 94d9e2d06b299..0de5262ec9702 100644 --- a/lib/ProductOpener/Import.pm +++ b/lib/ProductOpener/Import.pm @@ -103,6 +103,7 @@ use ProductOpener::Ecoscore qw/:all/; use ProductOpener::ForestFootprint qw/:all/; use ProductOpener::PackagerCodes qw/normalize_packager_codes/; use ProductOpener::API qw/get_initialized_response/; +use ProductOpener::CRM qw/update_company_last_import_type/; use CGI qw/:cgi :form escapeHTML/; use URI::Escape::XS; @@ -2773,6 +2774,12 @@ sub import_csv_file ($args_ref) { # sync CRM foreach my $org_id (keys %{$stats_ref->{orgs_existing}}) { update_import_date($org_id, $time); + if ($args_ref->{source_id} eq 'agena3000') { + update_company_last_import_type($org_id, 'AGENA3000'); + } + elsif ($args_ref->{source_id} eq 'equadis') { + update_company_last_import_type($org_id, 'EQUADIS'); + } } $log->debug( diff --git a/lib/ProductOpener/Orgs.pm b/lib/ProductOpener/Orgs.pm index 41dbd30f18936..319381d64abe8 100644 --- a/lib/ProductOpener/Orgs.pm +++ b/lib/ProductOpener/Orgs.pm @@ -58,6 +58,7 @@ BEGIN { &org_name &update_import_date &update_export_date + &update_last_logged_in_member ); # symbols to export on request %EXPORT_TAGS = (all => [@EXPORT_OK]); @@ -216,7 +217,7 @@ sub store_org ($org_ref) { and $org_ref->{main_contact} ne $previous_org_ref->{main_contact} and not change_company_main_contact($previous_org_ref, $org_ref->{main_contact})) { - # so we don't lose sync with CRM if main contact cannot be changed + # fail -> revert main contact, so we don't lose sync with CRM if main contact cannot be changed $org_ref->{main_contact} = $previous_org_ref->{main_contact}; } @@ -522,4 +523,22 @@ sub update_export_date($org_id, $time) { return; } +sub update_last_logged_in_member($user_ref) { + + my $org_id = $user_ref->{org_id} // $user_ref->{requested_org_id}; + return if not defined $org_id; + + my $org_ref = retrieve_org($org_id); + return if not defined $org_ref; + is_user_in_org_group($org_ref, $user_ref->{userid}, "members") or return; + + $org_ref->{last_logged_member} = $user_ref->{userid}; + + if (defined $org_ref->{crm_org_id}) { + update_company_last_logged_in_contact($org_ref, $user_ref); + } + + return; +} + 1; diff --git a/lib/ProductOpener/Test.pm b/lib/ProductOpener/Test.pm index c1479af7e2e54..b39af7a94fbc7 100644 --- a/lib/ProductOpener/Test.pm +++ b/lib/ProductOpener/Test.pm @@ -800,7 +800,7 @@ We remove time dependent fields, password (which encryption use salt) and sort s =cut sub normalize_user_for_test_comparison ($user_ref) { - my %specification = (fields_ignore_content => [qw(registered_t user_sessions encrypted_password ip)],); + my %specification = (fields_ignore_content => [qw(registered_t last_login_t user_sessions encrypted_password ip)],); normalize_object_for_test_comparison($user_ref, \%specification); return; diff --git a/lib/ProductOpener/Users.pm b/lib/ProductOpener/Users.pm index e0c71e15ca713..54dcf92e76a17 100644 --- a/lib/ProductOpener/Users.pm +++ b/lib/ProductOpener/Users.pm @@ -75,6 +75,7 @@ BEGIN { &check_session &generate_token + &update_login_time ); # symbols to export on request %EXPORT_TAGS = (all => [@EXPORT_OK]); @@ -88,10 +89,12 @@ use ProductOpener::Paths qw/%BASE_DIRS/; use ProductOpener::Mail qw/get_html_email_content send_email_to_admin send_email_to_producers_admin send_html_email/; use ProductOpener::Lang qw/$lc %Lang lang/; use ProductOpener::Display qw/:all/; -use ProductOpener::Orgs qw/add_user_to_org create_org remove_user_from_org retrieve_or_create_org retrieve_org/; +use ProductOpener::Orgs + qw/add_user_to_org create_org remove_user_from_org retrieve_or_create_org retrieve_org update_last_logged_in_member/; use ProductOpener::Products qw/find_and_replace_user_id_in_products/; use ProductOpener::Text qw/remove_tags_and_quote/; use ProductOpener::Brevo qw/add_contact_to_list/; +use ProductOpener::CRM qw/update_contact_last_login/; use CGI qw/:cgi :form escapeHTML/; use Encode; @@ -1170,6 +1173,7 @@ sub init_user ($request_ref) { migrate_password_hash($user_ref); open_user_session($user_ref, $request_ref); + update_login_time($user_ref); } } else { @@ -1423,4 +1427,12 @@ sub check_session ($user_id, $user_session) { return $results_ref; } +sub update_login_time ($user_ref) { + $user_ref->{last_login_t} = time(); + store_user($user_ref); + update_contact_last_login($user_ref); + update_last_logged_in_member($user_ref); + return; +} + 1; diff --git a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json index e08a15c23d3da..a49fa7d241a78 100644 --- a/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json +++ b/tests/integration/expected_test_results/api_v2_product_write/get-product-auth-good-password.json @@ -109,7 +109,7 @@ "origins_of_ingredients" : { "aggregated_origins" : [ { - "epi_score" : 0, + "epi_score" : "0", "origin" : "en:unknown", "percent" : 100, "transportation_score" : null diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-existing-product-gs1-gs.json b/tests/integration/expected_test_results/api_v3_product_read/get-existing-product-gs1-gs.json index 2feb00bd78dad..ff7acc65623de 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-existing-product-gs1-gs.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-existing-product-gs1-gs.json @@ -104,7 +104,7 @@ "origins_of_ingredients" : { "aggregated_origins" : [ { - "epi_score" : 0, + "epi_score" : "0", "origin" : "en:unknown", "percent" : 100, "transportation_score" : null diff --git a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json index ab19fefb25a54..c52ed9eb0cdce 100644 --- a/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json +++ b/tests/integration/expected_test_results/api_v3_product_read/get-fields-all.json @@ -104,7 +104,7 @@ "origins_of_ingredients" : { "aggregated_origins" : [ { - "epi_score" : 0, + "epi_score" : "0", "origin" : "en:unknown", "percent" : 100, "transportation_score" : null diff --git a/tests/integration/expected_test_results/create_pro_user/user-after-subscription.json b/tests/integration/expected_test_results/create_pro_user/user-after-subscription.json index 92eb0e42522e7..12c6fe8b9d17d 100644 --- a/tests/integration/expected_test_results/create_pro_user/user-after-subscription.json +++ b/tests/integration/expected_test_results/create_pro_user/user-after-subscription.json @@ -9,6 +9,7 @@ "initial_lc" : "en", "initial_user_agent" : "Product-opener-tests/1.0", "ip" : "--ignore--", + "last_login_t" : "--ignore--", "name" : "Test", "newsletter" : "", "old_email" : null,