diff --git a/hr_employee_vcard/README.rst b/hr_employee_vcard/README.rst new file mode 100644 index 00000000000..a8fb1380b34 --- /dev/null +++ b/hr_employee_vcard/README.rst @@ -0,0 +1,89 @@ +================= +HR Employee vCard +================= + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:b63660b3424b25ac1bb8b5ec70c333108b807fedb98875c0d3de5a75a53b1012 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge_devstat| image:: https://img.shields.io/badge/maturity-beta-brightgreen.png + :target: https://odoo-community.org/page/development-status + :alt: Beta + +.. |badge_license| image:: https://img.shields.io/badge/license-LGPL--3-blue.png + :alt: LGPL-3 + +|badge_devstat| |badge_license| + +This module lets you design and print business cards (vCards) for your +employees. + +A company-wide card design is configured once (layout, font, background +and which fields to show) and can be printed for any employee as a +two-sided PDF: the front holds the branding (logo, company name) and the +back the contact details (name, job position, email, phone, website). +Each card includes a QR code with the employee's contact information, so +it can be scanned straight into a phone's address book. + +The card adapts to the data available: only the selected fields that +actually have a value are shown. + +**Table of contents** + +.. contents:: + :local: + +Configuration +============= + +To configure the vCard design: + +1. Go to *Employees > Configuration > Settings*. +2. In the *Employee vCard* section, click *Configure vCard Layout*. +3. Choose the layout (Modern, Minimal or Classic), the font, the + background (blank, company logo or a custom image) and the fields to + display on the card. A live preview is shown as you change the + options. +4. Click *Save*. + +The design is stored per company, so each company can have its own card. + +Use *Preview vCard* to download a sample PDF with the current design. + +Usage +===== + +To print an employee's vCard: + +1. Open the employee form (*Employees > Employees*). +2. In the *Actions* menu, choose *Generate vCard*. + +The QR code is (re)generated from the employee's contact details and a +two-sided card PDF is downloaded, ready to print. + +Credits +======= + +Authors +------- + +* Vortex Dimensión Digital + +Contributors +------------ + +- `Vortex Dimensión Digital `__: + + - Jorge Rosado Julián + +Maintainers +----------- + +This module is maintained by Vortex Dimensión Digital. + +Contact the maintainer through their official support channels in case you find +any issues with this module. diff --git a/hr_employee_vcard/__init__.py b/hr_employee_vcard/__init__.py new file mode 100644 index 00000000000..a0f653930e7 --- /dev/null +++ b/hr_employee_vcard/__init__.py @@ -0,0 +1,3 @@ +from . import models +from . import wizards +from .hooks import post_init_hook diff --git a/hr_employee_vcard/__manifest__.py b/hr_employee_vcard/__manifest__.py new file mode 100644 index 00000000000..3e8e799c38d --- /dev/null +++ b/hr_employee_vcard/__manifest__.py @@ -0,0 +1,29 @@ +{ + "name": "HR Employee vCard", + "version": "18.0.1.0.0", + "summary": "Manage employee business cards.", + "author": "Vortex Dimensión Digital, Odoo Community Association (OCA)", + "website": "https://github.com/OCA/hr", + "license": "LGPL-3", + "category": "Human Resources", + "depends": ["hr"], + "post_init_hook": "post_init_hook", + "data": [ + "security/ir.model.access.csv", + "views/hr_employee_vcard_templates.xml", + "reports/hr_employee_vcard_reports.xml", + "data/hr_employee_vcard_layout_data.xml", + "data/hr_employee_vcard_field_data.xml", + "wizards/hr_employee_vcard_wizard.xml", + "views/hr_employee_views.xml", + "views/res_config_settings_views.xml", + ], + "assets": { + "web.report_assets_common": [ + "hr_employee_vcard/static/src/scss/*.scss", + ], + }, + "installable": True, + "auto_install": False, + "application": False, +} diff --git a/hr_employee_vcard/data/hr_employee_vcard_field_data.xml b/hr_employee_vcard/data/hr_employee_vcard_field_data.xml new file mode 100644 index 00000000000..11dae9ac70b --- /dev/null +++ b/hr_employee_vcard/data/hr_employee_vcard_field_data.xml @@ -0,0 +1,50 @@ + + + + + + Name + 15 + name + + + + Job Title + 20 + job_title + + + + Work Email + 25 + work_email + + + + Work Phone + 30 + work_phone + + + + Company Name + 35 + company_name + + + + Website + 40 + website + + + + QR + 45 + qr + + diff --git a/hr_employee_vcard/data/hr_employee_vcard_layout_data.xml b/hr_employee_vcard/data/hr_employee_vcard_layout_data.xml new file mode 100644 index 00000000000..caf30673b6d --- /dev/null +++ b/hr_employee_vcard/data/hr_employee_vcard_layout_data.xml @@ -0,0 +1,29 @@ + + + + Modern + 5 + + + + + Minimal + 10 + + + + + Classic + 15 + + + diff --git a/hr_employee_vcard/hooks.py b/hr_employee_vcard/hooks.py new file mode 100644 index 00000000000..fb5d53c6497 --- /dev/null +++ b/hr_employee_vcard/hooks.py @@ -0,0 +1,19 @@ +def post_init_hook(env): + """Apply the vCard defaults to companies that already existed on install. + + Field defaults only run when a company is created, so companies present + before installing the module would otherwise have no layout nor fields. + """ + layout = env.ref( + "hr_employee_vcard.hr_employee_vcard_layout_modern", raise_if_not_found=False + ) + for company in env["res.company"].search([]): + vals = {} + if not company.vcard_layout_id and layout: + vals["vcard_layout_id"] = layout.id + if not company.vcard_layout_field_ids: + vals["vcard_layout_field_ids"] = [ + (6, 0, company._default_vcard_layout_field_ids().ids) + ] + if vals: + company.write(vals) diff --git a/hr_employee_vcard/i18n/es.po b/hr_employee_vcard/i18n/es.po new file mode 100644 index 00000000000..5fd52d1ad1d --- /dev/null +++ b/hr_employee_vcard/i18n/es.po @@ -0,0 +1,326 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_employee_vcard +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#. module: hr_employee_vcard +#: model:ir.actions.report,print_report_name:hr_employee_vcard.action_report_vcard +msgid "'vCard - %s' % (object.name or '')" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_background +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_background +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_background +msgid "Background" +msgstr "Fondo" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__blank +msgid "Blank" +msgstr "En blanco" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Choose the layout used for vCard reports." +msgstr "Elige el diseño usado para los informes de vCard." + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_res_company +msgid "Companies" +msgstr "Compañías" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__company_id +msgid "Company" +msgstr "Compañía" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_company_name +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__company_name +msgid "Company Name" +msgstr "Nombre de la compañía" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_res_config_settings +msgid "Config Settings" +msgstr "Ajustes de configuración" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Configure vCard Layout" +msgstr "Configurar diseño de vCard" + +#. module: hr_employee_vcard +#: model:ir.actions.act_window,name:hr_employee_vcard.action_hr_employee_vcard_wizard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Configure your vCard Layout" +msgstr "Configura tu diseño de vCard" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__create_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__create_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__create_uid +msgid "Created by" +msgstr "Creado por" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__create_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__create_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__create_date +msgid "Created on" +msgstr "Creado el" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__custom +msgid "Custom" +msgstr "Personalizado" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__demo-logo +msgid "Demo Logo" +msgstr "Logo de ejemplo" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Discard" +msgstr "Descartar" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__display_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__display_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__display_name +msgid "Display Name" +msgstr "Nombre mostrado" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee +msgid "Employee" +msgstr "Empleado" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Employee vCard" +msgstr "vCard del empleado" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_field +msgid "Employee vCard Field" +msgstr "Campo de vCard de empleado" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_layout +msgid "Employee vCard Layout" +msgstr "Diseño de vCard de empleado" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_wizard +msgid "Employee vCard Wizard" +msgstr "Asistente de vCard de empleado" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__field_name +msgid "Field Name" +msgstr "Nombre del campo" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_field_ids +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_field_ids +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_field_ids +msgid "Fields" +msgstr "Campos" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__fira-mono +msgid "Fira Mono" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.actions.server,name:hr_employee_vcard.action_generate_employee_vcard +msgid "Generate vCard" +msgstr "Generar vCard" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__id +msgid "ID" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_job_title +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__job_title +msgid "Job Title" +msgstr "Puesto de trabajo" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__write_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__write_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__write_uid +msgid "Last Updated by" +msgstr "Última actualización por" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__write_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__write_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__write_date +msgid "Last Updated on" +msgstr "Última actualización el" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__lato +msgid "Lato" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_id +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Layout" +msgstr "Diseño" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_logo +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__logo +msgid "Logo" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__montserrat +msgid "Montserrat" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__name +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__name +msgid "Name" +msgstr "Nombre" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__open-sans +msgid "Open Sans" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__oswald +msgid "Oswald" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Preview vCard" +msgstr "Previsualizar vCard" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_qr +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__qr +msgid "QR" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__raleway +msgid "Raleway" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__roboto +msgid "Roboto" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Save" +msgstr "Guardar" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__sequence +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__sequence +msgid "Sequence" +msgstr "Secuencia" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__tajawal +msgid "Tajawal" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_font +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_font +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_font +msgid "Text" +msgstr "Texto" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_preview +msgid "Vcard Layout Preview" +msgstr "Previsualización del diseño de vCard" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_website +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__website +msgid "Website" +msgstr "Sitio web" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_work_email +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__work_email +msgid "Work Email" +msgstr "Correo electrónico del trabajo" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_work_phone +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__work_phone +msgid "Work Phone" +msgstr "Teléfono del trabajo" + +#. module: hr_employee_vcard +#: model:ir.actions.report,name:hr_employee_vcard.action_report_vcard +msgid "vCard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_background_image +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_background_image +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_background_image +msgid "vCard Background Image" +msgstr "Imagen de fondo de vCard" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee__vcard_qr +msgid "vCard ID" +msgstr "ID de vCard" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "vCard Layout" +msgstr "Diseño de vCard" + +#. module: hr_employee_vcard +#: model:ir.actions.report,name:hr_employee_vcard.action_report_vcard_preview +msgid "vCard Preview" +msgstr "Previsualización de vCard" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_mixin +msgid "vCard Rendering Mixin" +msgstr "Mixin de renderizado de vCard" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__view_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_view_id +msgid "vCard View" +msgstr "Vista de vCard" diff --git a/hr_employee_vcard/i18n/hr_employee_vcard.pot b/hr_employee_vcard/i18n/hr_employee_vcard.pot new file mode 100644 index 00000000000..3672b87f0a8 --- /dev/null +++ b/hr_employee_vcard/i18n/hr_employee_vcard.pot @@ -0,0 +1,325 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * hr_employee_vcard +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 18.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: hr_employee_vcard +#: model:ir.actions.report,print_report_name:hr_employee_vcard.action_report_vcard +msgid "'vCard - %s' % (object.name or '')" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_background +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_background +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_background +msgid "Background" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__blank +msgid "Blank" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Choose the layout used for vCard reports." +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_res_company +msgid "Companies" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__company_id +msgid "Company" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_company_name +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__company_name +msgid "Company Name" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_res_config_settings +msgid "Config Settings" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Configure vCard Layout" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.actions.act_window,name:hr_employee_vcard.action_hr_employee_vcard_wizard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Configure your vCard Layout" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__create_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__create_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__create_uid +msgid "Created by" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__create_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__create_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__create_date +msgid "Created on" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__custom +msgid "Custom" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_background__demo-logo +msgid "Demo Logo" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Discard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__display_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__display_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__display_name +msgid "Display Name" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee +msgid "Employee" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Employee vCard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_field +msgid "Employee vCard Field" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_layout +msgid "Employee vCard Layout" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_wizard +msgid "Employee vCard Wizard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__field_name +msgid "Field Name" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_field_ids +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_field_ids +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_field_ids +msgid "Fields" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__fira-mono +msgid "Fira Mono" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.actions.server,name:hr_employee_vcard.action_generate_employee_vcard +msgid "Generate vCard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__id +msgid "ID" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_job_title +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__job_title +msgid "Job Title" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__write_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__write_uid +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__write_uid +msgid "Last Updated by" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__write_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__write_date +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__write_date +msgid "Last Updated on" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__lato +msgid "Lato" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_id +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Layout" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_logo +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__logo +msgid "Logo" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__montserrat +msgid "Montserrat" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__name +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__name +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__name +msgid "Name" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__open-sans +msgid "Open Sans" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__oswald +msgid "Oswald" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "Preview vCard" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_qr +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__qr +msgid "QR" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__raleway +msgid "Raleway" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__roboto +msgid "Roboto" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.hr_employee_vcard_wizard_view_form +msgid "Save" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_field__sequence +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__sequence +msgid "Sequence" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__res_company__vcard_layout_font__tajawal +msgid "Tajawal" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_font +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_font +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_font +msgid "Text" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_preview +msgid "Vcard Layout Preview" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_website +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__website +msgid "Website" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_work_email +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__work_email +msgid "Work Email" +msgstr "" + +#. module: hr_employee_vcard +#: model:hr.employee.vcard.field,name:hr_employee_vcard.hr_employee_vcard_field_work_phone +#: model:ir.model.fields.selection,name:hr_employee_vcard.selection__hr_employee_vcard_field__field_name__work_phone +msgid "Work Phone" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.actions.report,name:hr_employee_vcard.action_report_vcard +msgid "vCard" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_wizard__vcard_layout_background_image +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_company__vcard_layout_background_image +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_background_image +msgid "vCard Background Image" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee__vcard_qr +msgid "vCard ID" +msgstr "" + +#. module: hr_employee_vcard +#: model_terms:ir.ui.view,arch_db:hr_employee_vcard.res_config_settings_view_form +msgid "vCard Layout" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.actions.report,name:hr_employee_vcard.action_report_vcard_preview +msgid "vCard Preview" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model,name:hr_employee_vcard.model_hr_employee_vcard_mixin +msgid "vCard Rendering Mixin" +msgstr "" + +#. module: hr_employee_vcard +#: model:ir.model.fields,field_description:hr_employee_vcard.field_hr_employee_vcard_layout__view_id +#: model:ir.model.fields,field_description:hr_employee_vcard.field_res_config_settings__vcard_layout_view_id +msgid "vCard View" +msgstr "" diff --git a/hr_employee_vcard/models/__init__.py b/hr_employee_vcard/models/__init__.py new file mode 100644 index 00000000000..24161da1603 --- /dev/null +++ b/hr_employee_vcard/models/__init__.py @@ -0,0 +1,6 @@ +from . import hr_employee_vcard_mixin +from . import hr_employee_vcard_field +from . import hr_employee_vcard_layout +from . import hr_employee +from . import res_company +from . import res_config_settings diff --git a/hr_employee_vcard/models/hr_employee.py b/hr_employee_vcard/models/hr_employee.py new file mode 100644 index 00000000000..6e794107652 --- /dev/null +++ b/hr_employee_vcard/models/hr_employee.py @@ -0,0 +1,86 @@ +import base64 + +from odoo import fields, models + + +class HrEmployee(models.Model): + _inherit = "hr.employee" + + vcard_qr = fields.Binary(string="vCard ID") + + def action_generate_vcard(self): + self.ensure_one() + self.vcard_qr = self._get_vcard_qr() + return self.action_print_vcard() + + def action_print_vcard(self): + self.ensure_one() + return self.env.ref("hr_employee_vcard.action_report_vcard").report_action( + self, config=False + ) + + def _get_vcard_field_mapping(self): + return { + "logo": lambda employee: employee.company_id.logo, + "name": lambda employee: employee.name, + "job_title": lambda employee: employee.job_title, + "work_email": lambda employee: employee.work_email, + "work_phone": lambda employee: employee.work_phone, + "company_name": lambda employee: employee.company_id.name, + "website": lambda employee: employee.company_id.website, + "qr": lambda employee: employee.vcard_qr, + } + + def _get_vcard_content_mapping(self): + return { + "name": lambda value: f"FN:{value}", + "job_title": lambda value: f"TITLE:{value}", + "company_name": lambda value: f"ORG:{value}", + "work_email": lambda value: f"EMAIL:{value}", + "work_phone": lambda value: f"TEL;TYPE=WORK:{value}", + "website": lambda value: f"URL:{value}", + } + + def _get_vcard_content(self): + self.ensure_one() + field_mapping = self._get_vcard_field_mapping() + content_mapping = self._get_vcard_content_mapping() + lines = ["BEGIN:VCARD", "VERSION:3.0"] + for field_name in self.company_id.vcard_layout_field_ids.mapped("field_name"): + resolver = field_mapping.get(field_name) + formatter = content_mapping.get(field_name) + if resolver and formatter: + value = resolver(self) + if value: + lines.append(formatter(value)) + lines.append("END:VCARD") + return "\n".join(lines) + + def _get_vcard_qr(self): + self.ensure_one() + if self.vcard_qr: + return self.vcard_qr + png = self.env["ir.actions.report"].barcode( + "QR", self._get_vcard_content(), width=200, height=200 + ) + return base64.b64encode(png) + + def _get_vcard_values(self): + self.ensure_one() + employee = self.with_context(bin_size=False) + selected_fields = employee.company_id.vcard_layout_field_ids.mapped( + "field_name" + ) + mapping = employee._get_vcard_field_mapping() + values = { + field_name: mapping[field_name](employee) + for field_name in selected_fields + if field_name in mapping + } + if "qr" in values: + values["qr"] = employee._get_vcard_qr() + return values + + def _get_vcard_card_data(self): + self.ensure_one() + return self.company_id._get_vcard_card_data(self._get_vcard_values()) diff --git a/hr_employee_vcard/models/hr_employee_vcard_field.py b/hr_employee_vcard/models/hr_employee_vcard_field.py new file mode 100644 index 00000000000..bea1a1a642b --- /dev/null +++ b/hr_employee_vcard/models/hr_employee_vcard_field.py @@ -0,0 +1,23 @@ +from odoo import fields, models + + +class HrEmployeeVcardField(models.Model): + _name = "hr.employee.vcard.field" + _description = "Employee vCard Field" + _order = "sequence, id" + + sequence = fields.Integer(default=50) + name = fields.Char(required=True, translate=True) + field_name = fields.Selection( + selection=[ + ("logo", "Logo"), + ("name", "Name"), + ("job_title", "Job Title"), + ("work_email", "Work Email"), + ("work_phone", "Work Phone"), + ("company_name", "Company Name"), + ("website", "Website"), + ("qr", "QR"), + ], + required=True, + ) diff --git a/hr_employee_vcard/models/hr_employee_vcard_layout.py b/hr_employee_vcard/models/hr_employee_vcard_layout.py new file mode 100644 index 00000000000..91105a6be18 --- /dev/null +++ b/hr_employee_vcard/models/hr_employee_vcard_layout.py @@ -0,0 +1,16 @@ +from odoo import fields, models + + +class HrEmployeeVcardLayout(models.Model): + _name = "hr.employee.vcard.layout" + _description = "Employee vCard Layout" + _order = "sequence" + + sequence = fields.Integer(default=50) + name = fields.Char(required=True) + view_id = fields.Many2one( + comodel_name="ir.ui.view", + string="vCard View", + required=True, + domain=[("type", "=", "qweb")], + ) diff --git a/hr_employee_vcard/models/hr_employee_vcard_mixin.py b/hr_employee_vcard/models/hr_employee_vcard_mixin.py new file mode 100644 index 00000000000..92e2dfb9797 --- /dev/null +++ b/hr_employee_vcard/models/hr_employee_vcard_mixin.py @@ -0,0 +1,90 @@ +from odoo import models +from odoo.tools.image import image_data_uri + +FONT_FAMILY = { + "lato": "'Lato', sans-serif", + "roboto": "'Roboto', sans-serif", + "open-sans": "'Open Sans', sans-serif", + "montserrat": "'Montserrat', sans-serif", + "oswald": "'Oswald', sans-serif", + "raleway": "'Raleway', sans-serif", + "tajawal": "'Tajawal', sans-serif", + "fira-mono": "'Fira Mono', monospace", +} + +IMAGE_FIELDS = {"logo", "qr"} + +FRONT_FIELDS = {"logo", "company_name"} + +FIELD_ICON = { + "work_email": "fa-envelope", + "work_phone": "fa-phone", + "website": "fa-globe", + "job_title": "fa-briefcase", +} + + +class HrEmployeeVcardMixin(models.AbstractModel): + _name = "hr.employee.vcard.mixin" + _description = "vCard Rendering Mixin" + + def _vcard_company(self): + self.ensure_one() + return self + + def _build_vcard_lines(self, values): + self.ensure_one() + front, back = [], [] + for field in self.vcard_layout_field_ids: + field_name = field.field_name + value = values.get(field_name) + if not value: + continue + if field_name in IMAGE_FIELDS: + line = { + "type": field_name, + "kind": "image", + "value": image_data_uri(value), + } + else: + line = { + "type": field_name, + "kind": "text", + "value": value, + "icon": FIELD_ICON.get(field_name), + } + target = front if field_name in FRONT_FIELDS else back + target.append(line) + return front, back + + def _get_vcard_card_data(self, values): + self.ensure_one() + front_lines, back_lines = self._build_vcard_lines(values) + return { + "vcard_layout_id": self.vcard_layout_id, + "front_lines": front_lines, + "back_lines": back_lines, + "font_family": FONT_FAMILY.get(self.vcard_layout_font, "inherit"), + "background_style": self._get_vcard_background_style(), + } + + def _get_vcard_background_style(self): + self.ensure_one() + record = self.with_context(bin_size=False) + image = False + if record.vcard_layout_background == "custom": + image = record.vcard_layout_background_image + elif record.vcard_layout_background == "demo-logo": + image = record._vcard_company().logo + if not image: + return "" + key = record.vcard_layout_id.view_id.key or "" + overlay = ( + "rgba(31,43,58,0.72)" + if key.endswith("modern") + else "rgba(255,255,255,0.80)" + ) + uri = image_data_uri(image) + return ( + f"background-image:-webkit-linear-gradient({overlay},{overlay}),url({uri});" + ) diff --git a/hr_employee_vcard/models/res_company.py b/hr_employee_vcard/models/res_company.py new file mode 100644 index 00000000000..415706d6f8c --- /dev/null +++ b/hr_employee_vcard/models/res_company.py @@ -0,0 +1,107 @@ +import base64 + +from odoo import fields, models + +PREVIEW_SAMPLE = { + "name": "Marc Demo", + "job_title": "Sales Manager", + "work_email": "marc.demo@example.com", + "work_phone": "+1 555 123 4567", + "website": "https://www.example.com", +} + + +class ResCompany(models.Model): + _name = "res.company" + _inherit = ["res.company", "hr.employee.vcard.mixin"] + + vcard_layout_id = fields.Many2one( + comodel_name="hr.employee.vcard.layout", + default=lambda self: self.env.ref( + "hr_employee_vcard.hr_employee_vcard_layout_modern", + raise_if_not_found=False, + ), + string="Layout", + ) + + vcard_layout_background = fields.Selection( + selection=[ + ("blank", "Blank"), + ("demo-logo", "Demo Logo"), + ("custom", "Custom"), + ], + default="blank", + required=True, + string="Background", + ) + + vcard_layout_background_image = fields.Binary(string="vCard Background Image") + + vcard_layout_font = fields.Selection( + selection=[ + ("lato", "Lato"), + ("roboto", "Roboto"), + ("open-sans", "Open Sans"), + ("montserrat", "Montserrat"), + ("oswald", "Oswald"), + ("raleway", "Raleway"), + ("tajawal", "Tajawal"), + ("fira-mono", "Fira Mono"), + ], + default="lato", + required=True, + string="Text", + ) + + vcard_layout_field_ids = fields.Many2many( + comodel_name="hr.employee.vcard.field", + default=lambda self: self._default_vcard_layout_field_ids(), + string="Fields", + ) + + def _default_vcard_layout_field_ids(self): + xmlids = [ + "hr_employee_vcard.hr_employee_vcard_field_logo", + "hr_employee_vcard.hr_employee_vcard_field_company_name", + "hr_employee_vcard.hr_employee_vcard_field_qr", + "hr_employee_vcard.hr_employee_vcard_field_name", + "hr_employee_vcard.hr_employee_vcard_field_job_title", + "hr_employee_vcard.hr_employee_vcard_field_work_email", + "hr_employee_vcard.hr_employee_vcard_field_work_phone", + "hr_employee_vcard.hr_employee_vcard_field_website", + ] + return self.env["hr.employee.vcard.field"].browse( + [ + record.id + for record in ( + self.env.ref(xmlid, raise_if_not_found=False) for xmlid in xmlids + ) + if record + ] + ) + + def _get_vcard_preview_values(self): + """Sample values for the preview (current user's employee + fallbacks).""" + self.ensure_one() + company = self.with_context(bin_size=False) + employee = self.env.user.employee_id + return { + "logo": company.logo, + "name": employee.name or PREVIEW_SAMPLE["name"], + "job_title": employee.job_title or PREVIEW_SAMPLE["job_title"], + "work_email": employee.work_email or PREVIEW_SAMPLE["work_email"], + "work_phone": employee.work_phone or PREVIEW_SAMPLE["work_phone"], + "company_name": company.name, + "website": company.website or PREVIEW_SAMPLE["website"], + "qr": (employee.vcard_qr if employee else False) + or self._get_vcard_preview_qr(), + } + + def _get_vcard_preview_qr(self): + png = self.env["ir.actions.report"].barcode( + "QR", + f"BEGIN:VCARD\nVERSION:3.0\nFN:{PREVIEW_SAMPLE['name']}\nEND:VCARD", + width=200, + height=200, + ) + return base64.b64encode(png) diff --git a/hr_employee_vcard/models/res_config_settings.py b/hr_employee_vcard/models/res_config_settings.py new file mode 100644 index 00000000000..ea05db538e7 --- /dev/null +++ b/hr_employee_vcard/models/res_config_settings.py @@ -0,0 +1,40 @@ +from odoo import fields, models + + +class ResConfigSettings(models.TransientModel): + _inherit = "res.config.settings" + + vcard_layout_id = fields.Many2one( + related="company_id.vcard_layout_id", + readonly=False, + ) + + vcard_layout_view_id = fields.Many2one( + related="vcard_layout_id.view_id", + ) + + vcard_layout_background = fields.Selection( + related="company_id.vcard_layout_background", + readonly=False, + ) + + vcard_layout_background_image = fields.Binary( + related="company_id.vcard_layout_background_image", + readonly=False, + ) + + vcard_layout_font = fields.Selection( + related="company_id.vcard_layout_font", + readonly=False, + ) + + vcard_layout_field_ids = fields.Many2many( + related="company_id.vcard_layout_field_ids", + readonly=False, + ) + + def action_preview_vcard(self): + self.ensure_one() + return self.env.ref( + "hr_employee_vcard.action_report_vcard_preview" + ).report_action(self.company_id, config=False) diff --git a/hr_employee_vcard/pyproject.toml b/hr_employee_vcard/pyproject.toml new file mode 100644 index 00000000000..4231d0cccb3 --- /dev/null +++ b/hr_employee_vcard/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/hr_employee_vcard/readme/CONFIGURE.md b/hr_employee_vcard/readme/CONFIGURE.md new file mode 100644 index 00000000000..d0d34d2663f --- /dev/null +++ b/hr_employee_vcard/readme/CONFIGURE.md @@ -0,0 +1,12 @@ +To configure the vCard design: + +1. Go to *Employees > Configuration > Settings*. +2. In the *Employee vCard* section, click *Configure vCard Layout*. +3. Choose the layout (Modern, Minimal or Classic), the font, the background + (blank, company logo or a custom image) and the fields to display on the + card. A live preview is shown as you change the options. +4. Click *Save*. + +The design is stored per company, so each company can have its own card. + +Use *Preview vCard* to download a sample PDF with the current design. diff --git a/hr_employee_vcard/readme/CONTRIBUTORS.md b/hr_employee_vcard/readme/CONTRIBUTORS.md new file mode 100644 index 00000000000..424d9957ad3 --- /dev/null +++ b/hr_employee_vcard/readme/CONTRIBUTORS.md @@ -0,0 +1,2 @@ +- [Vortex Dimensión Digital](https://www.dimensionvortex.com/): + - Jorge Rosado Julián \<\> \ No newline at end of file diff --git a/hr_employee_vcard/readme/DESCRIPTION.md b/hr_employee_vcard/readme/DESCRIPTION.md new file mode 100644 index 00000000000..0568de5b507 --- /dev/null +++ b/hr_employee_vcard/readme/DESCRIPTION.md @@ -0,0 +1,12 @@ +This module lets you design and print business cards (vCards) for your +employees. + +A company-wide card design is configured once (layout, font, background and +which fields to show) and can be printed for any employee as a two-sided PDF: +the front holds the branding (logo, company name) and the back the contact +details (name, job position, email, phone, website). Each card includes a QR +code with the employee's contact information, so it can be scanned straight +into a phone's address book. + +The card adapts to the data available: only the selected fields that actually +have a value are shown. diff --git a/hr_employee_vcard/readme/USAGE.md b/hr_employee_vcard/readme/USAGE.md new file mode 100644 index 00000000000..cbacf42d6aa --- /dev/null +++ b/hr_employee_vcard/readme/USAGE.md @@ -0,0 +1,7 @@ +To print an employee's vCard: + +1. Open the employee form (*Employees > Employees*). +2. In the *Actions* menu, choose *Generate vCard*. + +The QR code is (re)generated from the employee's contact details and a +two-sided card PDF is downloaded, ready to print. diff --git a/hr_employee_vcard/reports/hr_employee_vcard_reports.xml b/hr_employee_vcard/reports/hr_employee_vcard_reports.xml new file mode 100644 index 00000000000..becccd567a4 --- /dev/null +++ b/hr_employee_vcard/reports/hr_employee_vcard_reports.xml @@ -0,0 +1,74 @@ + + + + vCard + hr.employee + qweb-pdf + hr_employee_vcard.report_vcard + hr_employee_vcard.report_vcard + 'vCard - %s' % (object.name or '') + + report + + + + + + vCard Preview + res.company + qweb-pdf + hr_employee_vcard.report_vcard_preview + hr_employee_vcard.report_vcard_preview + + + + diff --git a/hr_employee_vcard/security/ir.model.access.csv b/hr_employee_vcard/security/ir.model.access.csv new file mode 100644 index 00000000000..b8e945fbf8f --- /dev/null +++ b/hr_employee_vcard/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +hr_employee_vcard.access_hr_employee_vcard_layout,access_hr_employee_vcard_layout,hr_employee_vcard.model_hr_employee_vcard_layout,base.group_user,1,1,1,1 +hr_employee_vcard.access_hr_employee_vcard_wizard,access_hr_employee_vcard_wizard,hr_employee_vcard.model_hr_employee_vcard_wizard,base.group_user,1,1,1,1 +hr_employee_vcard.access_hr_employee_vcard_field,access_hr_employee_vcard_field,hr_employee_vcard.model_hr_employee_vcard_field,base.group_user,1,1,1,1 diff --git a/hr_employee_vcard/static/description/icon.png b/hr_employee_vcard/static/description/icon.png new file mode 100644 index 00000000000..3a0328b516c Binary files /dev/null and b/hr_employee_vcard/static/description/icon.png differ diff --git a/hr_employee_vcard/static/description/index.html b/hr_employee_vcard/static/description/index.html new file mode 100644 index 00000000000..6521fb7599b --- /dev/null +++ b/hr_employee_vcard/static/description/index.html @@ -0,0 +1,447 @@ + + + + + +HR Employee vCard + + + +
+

HR Employee vCard

+ + +

Beta LGPL-3

+

This module lets you design and print business cards (vCards) for your +employees.

+

A company-wide card design is configured once (layout, font, background +and which fields to show) and can be printed for any employee as a +two-sided PDF: the front holds the branding (logo, company name) and the +back the contact details (name, job position, email, phone, website). +Each card includes a QR code with the employee’s contact information, so +it can be scanned straight into a phone’s address book.

+

The card adapts to the data available: only the selected fields that +actually have a value are shown.

+

Table of contents

+ +
+

Configuration

+

To configure the vCard design:

+
    +
  1. Go to Employees > Configuration > Settings.
  2. +
  3. In the Employee vCard section, click Configure vCard Layout.
  4. +
  5. Choose the layout (Modern, Minimal or Classic), the font, the +background (blank, company logo or a custom image) and the fields to +display on the card. A live preview is shown as you change the +options.
  6. +
  7. Click Save.
  8. +
+

The design is stored per company, so each company can have its own card.

+

Use Preview vCard to download a sample PDF with the current design.

+
+
+

Usage

+

To print an employee’s vCard:

+
    +
  1. Open the employee form (Employees > Employees).
  2. +
  3. In the Actions menu, choose Generate vCard.
  4. +
+

The QR code is (re)generated from the employee’s contact details and a +two-sided card PDF is downloaded, ready to print.

+
+
+

Credits

+
+

Authors

+
    +
  • Vortex Dimensión Digital
  • +
+
+ +
+

Maintainers

+

This module is maintained by Vortex Dimensión Digital.

+

Contact the maintainer through their official support channels in case you find +any issues with this module.

+
+
+
+ + diff --git a/hr_employee_vcard/static/src/scss/hr_employee_vcard.scss b/hr_employee_vcard/static/src/scss/hr_employee_vcard.scss new file mode 100644 index 00000000000..193bf896827 --- /dev/null +++ b/hr_employee_vcard/static/src/scss/hr_employee_vcard.scss @@ -0,0 +1,181 @@ +$o-vcard-width: 8.5cm; +$o-vcard-height: 5.4cm; +$o-vcard-radius: 0.25cm; + +$o-vcard-primary: #714b67; +$o-vcard-dark: #1f2b3a; +$o-vcard-text: #212529; +$o-vcard-muted: #8a8a8a; +$o-vcard-border: #e0e0e0; + +.o_hr_employee_vcard { + text-align: center; + + .o_hr_employee_vcard_face { + width: $o-vcard-width; + height: $o-vcard-height; + margin: 0 auto 0.4cm auto; + -webkit-box-sizing: border-box; + box-sizing: border-box; + position: relative; + overflow: hidden; + border-radius: $o-vcard-radius; + background-color: #ffffff; + background-repeat: no-repeat; + background-position: center center; + background-size: cover; + display: table; + } + + .o_hr_employee_vcard_content { + display: table-cell; + vertical-align: middle; + text-align: center; + padding: 0.45cm; + } + + .o_hr_employee_vcard_line { + margin: 0 0 0.1cm 0; + line-height: 1.2; + word-wrap: break-word; + } + + .o_hr_employee_vcard_img { + display: block; + margin: 0 auto; + } + + .o_hr_employee_vcard_logo { + max-width: 3.4cm; + max-height: 1.3cm; + margin-bottom: 0.15cm; + } + + .o_hr_employee_vcard_line_name { + font-size: 15px; + font-weight: bold; + } + + .o_hr_employee_vcard_line_job_title { + font-size: 10px; + text-transform: uppercase; + letter-spacing: 0.05cm; + color: $o-vcard-muted; + } + + .o_hr_employee_vcard_line_company_name { + font-size: 13px; + font-weight: bold; + } + + .o_hr_employee_vcard_line_work_email, + .o_hr_employee_vcard_line_work_phone, + .o_hr_employee_vcard_line_website { + font-size: 10px; + } + + .o_hr_employee_vcard_icon { + display: inline-block; + width: 0.5cm; + height: 0.5cm; + line-height: 0.5cm; + margin-right: 0.15cm; + border-radius: 50%; + background-color: $o-vcard-primary; + color: #ffffff; + font-size: 8px; + text-align: center; + vertical-align: middle; + } + + .o_hr_employee_vcard_value { + vertical-align: middle; + } + + .o_hr_employee_vcard_back .o_hr_employee_vcard_content { + text-align: left; + padding-right: 2.2cm; + } + + .o_hr_employee_vcard_back .o_hr_employee_vcard_line_qr { + position: absolute; + top: 50%; + right: 0.5cm; + width: 1.7cm; + margin: -0.85cm 0 0 0; + } + + .o_hr_employee_vcard_qr { + max-width: 1.7cm; + max-height: 1.7cm; + } +} + +.o_hr_employee_vcard_modern { + .o_hr_employee_vcard_face { + background-color: $o-vcard-dark; + color: #ffffff; + } + + .o_hr_employee_vcard_line_company_name { + color: #ffffff; + } + + .o_hr_employee_vcard_line_job_title { + color: $o-vcard-primary; + } + + .o_hr_employee_vcard_line_name { + display: inline-block; + padding-bottom: 0.08cm; + border-bottom: 0.05cm solid $o-vcard-primary; + } + + .o_hr_employee_vcard_qr { + background-color: #ffffff; + padding: 0.1cm; + border-radius: 0.1cm; + } +} + +.o_hr_employee_vcard_minimal { + .o_hr_employee_vcard_face { + border: 1px solid $o-vcard-border; + } + + .o_hr_employee_vcard_line_name { + font-weight: normal; + letter-spacing: 0.03cm; + } + + .o_hr_employee_vcard_line_company_name { + font-weight: normal; + color: $o-vcard-muted; + } + + .o_hr_employee_vcard_icon { + width: auto; + height: auto; + line-height: 1; + margin-right: 0.12cm; + background-color: transparent; + color: $o-vcard-primary; + font-size: 10px; + } +} + +.o_hr_employee_vcard_classic { + .o_hr_employee_vcard_face { + border: 1px solid $o-vcard-border; + border-left: 0.25cm solid $o-vcard-primary; + } + + .o_hr_employee_vcard_line_company_name { + color: $o-vcard-primary; + } + + .o_hr_employee_vcard_back .o_hr_employee_vcard_line_name { + padding-bottom: 0.1cm; + border-bottom: 1px solid $o-vcard-border; + } +} diff --git a/hr_employee_vcard/tests/__init__.py b/hr_employee_vcard/tests/__init__.py new file mode 100644 index 00000000000..10e9817b184 --- /dev/null +++ b/hr_employee_vcard/tests/__init__.py @@ -0,0 +1 @@ +from . import test_hr_employee_vcard diff --git a/hr_employee_vcard/tests/test_hr_employee_vcard.py b/hr_employee_vcard/tests/test_hr_employee_vcard.py new file mode 100644 index 00000000000..22ad51eb4bd --- /dev/null +++ b/hr_employee_vcard/tests/test_hr_employee_vcard.py @@ -0,0 +1,189 @@ +import base64 + +from odoo.tests import Form, TransactionCase + + +class TestHrEmployeeVcard(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.Report = cls.env["ir.actions.report"] + cls.company = cls.env.company + cls.layout_modern = cls.env.ref( + "hr_employee_vcard.hr_employee_vcard_layout_modern" + ) + cls.layout_minimal = cls.env.ref( + "hr_employee_vcard.hr_employee_vcard_layout_minimal" + ) + cls.field_website = cls.env.ref( + "hr_employee_vcard.hr_employee_vcard_field_website" + ) + # A valid PNG to use as an uploaded image. + cls.png = base64.b64encode(cls.Report.barcode("QR", "sample")) + # Put the company in a known configuration. + cls.company.write( + { + "vcard_layout_id": cls.layout_modern.id, + "vcard_layout_background": "blank", + "vcard_layout_font": "lato", + "vcard_layout_field_ids": [ + (6, 0, cls.env["hr.employee.vcard.field"].search([]).ids) + ], + } + ) + cls.employee = cls.env["hr.employee"].create( + { + "name": "Test Employee", + "job_title": "Tester", + "work_email": "test@example.com", + "work_phone": "+1 000 000", + "company_id": cls.company.id, + } + ) + + # ------------------------------------------------------------------ + # Company / defaults + # ------------------------------------------------------------------ + def test_new_company_defaults(self): + company = self.env["res.company"].create({"name": "vCard Test Co"}) + self.assertEqual(company.vcard_layout_id, self.layout_modern) + self.assertTrue(company.vcard_layout_field_ids) + self.assertEqual(company.vcard_layout_background, "blank") + self.assertEqual(company.vcard_layout_font, "lato") + + # ------------------------------------------------------------------ + # Line building (mixin) + # ------------------------------------------------------------------ + def test_build_lines_front_back_split(self): + values = self.company._get_vcard_preview_values() + front, back = self.company._build_vcard_lines(values) + front_types = [line["type"] for line in front] + back_types = [line["type"] for line in back] + self.assertIn("company_name", front_types) + self.assertIn("name", back_types) + self.assertNotIn("name", front_types) + self.assertNotIn("company_name", back_types) + + def test_lines_adapt_to_selected_fields(self): + self.company.vcard_layout_field_ids -= self.field_website + values = self.company._get_vcard_preview_values() + _front, back = self.company._build_vcard_lines(values) + self.assertNotIn("website", [line["type"] for line in back]) + + def test_lines_skip_empty_values(self): + values = self.company._get_vcard_preview_values() + values["work_email"] = False + _front, back = self.company._build_vcard_lines(values) + self.assertNotIn("work_email", [line["type"] for line in back]) + + def test_contact_line_has_icon(self): + values = self.company._get_vcard_preview_values() + _front, back = self.company._build_vcard_lines(values) + email = next(line for line in back if line["type"] == "work_email") + self.assertEqual(email["kind"], "text") + self.assertTrue(email["icon"]) + + def test_image_line_is_data_uri(self): + values = self.company._get_vcard_preview_values() + _front, back = self.company._build_vcard_lines(values) + qr = next(line for line in back if line["type"] == "qr") + self.assertEqual(qr["kind"], "image") + self.assertTrue(qr["value"].startswith("data:image/")) + + # ------------------------------------------------------------------ + # Card data (font / background) + # ------------------------------------------------------------------ + def test_card_data_font_family(self): + self.company.vcard_layout_font = "roboto" + data = self.company._get_vcard_card_data( + self.company._get_vcard_preview_values() + ) + self.assertIn("Roboto", data["font_family"]) + + def test_background_blank_is_empty(self): + self.company.vcard_layout_background = "blank" + self.assertEqual(self.company._get_vcard_background_style(), "") + + def test_background_custom_overlay(self): + self.company.write( + { + "vcard_layout_background": "custom", + "vcard_layout_background_image": self.png, + } + ) + style = self.company._get_vcard_background_style() + self.assertIn("background-image", style) + self.assertIn("linear-gradient", style) + + # ------------------------------------------------------------------ + # Employee: vCard content + QR + actions + # ------------------------------------------------------------------ + def test_vcard_content(self): + content = self.employee._get_vcard_content() + self.assertTrue(content.startswith("BEGIN:VCARD")) + self.assertTrue(content.strip().endswith("END:VCARD")) + self.assertIn("FN:Test Employee", content) + self.assertIn("EMAIL:test@example.com", content) + + def test_get_vcard_qr_is_png(self): + qr = self.employee._get_vcard_qr() + self.assertEqual(base64.b64decode(qr)[:4], b"\x89PNG") + + def test_action_generate_vcard(self): + self.employee.vcard_qr = False + action = self.employee.action_generate_vcard() + self.assertTrue(self.employee.vcard_qr) + self.assertEqual(action["type"], "ir.actions.report") + self.assertEqual(action["report_name"], "hr_employee_vcard.report_vcard") + + def test_action_print_vcard(self): + action = self.employee.action_print_vcard() + self.assertEqual(action["report_name"], "hr_employee_vcard.report_vcard") + + # ------------------------------------------------------------------ + # Wizard + # ------------------------------------------------------------------ + def test_wizard_onchange_sets_default_layout(self): + self.company.vcard_layout_id = False + wizard = self.env["hr.employee.vcard.wizard"].new( + {"company_id": self.company.id} + ) + wizard._onchange_company_id() + self.assertTrue(wizard.vcard_layout_id) + + def test_wizard_preview_reacts_to_fields(self): + form = Form(self.env["hr.employee.vcard.wizard"]) + self.assertIn("o_hr_employee_vcard_line_website", form.vcard_layout_preview) + form.vcard_layout_field_ids.remove(id=self.field_website.id) + self.assertNotIn("o_hr_employee_vcard_line_website", form.vcard_layout_preview) + + def test_wizard_preview_reacts_to_layout(self): + form = Form(self.env["hr.employee.vcard.wizard"]) + form.vcard_layout_id = self.layout_minimal + self.assertIn("o_hr_employee_vcard_minimal", form.vcard_layout_preview) + + # ------------------------------------------------------------------ + # Settings preview action + # ------------------------------------------------------------------ + def test_settings_preview_action(self): + settings = self.env["res.config.settings"].create({}) + action = settings.action_preview_vcard() + self.assertEqual( + action["report_name"], "hr_employee_vcard.report_vcard_preview" + ) + + # ------------------------------------------------------------------ + # Reports render (HTML, no wkhtmltopdf) + # ------------------------------------------------------------------ + def test_report_vcard_html(self): + html = self.Report._render_qweb_html( + "hr_employee_vcard.report_vcard", self.employee.ids + )[0] + self.assertIn(b"o_hr_employee_vcard", html) + self.assertIn(b'class="article"', html) + + def test_report_vcard_preview_html(self): + html = self.Report._render_qweb_html( + "hr_employee_vcard.report_vcard_preview", self.company.ids + )[0] + self.assertIn(b"o_hr_employee_vcard", html) diff --git a/hr_employee_vcard/views/hr_employee_vcard_templates.xml b/hr_employee_vcard/views/hr_employee_vcard_templates.xml new file mode 100644 index 00000000000..a69732bb960 --- /dev/null +++ b/hr_employee_vcard/views/hr_employee_vcard_templates.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + diff --git a/hr_employee_vcard/views/hr_employee_views.xml b/hr_employee_vcard/views/hr_employee_views.xml new file mode 100644 index 00000000000..c801ffbdd08 --- /dev/null +++ b/hr_employee_vcard/views/hr_employee_views.xml @@ -0,0 +1,12 @@ + + + + Generate vCard + + + action + form + code + action = records.action_generate_vcard() + + diff --git a/hr_employee_vcard/views/res_config_settings_views.xml b/hr_employee_vcard/views/res_config_settings_views.xml new file mode 100644 index 00000000000..9a52c960884 --- /dev/null +++ b/hr_employee_vcard/views/res_config_settings_views.xml @@ -0,0 +1,53 @@ + + + + res.config.settings.view.form + res.config.settings + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hr_employee_vcard/wizards/__init__.py b/hr_employee_vcard/wizards/__init__.py new file mode 100644 index 00000000000..1975fb931c4 --- /dev/null +++ b/hr_employee_vcard/wizards/__init__.py @@ -0,0 +1 @@ +from . import hr_employee_vcard_wizard diff --git a/hr_employee_vcard/wizards/hr_employee_vcard_wizard.py b/hr_employee_vcard/wizards/hr_employee_vcard_wizard.py new file mode 100644 index 00000000000..a8a9c891ef7 --- /dev/null +++ b/hr_employee_vcard/wizards/hr_employee_vcard_wizard.py @@ -0,0 +1,79 @@ +from odoo import api, fields, models + + +class HrEmployeeVcardWizard(models.TransientModel): + _name = "hr.employee.vcard.wizard" + _inherit = "hr.employee.vcard.mixin" + _description = "Employee vCard Wizard" + + company_id = fields.Many2one( + comodel_name="res.company", + default=lambda self: self.env.company, + required=True, + ) + + vcard_layout_id = fields.Many2one( + related="company_id.vcard_layout_id", readonly=False, required=True + ) + + vcard_layout_background = fields.Selection( + related="company_id.vcard_layout_background", readonly=False, required=True + ) + + vcard_layout_background_image = fields.Binary( + related="company_id.vcard_layout_background_image", + readonly=False, + ) + + vcard_layout_font = fields.Selection( + related="company_id.vcard_layout_font", readonly=False, required=True + ) + + vcard_layout_field_ids = fields.Many2many( + related="company_id.vcard_layout_field_ids", + readonly=False, + ) + + vcard_layout_preview = fields.Html( + compute="_compute_vcard_layout_preview", sanitize=False + ) + + @api.onchange("company_id") + def _onchange_company_id(self): + for wizard in self: + if not wizard.vcard_layout_id: + wizard.vcard_layout_id = self.env["hr.employee.vcard.layout"].search( + [], order="sequence", limit=1 + ) + + @api.depends( + "vcard_layout_id", + "vcard_layout_background", + "vcard_layout_background_image", + "vcard_layout_font", + "vcard_layout_field_ids", + ) + def _compute_vcard_layout_preview(self): + for wizard in self: + if wizard.vcard_layout_id: + if wizard.env.context.get("bin_size"): + wizard = wizard.with_context(bin_size=False) + wizard.vcard_layout_preview = wizard.env["ir.ui.view"]._render_template( + wizard._get_vcard_layout_template(), + wizard._get_vcard_layout_information(), + ) + else: + wizard.vcard_layout_preview = False + + def _vcard_company(self): + self.ensure_one() + return self.company_id + + def _get_vcard_layout_template(self): + return "hr_employee_vcard.hr_employee_vcard_preview" + + def _get_vcard_layout_information(self): + self.ensure_one() + data = self._get_vcard_card_data(self.company_id._get_vcard_preview_values()) + data["preview_css"] = "" + return data diff --git a/hr_employee_vcard/wizards/hr_employee_vcard_wizard.xml b/hr_employee_vcard/wizards/hr_employee_vcard_wizard.xml new file mode 100644 index 00000000000..bfab0478843 --- /dev/null +++ b/hr_employee_vcard/wizards/hr_employee_vcard_wizard.xml @@ -0,0 +1,60 @@ + + + + hr.employee.vcard.wizard.view.form + hr.employee.vcard.wizard + +
+ + + + + + + + + + +
+ +
+
+
+
+
+ +
+
+ + + Configure your vCard Layout + ir.actions.act_window + hr.employee.vcard.wizard + form + new + +