Merge branch 'Bemade:17.0' into 17.0

This commit is contained in:
Marc Durepos 2024-03-21 08:18:50 -04:00 committed by GitHub
commit b7f198495b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1984 additions and 845 deletions

33
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: tests
on:
pull_request:
branches:
- "17.0-test-ci"
push:
branches:
- "17.0-test-ci"
jobs:
test:
runs-on: ubuntu-22.04
container: ghcr.io/bemade/test-odoo_arm64:latest
name: Test Repo Addons With Odoo
strategy:
fail-fast: false
services:
postgres:
image: postgres:12.0
env:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
POSTGRES_DB: odoo
ports:
- 5432:5432
steps:
- uses: actions/checkout@v4
with:
persist-credientials: false
- name: Run Tests
run: run_tests.sh

View file

@ -1,6 +1,6 @@
{
'name': 'Account Credit Hold',
'version': '16.0.1.0.0',
'version': '17.0.1.0.0',
'summary': 'Allows setting clients on credit hold, blocking the ability confirm a new sales order.',
'description': 'Allows setting clients on hold, blocking the ability confirm a new sales order.',
'category': 'Accounting/Accounting',

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Change default when adding follower",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Change default when adding follower',
"description": """

View file

@ -5,4 +5,7 @@ from odoo import models, fields, api
class MailWizardInviteDefault(models.TransientModel):
_inherit = 'mail.wizard.invite'
send_mail = fields.Boolean(default=False, help="If true, an invitation email will be sent to the recipient")
send_mail = fields.Boolean(
default=False,
help="If true, an invitation email will be sent to the recipient"
)

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Fetchmail Only on production environment",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Fetchmail Only on production environment',
"description": """
@ -10,7 +10,7 @@
"author": "Bemade",
'website': 'https://www.bemade.org',
"depends": [
'fetchmail',
'mail',
],
"data": [
],

View file

@ -52,27 +52,18 @@
'views/menus.xml',
'views/task_views.xml',
'views/sale_order_views.xml',
<<<<<<< HEAD
'reports/worksheet_custom_report_templates.xml',
'reports/worksheet_custom_reports.xml',
=======
# 'reports/worksheet_custom_report_templates.xml',
# 'reports/worksheet_custom_reports.xml',
>>>>>>> 5a006ec3
'wizard/new_task_from_template.xml',
],
'assets': {
'web.assets_tests': [
'bemade_fsm/static/tests/tours/task_template_tour.js',
'bemade_fsm/static/tests/tours/equipment_tour.js',
'bemade_fsm/static/tests/tours/sale_order_tour.js',
],
'web.report_assets_common': [
'bemade_fsm/static/src/scss/bemade_fsm.scss'
],
'web.assets_backend': [
'bemade_fsm/static/src/js/kanban_view.js',
'bemade_fsm/static/src/js/list_view.js',
# BV: need to readd these files
# 'bemade_fsm/static/src/js/kanban_view.js',
# 'bemade_fsm/static/src/js/list_view.js',
],
'web.assets_qweb': [
'bemade_fsm/static/src/xml/project_view_buttons.xml',

View file

@ -4,7 +4,8 @@
<record id="planning_project_stage_waiting_parts" model="project.task.type">
<field name="sequence">2</field>
<field name="name">Waiting on Parts</field>
<field name="legend_blocked">Blocked</field>
<!-- BV: legend_blocked n'existe plus -->
<!-- BV: <field name="legend_blocked">Blocked</field>-->
<field name="fold" eval="False"/>
<!-- <field name="is_closed" eval="False"/>-->
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
@ -12,7 +13,7 @@
<record id="planning_project_stage_work_completed" model="project.task.type">
<field name="sequence">15</field>
<field name="name">Work Executed</field>
<field name="legend_blocked">Blocked</field>
<!-- BV: <field name="legend_blocked">Blocked</field>-->
<field name="fold" eval="False"/>
<!-- <field name="is_closed" eval="False"/>-->
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
@ -20,7 +21,7 @@
<record id="planning_project_stage_exception" model="project.task.type">
<field name="sequence">19</field>
<field name="name">Exception</field>
<field name="legend_blocked">Blocked</field>
<!-- BV: <field name="legend_blocked">Blocked</field>-->
<field name="fold" eval="False"/>
<!-- <field name="is_closed" eval="False"/>-->
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
@ -35,8 +36,9 @@
<record id="industry_fsm.fsm_project" model="project.project">
<field name="type_ids"
eval="[(4, ref('industry_fsm.planning_project_stage_0')), (4, ref('industry_fsm.planning_project_stage_1')), (4, ref('industry_fsm.planning_project_stage_2')), (4, ref('planning_project_stage_work_completed')), (4, ref('industry_fsm.planning_project_stage_3')), (4, ref('industry_fsm.planning_project_stage_4'))]"/>
<field name="allow_subtasks"
eval="True"/>
<!-- BV: allow_subtask n'existe plus-->
<!-- <field name="allow_subtasks"-->
<!-- eval="True"/>-->
</record>
<function model="ir.model.data" name="write">
<function name="search" model="ir.model.data">

View file

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 15.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-10-02 20:26+0000\n"
"PO-Revision-Date: 2023-10-02 20:26+0000\n"
"POT-Creation-Date: 2024-02-13 17:09+0000\n"
"PO-Revision-Date: 2024-02-13 17:09+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -16,7 +16,7 @@ msgstr ""
"Plural-Forms: \n"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid ""
"<span groups=\"account.group_show_line_subtotals_tax_excluded\">\n"
" Amount</span>\n"
@ -24,9 +24,9 @@ msgid ""
" Total Price</span>"
msgstr ""
"<span groups=\"account.group_show_line_subtotals_tax_excluded\">\n"
" Prix</span>\n"
" Montant</span>\n"
" <span groups=\"account.group_show_line_subtotals_tax_included\">\n"
" Prix</span>"
" Prix Total</span>"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_signature_block
@ -40,12 +40,12 @@ msgstr ""
" </span>"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "<span>Disc.%</span>"
msgstr "<span>Escompte %</span>"
msgstr "<span>% Escompte</span>"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid ""
"<strong class=\"mr16\">Section\n"
" Subtotal</strong>"
@ -54,19 +54,19 @@ msgstr ""
" Sous-total</strong>"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "<strong>Taxes</strong>"
msgstr ""
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "<strong>Total</strong>"
msgstr ""
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "<strong>Untaxed amount</strong>"
msgstr "<strong>Montant hors taxes</strong>"
msgstr "<strong>Montant hors-taxes</strong>"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__message_needaction
@ -78,20 +78,15 @@ msgstr "Action requise"
msgid "Activities"
msgstr "Activités"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_exception_decoration
msgid "Activity Exception Decoration"
msgstr ""
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_state
msgid "Activity State"
msgstr ""
msgstr "État de l'activité"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_type_icon
msgid "Activity Type Icon"
msgstr ""
msgstr "Icône du type d'activité"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__tag_ids
@ -119,14 +114,12 @@ msgstr "Bloqué"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__allow_billable
msgid "Can be billed"
msgstr ""
msgstr "Peut être facturé"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_res_partner__is_site_contact
#: model:ir.model.fields,help:bemade_fsm.field_res_users__is_site_contact
msgid ""
"Check if the contact is a site contact, otherwise it is a not in that list"
msgstr ""
#: model_terms:ir.ui.view,arch_db:bemade_fsm.project_task_from_template_wizard_view_form
msgid "Cancel"
msgstr "Annuler"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_bemade_fsm_equipment__tag_ids
@ -178,11 +171,37 @@ msgstr ""
msgid "Contacts and Equipment"
msgstr "Contacts et équipements"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.project_task_from_template_wizard_view_form
msgid "Create Task"
msgstr "Créer une tâche"
#. module: bemade_fsm
#. openerp-web
#: code:addons/bemade_fsm/static/src/xml/project_view_buttons.xml:0
#: code:addons/bemade_fsm/static/src/xml/project_view_buttons.xml:0
#, python-format
msgid "Create Task from Template"
msgstr "Créer une tâche à partir d'un gabarit"
#. module: bemade_fsm
#: model:ir.model,name:bemade_fsm.model_project_task_from_template_wizard
msgid "Create Task from Template Wizard"
msgstr "Assistant pour création de tâche à partir d'un gabarit"
#. module: bemade_fsm
#. openerp-web
#: code:addons/bemade_fsm/static/src/xml/project_view_buttons.xml:0
#, python-format
msgid "Create from Template"
msgstr "Créer à partir d'un gabarit"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__create_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__create_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__create_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__create_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__create_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__create_uid
msgid "Created by"
msgstr "Créé par"
@ -192,6 +211,7 @@ msgstr "Créé par"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__create_date
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__create_date
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__create_date
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__create_date
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__create_date
msgid "Created on"
msgstr "Date de création"
@ -206,6 +226,11 @@ msgstr "Client"
msgid "Customer:"
msgstr "Client :"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_timesheet_entries
msgid "Date"
msgstr ""
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__assignees
msgid "Default Assignees"
@ -247,7 +272,8 @@ msgid "Default tags for tasks created from this template."
msgstr "Étiquettes par défaut pour tâches créées à partir de ce gabarit."
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_materials_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "Delivered"
msgstr "Livré"
@ -255,7 +281,8 @@ msgstr "Livré"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__description
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__description
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_form_view
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_materials_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "Description"
msgstr ""
@ -274,6 +301,7 @@ msgstr "Détails"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__display_name
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__display_name
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__display_name
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__display_name
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__display_name
msgid "Display Name"
msgstr "Nom pour affichage"
@ -286,7 +314,7 @@ msgstr "Employés assignés aux tâches créées à partir de ce gabarit."
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__planned_date_end
msgid "End Date"
msgstr ""
msgstr "Date de fin"
#. module: bemade_fsm
#: model:ir.actions.act_window,name:bemade_fsm.action_window_equipment
@ -302,8 +330,9 @@ msgstr "Équipement désservi"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_res_partner__equipment_count
#: model:ir.model.fields,field_description:bemade_fsm.field_res_users__equipment_count
msgid "Equipment Count"
msgstr ""
msgstr "Nombre d'équipemenets"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__complete_name
@ -313,7 +342,7 @@ msgstr "Nom d'équipement"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_equipment_summary
msgid "Equipment Serviced"
msgstr "Équipement désservi"
msgstr "Équipement(s) désservi"
#. module: bemade_fsm
#: model:ir.actions.act_window,name:bemade_fsm.action_window_equipment_tags
@ -349,11 +378,6 @@ msgstr "Durée estimée"
msgid "Exception"
msgstr ""
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__failed_message_ids
msgid "Failed Messages"
msgstr "Messages échoués"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.bemade_fsm_project_task_form_inherit
#: model_terms:ir.ui.view,arch_db:bemade_fsm.partner_equipment_location_view_form
@ -389,7 +413,7 @@ msgstr "Abonnés (partenaires)"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_bemade_fsm_equipment__activity_type_icon
msgid "Font awesome icon e.g. fa-tasks"
msgstr ""
msgstr "Icône Font Awesome p.ex. fa-tasks"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__is_fully_delivered
@ -416,9 +440,10 @@ msgstr "A un message"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__id
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__id
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__id
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__id
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__id
msgid "ID"
msgstr ""
msgstr "Identifiant"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_exception_icon
@ -456,6 +481,8 @@ msgid ""
"Indicates whether a line or all the lines in a section have been entirely "
"delivered and invoiced."
msgstr ""
"Indique si une ligne ou toutes les lignes dans une section ont été "
"entièrement livrées et facturées."
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_bemade_fsm_visit__is_completed
@ -487,6 +514,12 @@ msgstr ""
msgid "Invoiced"
msgstr "Facturé"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order__is_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__is_fsm
msgid "Is FSM"
msgstr "Est FSM"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__is_field_service
msgid "Is Field Service"
@ -501,7 +534,7 @@ msgstr "Est un abonné"
#: model:ir.model.fields,field_description:bemade_fsm.field_res_partner__is_site_contact
#: model:ir.model.fields,field_description:bemade_fsm.field_res_users__is_site_contact
msgid "Is a site contact"
msgstr ""
msgstr "Est un contact sur site"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__label
@ -513,6 +546,7 @@ msgstr "Nom"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag____last_update
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type____last_update
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit____last_update
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard____last_update
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template____last_update
msgid "Last Modified on"
msgstr "Dernière modification"
@ -522,6 +556,7 @@ msgstr "Dernière modification"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__write_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__write_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__write_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__write_uid
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__write_uid
msgid "Last Updated by"
msgstr "Modifié par"
@ -531,6 +566,7 @@ msgstr "Modifié par"
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_tag__write_date
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment_type__write_date
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__write_date
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__write_date
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__write_date
msgid "Last Updated on"
msgstr "Dernière mise à jour"
@ -550,6 +586,11 @@ msgstr "Pièce jointe principale"
msgid "Mark as Delivered"
msgstr "Marquer comme livré"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_materials_table
msgid "Materials"
msgstr "Matériaux"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__message_has_error
msgid "Message Delivery error"
@ -560,6 +601,15 @@ msgstr "Erreur de livraison de message"
msgid "Messages"
msgstr ""
#. module: bemade_fsm
#: code:addons/bemade_fsm/models/task.py:0
#, python-format
msgid ""
"Modifying the planned start or end time on a task is not permitted when that"
" task already has planning records associated to it. Please modify or delete"
" the planning records instead."
msgstr ""
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__my_activity_date_deadline
msgid "My Activity Deadline"
@ -573,9 +623,14 @@ msgid "Name"
msgstr "Nom"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_calendar_event_id
msgid "Next Activity Calendar Event"
msgstr "Évènement au calendrier de la prochaine activité"
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__new_task_title
msgid "New Task Title"
msgstr "Nouveau titre de tâche"
#. module: bemade_fsm
#: model:ir.actions.act_window,name:bemade_fsm.action_new_task_from_template
msgid "New Task from Template"
msgstr "Nouvelle tâche à partir d'un gabarit"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_equipment__activity_date_deadline
@ -618,7 +673,8 @@ msgid "Number of unread messages"
msgstr "Nombre de messages non-lus"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_materials_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "Ordered"
msgstr "Commandé"
@ -636,6 +692,7 @@ msgid "P&ID Tag"
msgstr "Tag P&ID"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.project_task_view_search_fsm_inherit
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_search_view
msgid "Parent Task"
msgstr "Tâche parent"
@ -661,10 +718,25 @@ msgstr "Notes sur l'emplacement physique"
msgid "Plan as field service"
msgstr "Planifier comme service terrain"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid "Planned end:"
msgstr "Fin planifiée :"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid "Purchase Order:"
msgstr "Commande :"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid "Planned start:"
msgstr "Début planifié :"
#. module: bemade_fsm
#: model:ir.model,name:bemade_fsm.model_product_template
msgid "Product Template"
msgstr "Modèle de produit"
msgstr "Modèle d'article"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_product_product__is_field_service
@ -677,10 +749,21 @@ msgstr ""
" considéré lors de la planification."
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__project_id
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_search_view
msgid "Project"
msgstr "Projet"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__propagate_assignment
msgid "Propagate Assignment"
msgstr "Propager l'assignation"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_project_task__propagate_assignment
msgid "Propagate assignment of this task to all subtasks."
msgstr "Propager l'assignation de cette tâche à toutes les sous-tâches."
#. module: bemade_fsm
#: model:project.task.type,legend_done:bemade_fsm.planning_project_stage_exception
#: model:project.task.type,legend_done:bemade_fsm.planning_project_stage_waiting_parts
@ -717,12 +800,17 @@ msgstr "Section de commande de vente"
#: model:ir.model,name:bemade_fsm.model_sale_order
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__sale_order_id
msgid "Sales Order"
msgstr "Commande de ventes"
msgstr "Bon de commande"
#. module: bemade_fsm
#: model:ir.model,name:bemade_fsm.model_sale_order_line
msgid "Sales Order Line"
msgstr "Ligne de commandes de vente"
msgstr "Ligne de bons de commande"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__section_line_ids
msgid "Section Line"
msgstr "Section"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_template__sequence
@ -730,7 +818,9 @@ msgid "Sequence"
msgstr "Séquence"
#. module: bemade_fsm
#: code:addons/bemade_fsm/models/sale_order.py:0
#: model:ir.model.fields,field_description:bemade_fsm.field_bemade_fsm_visit__task_id
#, python-format
msgid "Service Visit"
msgstr "Visite de service"
@ -759,13 +849,14 @@ msgstr "Contacts sur site :"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_res_partner__equipment_ids
#: model:ir.model.fields,field_description:bemade_fsm.field_res_users__equipment_ids
msgid "Site Equipment"
msgstr ""
msgstr "Équipement sur site"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__planned_date_begin
msgid "Start Date"
msgstr ""
msgstr "Date de début"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_tasks_table
@ -816,6 +907,7 @@ msgstr "Tâche"
#: model:ir.actions.act_window,name:bemade_fsm.task_template_act_window
#: model:ir.model.fields,field_description:bemade_fsm.field_product_product__task_template_id
#: model:ir.model.fields,field_description:bemade_fsm.field_product_template__task_template_id
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task_from_template_wizard__task_template_id
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_form_view
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_search_view
#: model_terms:ir.ui.view,arch_db:bemade_fsm.task_template_tree_view
@ -835,14 +927,10 @@ msgstr "Nom de tâche"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_tasks_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_timesheet_entries
msgid "Technician"
msgstr "Technicien·ne"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid "Technicians:"
msgstr "Technicien·ne·s:"
#. module: bemade_fsm
#: model:ir.model,name:bemade_fsm.model_project_task_template
msgid "Template for new project tasks"
@ -855,6 +943,11 @@ msgstr ""
"L'équipement à désservir par défaut pour les nouveaux articles de commande "
"de ventes."
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_project_task_from_template_wizard__project_id
msgid "The project the new task should be created in."
msgstr ""
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_bemade_fsm_visit__so_section_id
msgid ""
@ -864,6 +957,20 @@ msgstr ""
"La section sur la commande de vente représentant la main d'oeuvre et les "
"pièces pour cette visite"
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_project_task_from_template_wizard__task_template_id
msgid "The template to use when creating the new task."
msgstr "Le gabarit à utiliser en créant la nouvelle tâche."
#. module: bemade_fsm
#: model:ir.model.fields,help:bemade_fsm.field_project_task_from_template_wizard__new_task_title
msgid ""
"The title (name) for the newly created task. If left blank, the name of the "
"template will be used."
msgstr ""
"Le titre (nom) de la nouvelle tâche. Si laissé vide, le nom du gabarit sera "
"utilisé."
#. module: bemade_fsm
#: model_terms:ir.actions.act_window,help:bemade_fsm.task_template_act_window
msgid "There are no task templates, click above to create one."
@ -871,7 +978,22 @@ msgstr ""
"Il n'existe aucun gabarit de tâche, cliquez ci-dessus pour en créer une."
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: code:addons/bemade_fsm/models/sale_order_line.py:0
#, python-format
msgid ""
"This task has been created from: <a href=# data-oe-model=sale.order data-oe-"
"id=%d>%s</a> (%s)"
msgstr ""
"Cette tâche a été créée à partir de : <a href=# data-oe-model=sale.order data-oe-"
"id=%d>%s</a> (%s)"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_timesheet_entries
msgid "Time"
msgstr "Temps"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "Time &amp; Material"
msgstr "Temps &amp; matériaux"
@ -886,7 +1008,7 @@ msgid "Type of the exception activity on record."
msgstr "Type d'activité d'exception sur l'enregistrement."
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour
msgid "Unit Price"
msgstr "Prix unitaire"
@ -906,13 +1028,19 @@ msgid "View Task"
msgstr "Voir tâche"
#. module: bemade_fsm
#: code:addons/bemade_fsm/models/sale_order_line.py:0
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__visit_id
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order__visit_ids
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__visit_id
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__visit_ids
#, python-format
msgid "Visit"
msgstr "Visite"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_sale_order_line__visit_ids
msgid "Visits"
msgstr "Visites"
#. module: bemade_fsm
#: model:project.task.type,name:bemade_fsm.planning_project_stage_waiting_parts
msgid "Waiting on Parts"
@ -928,24 +1056,23 @@ msgstr "Messages du site web"
msgid "Website communication history"
msgstr "Historique de communication du site web"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_timesheet_entries
msgid "Work Completed"
msgstr "Travaux complétés"
#. module: bemade_fsm
#: model:project.task.type,name:bemade_fsm.planning_project_stage_work_completed
msgid "Work Executed"
msgstr "Travaux exécutés"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid "Work Order"
msgstr "Bon de travail"
#. module: bemade_fsm
#: model_terms:ir.ui.view,arch_db:bemade_fsm.workorder_page_info_block
msgid ""
"Work Order\n"
" Contacts:"
msgstr ""
"Bon de travail\n"
" Contacts :"
"Contacts recevant le bon de travail :"
#. module: bemade_fsm
#: model:ir.model.fields,field_description:bemade_fsm.field_project_task__work_order_contacts

View file

@ -72,4 +72,5 @@ class Equipment(models.Model):
def _compute_complete_name(self):
for rec in self:
tag_part = "[%s] " % rec.pid_tag if rec.pid_tag else ""
rec.complete_name = tag_part + rec.name
name = rec.name or ""
rec.complete_name = tag_part + name

View file

@ -94,7 +94,7 @@ class Task(models.Model):
pattern = re.compile(r"\d+$")
matches = map(lambda n: pattern.search(n), prev_seqs)
seq += max(map(lambda n: int(n.group(1)) if n else 0), matches)
rec.work_order_number = rec.sale_order_id.name.replace('SO', 'WO', 1) \
rec.work_order_number = rec.sale_order_id.name.replace('SO', 'SVR', 1) \
+ f"-{seq}"
return res
@ -102,6 +102,9 @@ class Task(models.Model):
super().write(vals)
if not self: # End recursion on empty RecordSet
return
if 'propagate_assignment' in vals:
# When a user sets propagate assignment, it should propagate that setting all the way down the chain
self.child_ids.write({'propagate_assignment': vals['propagate_assignment']})
if 'user_ids' in vals:
to_propagate = self.filtered(lambda task: task.propagate_assignment)
# Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment

View file

@ -1,7 +1,61 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<template id="workorder_page_sale_order_table">
<template id="workorder_page_materials_table">
<t t-set="order_lines"
t-value="doc.relevant_order_lines.filtered(lambda l:
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/>
<t t-if="order_lines">
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
<t t-set="section_lines"
t-value="order_lines.filtered(lambda l: l.display_type == 'line_section')"/>
</t>
<div t-if="order_lines.filtered(lambda l: not l.display_type)" style="page-break-inside: avoid;">
<h2 t-if="order_lines">Materials</h2>
<div t-if="order_lines" class="table-responsive-sm">
<table class="table table-sm o_main_table">
<thead>
<tr>
<th class="text-left">Description</th>
<th class="text-right">Ordered</th>
<th class="text-right">Delivered</th>
</tr>
</thead>
<tbody class="sale_tbody">
<t t-foreach="order_lines" t-as="line">
<t t-set="is_task" t-value="line == doc.sale_line_id"/>
<tr t-att-class="'bg-200 font-weight-bold o_line_section' if line.display_type == 'line_section' else 'font-italic o_line_note' if line.display_type == 'line_note' else ''">
<t t-if="not line.display_type">
<td><span t-field="line.name"/></td>
<td class="text-right">
<span t-field="line.product_uom_qty"/>
<span t-field="line.product_uom"
groups="uom.group_uom"/>
</td>
<td class="text-right">
<span t-field="line.qty_delivered"/>
<span t-field="line.product_uom"
groups="uom.group_uom"/>
</td>
</t>
<t t-if="line.display_type == 'line_section'">
<td colspan="99">
<span t-field="line.name"/>
</td>
</t>
<t t-if="line.display_type == 'line_note'">
<td colspan="99">
<span t-field="line.name"/>
</td>
</t>
</tr>
</t>
</tbody>
</table>
</div>
</div>
</template>
<template id="workorder_page_sale_order_table_with_pricing_and_labour">
<t t-set="order_lines" t-value="doc.relevant_order_lines"/>
<t t-if="order_lines">
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
@ -164,7 +218,6 @@
</template>
<template id="workorder_page_tasks_table">
<t t-set="interventions" t-value="doc.child_ids"/>
<t t-set="timesheets" t-value="doc.timesheet_ids"/>
<t t-foreach="interventions" t-as="intervention">
<div style="page-break-inside: avoid;">
<h2 t-out="str(intervention_index + 1) + '. ' + intervention.name"/>
@ -182,9 +235,8 @@
<thead>
<tr>
<th width="7%">Status</th>
<th width="30%">Task</th>
<th width="18%">Technician</th>
<th width="45%">Comments</th>
<th width="39%">Task</th>
<th width="54%">Comments</th>
</tr>
</thead>
<tbody>
@ -194,11 +246,6 @@
<td>
<span t-out="task.name"/>
</td>
<td>
<t t-foreach="task.user_ids" t-as="user">
<span t-out="user.name + (', ' if not user_last else '')"/>
</t>
</td>
<td t-out="task.description or ''"/>
</tr>
<tr class="mt-0 pt-0">
@ -207,7 +254,6 @@
<t t-call="bemade_fsm.subtask_list"/>
</td>
<td/>
<td/>
</tr>
</t>
</tbody>
@ -218,20 +264,16 @@
</template>
<template id="workorder_page_info_block">
<h1>Work Order <span t-out="doc.work_order_number"></span></h1>
<div class="row">
<div t-attf-class="{{'col-6' if report_type == 'pdf' else 'col-md-6 col-12'}}">
<div t-if="doc.user_ids"><h6>Technicians: </h6></div>
<t t-foreach="doc.user_ids" t-as="user">
<div class="mb-3">
<div t-esc="user" t-options='{
"widget": "contact",
"fields": ["name", "email"]
}'/>
</div>
</t>
</div>
<h1>
<span name="work_order_number" t-out="doc.name"/>
</h1>
<h4 t-if="doc.sale_order_id and doc.sale_order_id.client_order_ref">
Purchase Order: <span
name="po_number"
t-out="doc.sale_order_id.client_order_ref"/>
</h4>
<hr/>
<div class="row" name="address_and_time">
<div t-attf-class="{{('col-6' if report_type == 'pdf' else 'col-md-6 col-12') + ' mb-3'}}">
<t t-if="doc.partner_id">
<div><h6>Customer: </h6></div>
@ -241,8 +283,19 @@
}'/>
</t>
</div>
<div t-attf-class="{{('col-6' if report_type == 'pdf' else 'col-md-6 col-12') + ' mb-3'}}"
t-if="doc.planned_date_begin or doc.planned_date_end">
<div t-if="doc.planned_date_begin"><h6>Planned start: </h6></div>
<div class="mb-3">
<div t-out="doc.planned_date_begin.strftime('%Y-%m-%d %H:%M')"/>
</div>
<div t-if="doc.planned_date_end"><h6>Planned end: </h6></div>
<div class="mb-3">
<div t-out="doc.planned_date_end.strftime('%Y-%m-%d %H:%M')"/>
</div>
</div>
</div>
<div class="row">
<div class="row" name="site_and_work_order_contacts">
<div t-attf-class="{{'col-6' if report_type == 'pdf' else 'col-md-6 col-12'}}">
<div t-if="doc.site_contacts"><h6>Site Contacts: </h6></div>
<t t-foreach="doc.site_contacts" t-as="contact">
@ -267,13 +320,20 @@
</t>
</div>
</div>
<hr/>
<div t-if="not is_html_empty(doc.description)" class="row" name="visit_description">
<div class="col-12">
<span t-out="doc.description" />
</div>
</div>
<hr t-if="not is_html_empty(doc.description)"/>
</template>
<template id="workorder_equipment_summary">
<div t-if="doc.equipment_ids and len(doc.child_ids) > 1"
style="page-break-inside: avoid;">
<h3>Equipment Serviced</h3>
<div class="table-responsive-sm">
<table class="table table-sm o_main_table">
<table class="table table-sm o_main_table overflow-hidden">
<thead>
<tr>
<th>Tag</th>
@ -305,6 +365,47 @@
</div>
</div>
</template>
<template id="workorder_page_timesheet_entries">
<t t-set="timesheets" t-value="doc.timesheet_ids"/>
<div t-if="timesheets" style="page-break-inside: avoid;">
<h1>Time</h1>
<div class="table-responsive-sm">
<table class="table table-sm o_main_table">
<thead>
<tr>
<th width="14%">Date</th>
<th width="30%">Technician</th>
<th width="49%">Work Completed</th>
<th width="7%" class="text-right">Time</th>
</tr>
</thead>
<tbody>
<tr t-foreach="timesheets" t-as="timesheet" class="p-1 m-1">
<td>
<span t-out="timesheet.date.strftime('%Y-%m-%d')"/>
</td>
<td>
<span t-field="timesheet.employee_id.name"/>
</td>
<td>
<span t-field="timesheet.name"/>
</td>
<td>
<span t-esc="timesheet.unit_amount"
t-options="{
'widget': 'duration',
'digital': True,
'unit': 'hour',
'round': 'minute'
}"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<template id="workorder_page_signature_block">
<div t-if="doc.worksheet_signature">
<div t-if="report_type == html" class="ribbon" style="
@ -342,7 +443,8 @@
<template id="work_order_page">
<div class="page">
<t t-call="bemade_fsm.workorder_page_info_block"/>
<t t-call="bemade_fsm.workorder_page_sale_order_table"/>
<t t-call="bemade_fsm.workorder_page_timesheet_entries"/>
<t t-call="bemade_fsm.workorder_page_materials_table"/>
<t t-call="bemade_fsm.workorder_equipment_summary"/>
<t t-call="bemade_fsm.workorder_page_signature_block"/>
<t t-call="bemade_fsm.workorder_page_tasks_table"/>
@ -359,13 +461,14 @@
</t>
</t>
</template>
<!-- MD Commented out since this no longer matches the structure of industry_fsm at all -->
<!-- Make the front-end "Sign" page show our new template -->
<template id="portal_my_worksheet"
inherit_id="industry_fsm_report.portal_my_worksheet">
<xpath expr="//div[@t-call='industry_fsm_report.worksheet_custom_page']"
position="replace">
<div t-call="bemade_fsm.work_order_page"/>
</xpath>
</template>
<!-- <template id="portal_my_worksheet" -->
<!-- inherit_id="industry_fsm.portal_my_task"> -->
<!-- <xpath expr="//div[@t-call='industry_fsm.worksheet_custom_page']" -->
<!-- position="replace"> -->
<!-- <div t-call="bemade_fsm.work_order_page"/> -->
<!-- </xpath> -->
<!-- </template> -->
</data>
</odoo>
</odoo>

View file

@ -1,13 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="industry_fsm_report.task_custom_report" model="ir.actions.report">
<record id="industry_fsm.task_custom_report" model="ir.actions.report">
<field name="name">Work Order Report (PDF)</field>
<field name="report_name">bemade_fsm.work_order</field>
<field name="report_file">bemade_fsm.work_order</field>
<field name="print_report_name">'%s Work Order %s' % (
time.strftime('%Y-%m-%d'), object.work_order_number)</field>
<field name="print_report_name">'%s %s' % (
object.planned_date_begin.strftime('%Y-%m-%d') if object.planned_date_begin else time.strftime('%Y-%m-%d'),
object.name
)
</field>
<field name="report_type">qweb-pdf</field>
</record>
</data>
</odoo>
</odoo>

View file

@ -1,146 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="default_worksheet_template" model="ir.actions.report">
<field name="name">Complete Worksheet Report (PDF)</field>
<field name="model">project.task</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">bemade_fsm.worksheet_complete</field>
<field name="print_report_name">
'%s Worksheet %s' % (time.strftime('%Y-%m-%d'), object.name)
</field>
<field name="binding_model_id" ref="model_project_task"/>
<field name="binding_type">report</field>
</record>
<template id="worksheet_complete">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="bemade_fsm.worksheet_complete_page"
t-lang="doc.partner_id.lang"/>
</t>
</t>
</template>
<template id="worksheet_complete_page">
<t t-call="web.external_layout">
<t t-set="address" t-if="doc.sale_order_id">
<strong class="big-red">Customer:</strong>
<div t-field="doc.sale_order_id.partner_id"
t-options='{"widget": "contact",
"fields": ["address", "name"], "no_marker": True}'/>
</t>
<t t-set="information_block">
<strong>Service Address:</strong>
<div t-field="doc.partner_id"
t-options='{"widget": "contact",
"fields": ["address", "name", "phone"],
"no_marker": True, "phone_icons": True}'/>
</t>
<div class="page">
<div class="oe_structure"/>
<h2 class="mt-3">
<span>Work Order </span>
<span t-if="doc.sale_order_id" t-field="doc.sale_order_id.name"/>
<span t-else="" t-field="doc.name"/>
</h2>
<div class="container">
<div class="row mb-3 border-dark">
<div t-if="doc.sale_order_id.client_order_ref" class="col-4">
<span class="font-weight-bolder">Your Reference:</span>
<span t-field="doc.sale_order_id.client_order_ref"/>
</div>
<div t-if="doc.site_contacts" class="col-4">
<span class="font-weight-bolder">Site Contacts:</span>
<t t-foreach="doc.site_contacts" t-as="contact">
<span t-field="contact.name"/>
<span t-out="contact"
t-options='{"widget": "contact",
"fields": ["phone", "mobile", "email"],
"no_marker": True, "phone_icons": True}'/>
</t>
</div>
<div t-if="doc.work_order_contacts" class="col-4">
<span class="font-weight-bolder">Work order attn:</span>
<t t-foreach="doc.work_order_contacts" t-as="contact">
<span t-field="contact.name"/>
<span t-out="contact"
t-options='{"widget": "contact",
"fields": ["phone", "mobile", "email"],
"no_marker": True, "phone_icons": True}'/>
</t>
</div>
</div>
<div class="row mb-3 border-dark">
<div class="col-4" t-if="doc.user_ids">
<span class="font-weight-bolder">Technician(s)</span>
<t t-foreach="doc.user_ids" t-as="technician">
<p class="m-0" t-field="technician.name"/>
</t>
</div>
<div class="col-4" t-if="doc.planned_date_begin">
<span class="font-weight-bolder">Planned Start</span>
<p t-field="doc.planned_date_begin"
t-options="{'widget': 'datetime'}"/>
</div>
<div class="col-4" t-if="doc.planned_date_end">
<span class="font-weight-bolder">Planned End</span>
<p t-field="doc.planned_date_end"
t-options="{'widget': 'datetime'}"/>
</div>
</div>
</div>
<div class="container">
<h3 class="mb32"><strong>Interventions</strong></h3>
<t t-foreach="doc.child_ids" t-as="intervention">
<div class="row">
<div class="col-6">
<h4><span t-field="intervention.name"/></h4>
<span t-if="intervention.description"
t-field="intervention.description"/>
</div>
<div t-if="intervention.equipment_ids"
class="col-6">
<h4>Equipment: </h4>
<t t-foreach="intervention.equipment_ids"
t-as="equipment">
<span t-field="equipment.complete_name"/>
<span t-if="equipment_index &lt; equipment_size -1"
t-out="', '"/>
</t>
</div>
</div>
<div class="row">
<table class="table table-borderless col-12">
<thead>
<th>Done</th>
<th>Task to do</th>
</thead>
<tbody>
<t t-foreach="intervention.child_ids"
t-as="task">
<tr class="wo-task">
<td style="width: 5%">
<input type="checkbox"
t-att-checked="task.is_complete"/>
</td>
<td style="width: 95%">
<p t-field="task.name"/>
<p t-field="task.description"/>
</td>
<!-- TODO: Figure out what to do with the old
concept of task comments -->
</tr>
</t>
</tbody>
</table>
</div>
</t>
</div>
</div>
</t>
</template>
</data>
</odoo>

View file

@ -19,3 +19,7 @@ class TestEquipment(BemadeFSMBaseTest):
partner_company.write({'equipment_ids': [Command.set([])]})
with self.assertRaises(MissingError):
equipment.name
def test_compute_complete_name_when_name_blank(self):
equipment = self._generate_equipment(name=False)
complete_name = equipment.complete_name

View file

@ -6,37 +6,51 @@ from odoo import Command
@tagged('post_install', '-at_install')
class TaskTest(BemadeFSMBaseTest):
@classmethod
def setUpClass(cls):
# Chose to set up all tests the same way since this code was becoming very redundant
super().setUpClass()
cls.so = cls._generate_sale_order()
cls.template = cls._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1])
cls.product = cls._generate_product(task_template=cls.template)
cls.sol = cls._generate_sale_order_line(sale_order=cls.so, product=cls.product)
cls.user = cls._generate_project_manager_user('Bob', 'Bob')
cls.so.action_confirm()
cls.task = cls.sol.task_id
def test_reassigning_assignment_propagating_task_changes_subtasks(self):
# This creation step is a bit lazy - we use defaults to make tasks with a hierachy and settings we want
so = self._generate_sale_order()
template = self._generate_task_template(names=['Parent', 'Child'], structure=[2])
product = self._generate_product(task_template=template)
sol = self._generate_sale_order_line(sale_order=so, product=product)
user = self._generate_project_manager_user('Bob', 'Bob')
so.action_confirm()
task = sol.task_id
task = self.task
task.propagate_assignment = True
task.write({
'user_ids': [Command.set([user.id])],
'user_ids': [Command.set([self.user.id])],
'propagate_assignment': True,
})
self.assertTrue(all([t.user_ids == user for t in task | task._get_all_subtasks()]))
def test_reassigning_assignment_non_propagating_task_doesnt_change_subtasks(self):
so = self._generate_sale_order()
template = self._generate_task_template(names=['Parent', 'Child', 'Grandchild'], structure=[2, 1])
product = self._generate_product(task_template=template)
sol = self._generate_sale_order_line(sale_order=so, product=product)
user = self._generate_project_manager_user('Bob', 'Bob')
so.action_confirm()
task = sol.task_id
task.child_ids.write({'propagate_assignment': False}) # Stop propagation after the first level
self.assertTrue(all([t.user_ids == self.user for t in task | task._get_all_subtasks()]))
def test_reassigning_task_doesnt_propagate_by_default(self):
task = self.task
task.write({
'user_ids': [Command.set([user.id])],
'user_ids': [Command.set([self.user.id])],
'propagate_assignment': True,
})
self.assertTrue(all([t.user_ids == user for t in task | task.child_ids]))
self.assertFalse(any([t.user_ids for t in task.child_ids.child_ids]))
def test_unset_propagate_assignment_unsets_for_all_children(self):
task = self.task
# First, set propagation and assign
task.propagate_assignment = True
task.write({
'user_ids': [Command.set([self.user.id])]
})
# Then, unset propagation for the children and re-set assignment
task.child_ids.write({'propagate_assignment': False})
self.assertFalse(any([t.propagate_assignment for t in task._get_all_subtasks()]))
# Then, test that assigning the parent only assigns its children, not its grandchildren
task.write({
'user_ids': [Command.set([])]
})
self.assertTrue(all([not t.user_ids for t in task | task.child_ids]))
self.assertTrue(all([t.user_ids == self.user for t in task.child_ids.child_ids]))

View file

@ -8,24 +8,26 @@
<field name="inherit_id" ref="sale_project.product_template_form_view_invoice_policy_inherit_sale_project"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='project_id']" position="after">
<field name="task_template_id"
attrs="{'invisible': [('service_tracking', 'not in', ('task_global_project', 'task_in_project'))]}"
domain="[('parent', '=', False)]"/>
<field name="task_template_id"
invisible="service_tracking not in ('task_global_project', 'task_in_project')"
domain="[('parent', '=', False)]"/>
</xpath>
<xpath expr="//field[@name='planning_enabled']" position="after">
<field name="is_field_service"/>
</xpath>
</field>
</record>
<record id="product_search_form_view_inherit_bemade_fsm" model="ir.ui.view">
<field name="name">bemade_fsm.product_search_form_view_inherit_bemade_fsm</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="industry_fsm_sale.product_search_form_view_inherit_fsm_sale"/>
<field name="arch" type="xml">
<xpath expr="//searchpanel//field[@name='categ_id']" position="attributes">
<attribute name="limit">0</attribute>
</xpath>
</field>
</record>
<!-- BV: Did comment that one cause can not match inherit_id and don't understand it use -->
<!-- <record id="product_search_form_view_inherit_bemade_fsm" model="ir.ui.view">-->
<!-- <field name="name">bemade_fsm.product_search_form_view_inherit_bemade_fsm</field>-->
<!-- <field name="model">product.product</field>-->
<!-- <field name="inherit_id" ref="industry_fsm_sale.product_search_form_view_inherit_fsm_sale"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="//searchpanel//field[@name='categ_id']" position="attributes">-->
<!-- <attribute name="limit">0</attribute>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
</data>
</odoo>

View file

@ -17,21 +17,22 @@
<page name="field_service" string="Field Service">
<field name="owned_equipment_ids" invisible="True"/>
<group>
<field name="site_contacts"
attrs="{'invisible': [('company_type','=','person')]}"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
<field name="work_order_contacts"
attrs="{'invisible': [('company_type','=','person')]}"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
<field name="equipment_ids" attrs="{'invisible': [('company_type','=','person')]}"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}"
readonly="False"/>
<field attrs="{'invisible': ['|', ('company_type','=','person'), ('owned_equipment_ids','=',False)]}"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}"
name="owned_equipment_ids"
readonly="True"/>
<field name="site_ids"
attrs="{'invisible': [('company_type','=','company')]}">
<field name="site_contacts"
invisible="company_type == 'person'"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
<field name="work_order_contacts"
invisible="company_type == 'person'"
context="{'tree_view_ref': 'bemade_fsm.fsm_contacts_view_tree'}"/>
<field name="equipment_ids"
invisible="company_type == 'person'"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}"
readonly="False"/>
<field name="owned_equipment_ids"
invisible="company_type == 'person' or owned_equipment_ids == False"
context="{'tree_view_ref': 'bemade_fsm.fsm_equipment_view_tree'}"
readonly="True"/>
<field name="site_ids"
invisible="company_type == 'company'">
<tree editable="bottom">
<field name="name" widget="res_partner_many2one"/>
</tree>

View file

@ -9,8 +9,7 @@
<field name="arch" type="xml">
<xpath expr="//field[@name='name']/.." position="before">
<h1 class="d-flex flex-row justify-content-between">
<field name="work_order_number"
attrs="{'invisible': [('work_order_number', '=', False)]}"/>
<field name="work_order_number" invisible="work_order_number == False"/>
</h1>
</xpath>
<xpath expr="//page[@name='extra_info']" position="after">

View file

@ -18,7 +18,7 @@
#
{
'name': 'Full Form from Dialog',
'version': '16.0.1.0.0',
'version': '17.0.1.0.0',
'summary': 'Allows opening the full form view from the dialog (modal) view.',
'description': 'Adds a button to open the full form view when viewing the form view for a record in a dialog.',
'category': 'Technical',
@ -29,7 +29,7 @@
'data': [],
'assets': {
'web.assets_backend': [
'bemade_full_formview_from_modal/static/src/**/*',
# 'bemade_full_formview_from_modal/static/src/**/*',
],
},
'installable': True,

View file

@ -4,7 +4,7 @@
<record id="helpdesk_stage_spam" model="helpdesk.stage">
<field name="name">Spam</field>
<field name="sequence">50</field>
<field name="is_close">True</field>
<field name="fold">True</field>
</record>
</data>
</odoo>

View file

@ -6,7 +6,8 @@
<field name="inherit_id" ref="helpdesk.helpdesk_ticket_view_form"/>
<field name="arch" type="xml">
<header position="inside">
<button name="action_add_blacklist" type="object" string="Add to Blacklist" class="oe_highlight" attrs="{'invisible': [('partner_email', '=', False)]}"/>
<field name="partner_email" invisble="1"/>
<button name="action_add_blacklist" type="object" string="Add to Blacklist" class="oe_highlight" invisible="partner_email == False"/>
</header>
</field>
</record>

View file

@ -18,7 +18,7 @@
#
{
'name': 'Helpdesk One Ticket Per Email',
'version': '15.0.1.0.0',
'version': '17.0.1.0.0',
'summary': 'Restrict ticket creation to a single ticket per email received.',
'category': 'Helpdesk',
'author': 'Bemade Inc.',

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Hide Decimal on unit",
"version": "15.0.0.1.1",
"version": "17.0.0.1.1",
"category": "Extra Tools",
'summary': 'Hide decimal on Qty when there is no decimal',
"description": """
@ -14,9 +14,8 @@
'purchase'
],
"data": [
# BV : FOR MIGRATION
# 'views/sale.xml',
# 'views/purchase.xml'
'views/sale.xml',
'views/purchase.xml'
],
"auto_install": False,
"installable": True,

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_purchasequotation_document_strip_decimal" inherit_id="purchase.report_purchasequotation_document">
<xpath expr="//tbody//td[hasclass('text-right')]" position="replace">
<td name="td_quantity" class="text-right">
<xpath expr="//tbody//td[hasclass('text-end')]" position="replace">
<td name="td_quantity" class="text-end">
<t t-if="int(order_line.product_uom_qty) == order_line.product_uom_qty">
<span t-esc="'%.0f' % order_line.product_uom_qty"/>
</t>
@ -15,8 +15,8 @@
</template>
<template id="report_purchaseorder_document_strip_decimal" inherit_id="purchase.report_purchaseorder_document">
<xpath expr="//tbody//td[hasclass('text-right')][1]" position="replace">
<td name="td_quantity" class="text-right">
<xpath expr="//tbody//td[hasclass('text-end')][1]" position="replace">
<td name="td_quantity" class="text-end">
<t t-if="int(line.product_uom_qty) == line.product_uom_qty">
<span t-esc="'%.0f' % line.product_uom_qty"/>
</t>

View file

@ -0,0 +1 @@
from . import models

View file

@ -0,0 +1,37 @@
#
# Bemade Inc.
#
# Copyright (C) 2023-June Bemade Inc. (<https://www.bemade.org>).
# Author: Marc Durepos (Contact : marc@bemade.org)
#
# This program is under the terms of the Odoo Proprietary License v1.0 (OPL-1)
# It is forbidden to publish, distribute, sublicense, or sell copies of the Software
# or modified copies of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
#
{
'name': 'Quebec Payroll',
'version': '17.0.1.0.0',
'summary': 'Computations for Quebec Payslips',
'category': 'Human Resources/Payroll',
'author': 'Bemade Inc.',
'website': 'http://www.bemade.org',
'license': 'OPL-1',
'depends': [
'hr_payroll',
'l10n_ca',
],
'data': [
'data/hr_salary_rule_data.xml',
],
'assets': {},
'installable': True,
'auto_install': False
}

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="l10n_ca_input_bonus" model="hr.payslip.input.type">
<field name="name">Bonus or other non-period payment</field>
<field name="code">BONUS</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_f1" model="hr.payslip.input.type">
<field name="name">Employee-requested deductions (Federal)</field>
<field name="code">FED_F1</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_f2" model="hr.payslip.input.type">
<field name="name">Court-ordered deductions (Federal)</field>
<field name="code">FED_F2</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_U1" model="hr.payslip.input.type">
<field name="name">Union or association dues for the period (Federal)</field>
<field name="code">FED_U1</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_HD" model="hr.payslip.input.type">
<field name="name">Allowance for residents of specified regions (Federal)</field>
<field name="code">FED_HD</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_F" model="hr.payslip.input.type">
<field name="name">Deduction for retirement plan contributions (Federal)</field>
<field name="code">FED_F</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
<record id="l10n_ca_input_fed_TC" model="hr.payslip.input.type">
<field name="name">Total Requested Amount on Federal form TD1</field>
<field name="code">FED_TC</field>
<field name="country_id" ref="base.ca"/>
<field name="struct_ids" eval="Command.link(ref('hr_payroll.default_structure'))"/>
</record>
</data>
</odoo>

View file

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="l10n_ca_parameter_ARK" model="hr.rule.parameter">
<field name="name">A, R, K constants for Federak Tax Calculation</field>
<field name="code">ARK</field>
</record>
<record id="l10n_ca_parameter_value_ARK_2024" model="hr.rule.parameter.value">
<field name="date_from">2024-01-01</field>
<field name="rule_parameter_id" ref="bemade_l10n_ca_payroll.l10n_ca_parameter_ARK"/>
<field name="parameter_value">
[
(0, 0.15, 0),
(55867, 0.2050, 3073),
(111733, 0.26, 9218),
(173205, 0.29, 14414),
(246752, 0.33, 24284),
]
</field>
</record>
<record id="l10n_ca_parameter_TC" model="hr.rule.parameter">
<field name="name">Base amount for federal form TD1</field>
<field name="code">FED_TC</field>
</record>
<record id="l10n_ca_parameter_value_TC_2024" model="hr.rule.parameter.value">
<field name="date_from">2024-01-01</field>
<field name="rule_parameter_id" ref="bemade_l10n_ca_payroll.l10n_ca_parameter_TC"/>
<field name="parameter_value">15704</field>
</record>
<record id="l10n_ca_fed_tax_on_payslip" model="hr.salary.rule">
<field name="name">Income Tax - Federal</field>
<field name="category_id" ref="hr_payroll.DED"/>
<field name="sequence" eval="205"/>
<field name="struct_id" ref="hr_payroll.default_structure"/>
<field name="appears_on_payslip" eval="True"/>
<field name="appears_on_employee_cost_dashboard" eval="False"/>
<field name="appears_on_payroll_report" eval="True"/>
<field name="condition_select">none</field>
<field name="amount_select">code</field>
<field name="amount_python_compute">
"""<![CDATA[
Source: https://www.canada.ca/fr/agence-revenu/services/formulaires-publications/retenues-paie/t4127-formules-calcul-retenues-paie/t4127-jan/t4127-jan-formules-calcul-informatise-retenues-paie.html#toc31
Pour le Québec, calcul en 4 étapes (étapes 1, 2, 3, et 6 du guide ci-dessus)
Étape 1: Calculer "A" - le revenu imposable
Étape 2: Calculer "T3" - l'impôt fédéral de base
Étape 3: Calculer "T1" - l'impôt fédéral annuel à payer
Étape 4: Calculer "T" - L'impôt à payer pour cette paye
Étape 1
A = P x (I - F - F2 - F5A -U1) - HD - F1
P = Nombre de périodes de paie dans l'année
I = Rémunération brute pour la période de paie, excluant les primes, augmentations salariales rétroactives ou autres paiements non-périodiques
F = Retenues pour la période pour un REER, RPA, RPAC ou CR.
F2 = saisie ordonnée par la cour (pension alimentaire, etc.)
F5A = Déductions des cotisations supplémentaires au RRQ pour la période de paie
U1 = Cotisations à un syndicat ou assoc. de fonctionnaires, pour la période de paie
HD = Retenue annuelle accordée aux résidents d'une région visée par le règlement selon formulaire TD1
F1 = Retenues annuelles (frais de garde d'enfants, pensions alimentaires, demandées par l'employé et autorisés par bureau svcs. fiscaux)
PI = gains ouvrant droit à une pension pour la période de paie. Nous assumons ici que c'est égal à la rémunération brute.
B = Prime brute, augmentation de salaire rétroactive, ou autres montants non périodiques
"""
pay_periods_map = {
'annually': 1,
'semi-annually': 2,
'quarterly': 4,
'bi-monthly': 6,
'monthly': 12,
'semi-monthly': 24,
'bi-weekly': 26,
'weekly': 52,
'daily': 365,
}
P = payslip.struct_type_id.default_pay_periods_per_year
I = categories.get("GROSS") - (inputs['BONUS'].amount if 'BONUS' in inputs else 0)
F = inputs['FED_F'].amount if 'FED_F' in inputs else 0
F2 = inputs['FED_F2'].amount if 'FED_F2' in inputs else 0
C = categories.get("COTISATIONS_RRQ", 0)
C2 = categories.get("COTISATIONS_RRQ_2", 0)
F5Q = C * (0.01/0.0640) + C2
PI = categories.get("GROSS")
B = inputs['BONUS'].amount if 'BONUS' in inputs else 0
F5A = F5Q * ((PI - B)/PI)
U1 = inputs['FED_U1'].amount if 'FED_U1' in inputs else 0
HD = inputs['FED_HD'].amount if 'FED_HD' in inputs else 0
F1 = inputs['FED_F1'].amount if 'FED_F1' in inputs else 0
A = P * (I - F - F2 - F5A - U1) - HD - F1
"""
Étape 2 - Calcul de l'impôt fédéral de base (T3)
T3 = (R x A) - K - K1 - K2Q - K3 - K4
R = taux d'imposition fédéral qui s'applique au revenu imposable annuel A
"""
# TODO: Get this into a configuration data structure
ARK = payslip._rule_parameter('ARK')
R, K = payslip._l10n_ca_compute_fed_tax_constants(A, ARK)
TC = inputs['FED_TC'].amount if 'FED_TC' in inputs else payslip._rule_parameter('FED_TC_BASIC')
K1 = 0.15 * TC
PM = payslip._rule_parameter('RRQ_NO_MOIS_TOTAL')
IE = A # Assume that insurables are the gross pay
AE = categories.get("EI_CONTR")
K2Q = ((0.15 * min(P * C * (0.0540/0.0640), 3217.50) * (PM/12)) + (0.15 * min(P * AE, 834.24) + (0.15 * min(P * IE * 0.00494, 464.36))
K3 = inputs['FED_K3'].amount if 'FED_K3' in inputs else 0
CCE = payslip._rule_parameter('FED_CCE') # 1 433 for 2024
K4 = min(0.5 * A, CCE)
T3 = (R * A) - K - K1 - K2Q - K3 - K4
"""
Étape 3 - Formule pour calculer l'impôt fédéral à payer (T1)
T1 = ((T3 - (P x LCF)) - (0.165 * T3)
"""
LCF = min(750, 0.15 * (inputs['DED_CAPITAL_PURCH'] if 'DED_CAPITAL_PURCH' in inputs else 0))
T1 = (T3 - (P * LCF)) - (0.165 * T3)
"""
Étape 6 - Formule pour calculer une estimation des retenus d'impôt fédéral pour la période de paie (T)
T = (T1 / P) / L
L = Retenues d'impôt additionnelles pour la période de paie, demandées par l'employé(e) sur TD1
"""
L = inputs['FED_DEDUCT_REQUEST'].amount if 'FED_DEDUCT_REQUEST' in inputs else 0
T = (T1 / P) / L
result = T
]]>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1 @@
from . import hr_payroll_structure_type

View file

@ -0,0 +1,26 @@
from odoo import models, fields, api
class HrPayrollStructureType(models.Model):
_inherit = 'hr.payroll.structure.type'
default_pay_periods_per_year = fields.Integer(
compute="_compute_pay_periods_per_year",
compute_sudo=True,
)
@api.depends("default_schedule_pay")
def _compute_pay_periods_per_year(self):
pay_periods_map = {
'annually': 1,
'semi-annually': 2,
'quarterly': 4,
'bi-monthly': 6,
'monthly': 12,
'semi-monthly': 24,
'bi-weekly': 26,
'weekly': 52,
'daily': 365,
}
for rec in self:
rec.default_pay_periods_per_year = pay_periods_map.get(rec.default_schedule_pay, False)

View file

@ -0,0 +1,29 @@
from odoo import models, fields, api
class Payslip(models.Model):
_inherit = "hr.payslip"
def _l18n_ca_compute_fed_tax_constants(self, taxable_income: float, coefficients):
"""
Take a table of input coefficients in the form
[
(a, r, k),
...
] where a, r, and k are the threshold, tax rate and federal constants from government tables,
and return the r and k applicable for the given annual taxable income.
:param taxable_income: annual taxable income
:param coefficients: coefficients table to use (get it from rule parameters data, usually)
:return: (r, k) values where r is the tax rate and k is the federal constant to use
"""
R = coefficients[0][1]
K = coefficients[0][2]
# Get the rate and constant by income tier (stop once we reach a tier above the taxable income)
for a, r, k in coefficients:
if taxable_income < a:
return R, K
R = r
K = k
return R, K

View file

@ -34,8 +34,9 @@
],
"assets": {
"web.assets_backend": [
"bemade_mailcow_integration/static/src/js/mailcow.js",
"bemade_mailcow_integration/static/src/xml/mailcow_templates.xml",
# BV: Commented out the following lines to avoid errors when installing the module.
# "bemade_mailcow_integration/static/src/js/mailcow.js",
# "bemade_mailcow_integration/static/src/xml/mailcow_templates.xml",
],
},
'installable': True,

View file

@ -3,9 +3,9 @@
<record id="view_res_config_settings_form_inherit_mailcow" model="ir.ui.view">
<field name="name">res.config.settings.form.inherit.mailcow</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="inherit_id" ref="mail.res_config_settings_view_form"/>
<field name="arch" type="xml">
<div id="emails" position='after'>
<setting id="email_servers_setting" position='after'>
<div id="mailcow">
<h2>Mailcow Settings</h2>
<div class="row mt16 o_settings_container" name="mailcow_setting">
@ -49,7 +49,7 @@
</div>
</div>
</div>
</div>
</setting>
</field>
</record>
</odoo>

View file

@ -6,7 +6,7 @@
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<field name="email" position="before">
<div id="mailcow_mailcow" attrs="{'invisible': [('id', '!=', False)]}">
<div id="mailcow_mailcow" invisible="id != False">
<label for="mailcow_mailbox" string="Create mailbox on Mailcow"/>
<field name="mailcow_mailbox"/>
</div>

View file

@ -18,7 +18,7 @@
#
{
'name': 'Sales Margin on Vendor Price',
'version': '15.0.0.0.1',
'version': '17.0.0.0.1',
'summary': 'Enables calculation of sales margins based on vendor pricelists.',
'description': """Adds a new method for calculating """,
'author': 'Bemade Inc.',

View file

@ -1 +1,2 @@
from . import sale_order
from . import sale_order_line

View file

@ -21,157 +21,3 @@ class SaleOrder(models.Model):
order.margin_percent_actual = order.amount_untaxed and order.margin_actual / order.amount_untaxed
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
purchase_price_vendor = fields.Float(
compute='_compute_purchase_price_vendor',
string="Vendor Price",
groups="base.group_user",
digits='Product Price'
)
margin_percent_vendor = fields.Float(
string='Margin (%) on Vendor Price',
groups='base.group_user',
group_operator='avg',
compute='_compute_margin_vendor'
)
margin_vendor = fields.Float(
string='Margin on Vendor Price',
groups='base.group_user',
digits='Product Price',
compute='_compute_margin_vendor'
)
purchase_price_actual = fields.Float(
compute="_compute_actual_margins",
digits='Product Price',
groups="base.group_user",
string="Purchase Price"
)
margin_actual = fields.Float(
compute="_compute_actual_margins",
digits='Product Price',
groups="base.group_user",
string="Our Margin"
)
margin_percent_actual = fields.Float(
compute="_compute_actual_margins",
groups="base.group_user",
string="Our Margin (%)"
)
@api.depends(
'purchase_price',
'purchase_price_vendor',
'move_ids.product_id',
'move_ids.product_id.qty_available',
'move_ids.state',
'qty_to_deliver'
)
def _compute_actual_margins(self):
""" We want to use the margin based on average inventory valuation when the
sale order line will be completely fulfilled (or has been fulfilled) from stock.
For product not yet in stock we want to use the vendor price. We can also have
blended calculations (partly on vendor price, partly on stock valuation). This
occurs when an order has been or would be partially fulfilled from available
stock.
:return:
"""
non_product_lines = self.filtered(lambda r: not r.product_id)
non_product_lines.purchase_price_actual = 0.0
non_product_lines.margin_actual = 0.0
non_product_lines.margin_percent_actual = 0.0
for line in self - non_product_lines:
stock_missing = line._determine_missing_stock()
if float_is_zero(stock_missing, precision_rounding=line.product_uom.rounding):
# everything is coming from stock, use inventory valuation
line.purchase_price_actual = line.purchase_price
elif float_compare(line.product_uom_qty, stock_missing,
precision_rounding=line.product_uom.rounding) == 0:
# everything is coming from the vendor, use vendor pricing
line.purchase_price_actual = line.purchase_price_vendor
else:
# we have a mix, use blended pricing
qty_from_stock = line.product_uom_qty - stock_missing
line.purchase_price_actual = \
(stock_missing * line.purchase_price_vendor
+ qty_from_stock * line.purchase_price) \
/ line.product_uom_qty
line.margin_actual = line.price_subtotal - (
line.purchase_price_actual * line.product_uom_qty)
line.margin_percent_actual = line.price_subtotal and line.margin_actual / line.price_subtotal
def _determine_missing_stock(self) -> float:
""" Compute how much stock is missing to meet an order line's demand. In the
case of a quotation line, available stock is checked as if the order were to be
placed immediately.
:return: The quantity missing from available stock to fulfill the line, in
the unit of measure matching self.product_uom.
"""
self.ensure_one()
is_order = self.order_id.state in ('sale', 'done')
if is_order and self.qty_to_deliver == 0:
return 0
elif is_order and self.qty_to_deliver > 0:
reserved = sum([m.reserved_availability for m in self.move_ids])
missing = self.qty_to_deliver - reserved
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Not enough reserved, check stock
missing = missing - self.product_id.qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Missing some stock to meet demand, return the quantity
return missing
else:
# Enough stock available to meet this line's demand
return 0
else:
# Already have stock reserved
return 0
else:
# This is a quotation, don't bother with stock reservations
missing = self.product_uom_qty - self.product_id.qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
return missing
else:
return 0
@api.depends('product_id', 'product_id.seller_ids',
'product_id.seller_ids.price')
def _compute_purchase_price_vendor(self):
for line in self:
product = line.product_id
suppinfos = product.seller_ids.sorted('sequence')
if not suppinfos:
line.purchase_price_vendor = 0.0
continue
suppinfo = suppinfos[0]
purch_currency = suppinfo.currency_id
to_cur = line.currency_id or line.order_id.currency_id
line.purchase_price_vendor = purch_currency._convert(
from_amount=suppinfo.price,
to_currency=to_cur,
company=line.company_id or self.env.company,
date=line.order_id.date_order or fields.Date.today(),
round=False,
) if to_cur and suppinfo.price else suppinfo.price
@api.depends('purchase_price_vendor')
def _compute_margin_vendor(self):
for line in self:
if not line.price_unit or float_is_zero(line.price_unit):
line.margin_vendor = 0
line.margin_percent_vendor = 0
continue
unit_margin = line.price_unit - line.purchase_price_vendor
line.margin_percent_vendor = unit_margin / line.price_unit
line.margin_vendor = unit_margin * line.product_uom_qty

View file

@ -0,0 +1,158 @@
from odoo import models, fields, api, _
from odoo.tools.float_utils import float_is_zero, float_compare
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
purchase_price_vendor = fields.Float(
compute='_compute_purchase_price_vendor',
string="Vendor Price",
groups="base.group_user",
digits='Product Price'
)
margin_percent_vendor = fields.Float(
string='Margin (%) on Vendor Price',
groups='base.group_user',
group_operator='avg',
compute='_compute_margin_vendor'
)
margin_vendor = fields.Float(
string='Margin on Vendor Price',
groups='base.group_user',
digits='Product Price',
compute='_compute_margin_vendor'
)
purchase_price_actual = fields.Float(
compute="_compute_actual_margins",
digits='Product Price',
groups="base.group_user",
string="Purchase Price"
)
margin_actual = fields.Float(
compute="_compute_actual_margins",
digits='Product Price',
groups="base.group_user",
string="Our Margin"
)
margin_percent_actual = fields.Float(
compute="_compute_actual_margins",
groups="base.group_user",
string="Our Margin (%)"
)
@api.depends(
'purchase_price',
'purchase_price_vendor',
'move_ids.product_id',
'move_ids.product_id.qty_available',
'move_ids.state',
'qty_to_deliver'
)
def _compute_actual_margins(self):
""" We want to use the margin based on average inventory valuation when the
sale order line will be completely fulfilled (or has been fulfilled) from stock.
For product not yet in stock we want to use the vendor price. We can also have
blended calculations (partly on vendor price, partly on stock valuation). This
occurs when an order has been or would be partially fulfilled from available
stock.
:return:
"""
non_product_lines = self.filtered(lambda r: not r.product_id)
non_product_lines.purchase_price_actual = 0.0
non_product_lines.margin_actual = 0.0
non_product_lines.margin_percent_actual = 0.0
for line in self - non_product_lines:
stock_missing = line._determine_missing_stock()
if float_is_zero(stock_missing, precision_rounding=line.product_uom.rounding):
# everything is coming from stock, use inventory valuation
line.purchase_price_actual = line.purchase_price
elif float_compare(line.product_uom_qty, stock_missing,
precision_rounding=line.product_uom.rounding) == 0:
# everything is coming from the vendor, use vendor pricing
line.purchase_price_actual = line.purchase_price_vendor
else:
# we have a mix, use blended pricing
qty_from_stock = line.product_uom_qty - stock_missing
line.purchase_price_actual = \
(stock_missing * line.purchase_price_vendor
+ qty_from_stock * line.purchase_price) \
/ line.product_uom_qty
line.margin_actual = line.price_subtotal - (
line.purchase_price_actual * line.product_uom_qty)
line.margin_percent_actual = line.price_subtotal and line.margin_actual / line.price_subtotal
def _determine_missing_stock(self) -> float:
""" Compute how much stock is missing to meet an order line's demand. In the
case of a quotation line, available stock is checked as if the order were to be
placed immediately.
:return: The quantity missing from available stock to fulfill the line, in
the unit of measure matching self.product_uom.
"""
self.ensure_one()
is_order = self.order_id.state in ('sale', 'done')
if is_order and self.qty_to_deliver == 0:
return 0
elif is_order and self.qty_to_deliver > 0:
reserved = sum([m.reserved_availability for m in self.move_ids])
missing = self.qty_to_deliver - reserved
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Not enough reserved, check stock
missing = missing - self.product_id.qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
# Missing some stock to meet demand, return the quantity
return missing
else:
# Enough stock available to meet this line's demand
return 0
else:
# Already have stock reserved
return 0
else:
# This is a quotation, don't bother with stock reservations
missing = self.product_uom_qty - self.product_id.qty_available
if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1:
return missing
else:
return 0
@api.depends('product_id', 'product_id.seller_ids',
'product_id.seller_ids.price')
def _compute_purchase_price_vendor(self):
for line in self:
product = line.product_id
suppinfos = product.seller_ids.sorted('sequence')
if not suppinfos:
line.purchase_price_vendor = 0.0
continue
suppinfo = suppinfos[0]
purch_currency = suppinfo.currency_id
to_cur = line.currency_id or line.order_id.currency_id
line.purchase_price_vendor = purch_currency._convert(
from_amount=suppinfo.price,
to_currency=to_cur,
company=line.company_id or self.env.company,
date=line.order_id.date_order or fields.Date.today(),
round=False,
) if to_cur and suppinfo.price else suppinfo.price
@api.depends('purchase_price_vendor')
def _compute_margin_vendor(self):
for line in self:
if not line.price_unit or float_is_zero(line.price_unit):
line.margin_vendor = 0
line.margin_percent_vendor = 0
continue
unit_margin = line.price_unit - line.purchase_price_vendor
line.margin_percent_vendor = unit_margin / line.price_unit
line.margin_vendor = unit_margin * line.product_uom_qty

View file

@ -1,6 +1,6 @@
{
'name': 'Module Linker',
'version': '15.0.1.0.0',
'version': '17.0.1.0.0',
'category': 'Extra Tools',
'summary': 'Link modules from external repositories',
'description': """

View file

@ -17,22 +17,27 @@
# DEALINGS IN THE SOFTWARE.
#
{
'name': 'bemade_multiple_billing_contacts',
'version': '15.0.1.0.1',
'name': 'Multiple Billing Contacts',
'version': '17.0.1.0.1',
'summary': 'Send invoices to multiple contacts by default.',
'description': """By default, newly created invoices add all invoice addresses for the given partner as
followers on the invoice. If billing contacts are set manually on the sales order, those billing
contacts are added as followers on the invoice instead.""",
'description': """
By default, newly created invoices add all invoice addresses for the given partner as
followers on the invoice. If billing contacts are set manually on the sales order, those billing
contacts are added as followers on the invoice instead.
""",
'category': 'Invoicing Management',
'author': 'Bemade Inc.',
'website': 'https://www.bemade.org',
'license': 'OPL-1',
'depends': ['sale',
'account',
'bemade_partner_root_ancestor',
],
'data': ['views/account_move_views.xml',
'views/res_partner_views.xml'],
'depends': [
'sale',
'account',
'bemade_partner_root_ancestor',
],
'data': [
'views/account_move_views.xml',
'views/res_partner_views.xml'
],
'demo': [],
'installable': True,
'auto_install': False,

View file

@ -4,11 +4,13 @@ from odoo import models, fields, api
class AccountMove(models.Model):
_inherit = 'account.move'
billing_contacts = fields.Many2many(comodel_name='res.partner',
string="Billing Contacts",
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True,)
billing_contacts = fields.Many2many(
comodel_name='res.partner',
string="Billing Contacts",
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True
)
@api.depends('line_ids.sale_line_ids.order_id', 'partner_id')
def _compute_billing_contacts(self):

View file

@ -4,12 +4,17 @@ from odoo import models, fields, api, _, Command
class Partner(models.Model):
_inherit = 'res.partner'
billing_contacts = fields.Many2many(string='Default Billing Contacts',
comodel_name='res.partner',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts')
potential_billing_contacts = fields.Many2many(comodel_name='res.partner',
compute='_compute_billing_contacts')
billing_contacts = fields.Many2many(
string='Default Billing Contacts',
comodel_name='res.partner',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts'
)
potential_billing_contacts = fields.Many2many(
comodel_name='res.partner',
compute='_compute_billing_contacts'
)
@api.depends('child_ids.type')
def _compute_billing_contacts(self):

View file

@ -4,11 +4,13 @@ from odoo import models, fields, api, _, Command
class SaleOrder(models.Model):
_inherit = 'sale.order'
billing_contacts = fields.Many2many(comodel_name='res.partner',
string='Billing Contacts',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True)
billing_contacts = fields.Many2many(
comodel_name='res.partner',
string='Billing Contacts',
compute='_compute_billing_contacts',
inverse='_inverse_billing_contacts',
store=True
)
@api.depends('partner_id')
def _compute_billing_contacts(self):

View file

@ -8,8 +8,7 @@
<field name="arch" type="xml">
<field name="partner_id" position="after">
<field name="billing_contacts"
attrs="{'invisible': [('move_type', 'not in',
('out_invoice', 'out_refund'))]}"
invisible="move_type not in ('out_invoice', 'out_refund')"
widget="many2many_checkboxes"
domain="['|', '|', ('id', 'in', billing_contacts),
('parent_id', '=', partner_id),

View file

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- Héritage de la vue formulaire du projet pour ajuster les champs de synchronisation -->
<record id="view_project_project_form_inherit_sync" model="ir.ui.view">
<field name="name">project.project.form.inherit.sync.odoo17</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project"/>
<field name="arch" type="xml">
<xpath expr="//sheet" position="inside">
<!-- Ajout d'un nouveau séparateur pour les champs de synchronisation -->
<group string="Synchronisation Odoo">
<field name="customer_odoo_server"/>
<field name="customer_username" invisible="customer_odoo_server == False"/>
<field name="customer_password" invisible="customer_odoo_server == False"/>
</group>
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -0,0 +1,10 @@
<record id="cron_sync_projects" model="ir.cron">
<field name="name">Synchroniser les Projets avec Bébé</field>
<field name="model_id" ref="model_project_project"/>
<field name="state">code</field>
<field name="code">model.sync_projects_with_baby()</field>
<field name="interval_number">1</field>
<field name="interval_type">hours</field>
<field name="numbercall">-1</field>
<field name="active">True</field>
</record>

View file

@ -0,0 +1 @@
from . import project_project

View file

@ -0,0 +1,47 @@
from odoo import models, fields, api
class Odoo2OdooSync(models.Model):
_name = 'odoo2odoo.sync'
_description = 'Synchronisation Odoo à Odoo'
model_id = fields.Many2one(
comodel_name='ir.model',
string='Modèle Cible',
required=True,
help="Le modèle Odoo cible de la synchronisation."
)
res_id = fields.Integer(
string='ID Ressource',
required=True,
help="L'ID de la ressource cible dans le modèle spécifié."
)
json_rpc_request = fields.Text(
string='Requête JSON RPC',
required=True,
help="Le corps complet de la requête JSON RPC pour la synchronisation."
)
state = fields.Selection(
selection=[
('draft', 'Brouillon'),
('success', 'Succès'),
('failed', 'Échoué')
],
string='État',
default='draft',
required=True,
help="L'état de la tentative de synchronisation."
)
last_attempt = fields.Datetime(
string='Dernière Tentative',
help="La date et l'heure de la dernière tentative de synchronisation."
)
confirmation = fields.Datetime(
string='Confirmation',
help="La date et l'heure de la confirmation de la synchronisation réussie."
)

View file

@ -0,0 +1,92 @@
from odoo import models, fields, api
class Project(models.Model):
_inherit = 'project.project' # Héritage du modèle projet existant
customer_project_id = fields.Integer(
string="ID Projet Client",
required=False,
help="L'ID du projet dans le système client."
)
customer_odoo_server = fields.Char(string="Serveur Odoo Client")
customer_username = fields.Char(string="Nom d'usager Client")
customer_password = fields.Char(string="Mot de passe Client", invisible=True)
odoo2odoo_sync_ids = fields.One2many(
comodel_name='odoo2odoo.sync',
inverse_name='project_id',
string='Synchronisations'
)
last_push = fields.Datetime(
string="Dernière Tentative de Synchronisation",
compute="_compute_sync_status",
store=True
)
last_push_completed = fields.Datetime(
string="Dernière Synchronisation Réussie",
compute="_compute_sync_status",
store=True
)
last_updated = fields.Datetime(
string="Dernière Mise à Jour par Bébé",
ompute="_compute_sync_status",
store=True
)
odoo2odoo_sync_count = fields.Integer(
string="Nombre de Synchronisations",
compute="_compute_sync_status"
)
odoo2odoo_sync_completed_count = fields.Integer(
string="Nombre de Synchronisations Réussies",
compute="_compute_sync_status"
)
@api.model
def create(self, vals):
# Appel de la méthode super() pour créer le projet dans Odoo.
new_project = super(Project, self).create(vals)
if 'customer_odoo_server' in vals and vals['customer_odoo_server']:
# Simulation de données pour la synchronisation avec le système "bébé".
# Vous adapterez cette partie selon votre logique spécifique de synchronisation.
json_rpc_request = json.dumps({
"jsonrpc": "2.0",
"method": "create_project",
"params": {
"name": new_project.name,
# Ajoutez d'autres paramètres nécessaires pour la création du projet dans le système "bébé".
},
})
# Création d'un enregistrement dans odoo2odoo.sync pour suivre la tentative de synchronisation.
self.env['odoo2odoo.sync'].create({
'model_id': self.env.ref('base.model_project_project').id,
'res_id': new_project.id,
'json_rpc_request': json_rpc_request,
'state': 'draft', # Commencez avec l'état 'draft' pour la nouvelle synchronisation.
})
return new_project
@api.depends('odoo2odoo_sync_ids.state')
def _compute_sync_status(self):
for project in self:
sync_records = self.env['odoo2odoo.sync'].search([
('model_id.model', '=', 'project.project'),
('res_id', '=', project.id),
], order='last_attempt desc')
project.odoo2odoo_sync_count = len(sync_records)
if sync_records:
project.odoo2odoo_sync_completed_count = len(sync_records.filtered(lambda r: r.state == 'success'))
project.last_push = sync_records[0].last_attempt
success_records = sync_records.filtered(lambda r: r.state == 'success')
if success_records:
project.last_push_completed = success_records[0].last_attempt
# Assume that 'last_updated' reflects the last successful pull from 'bébé'
# This requires additional logic to track when updates are received from the bébé system
project.last_updated = success_records[0].confirmation if success_records else False

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
</data>
</odoo>

View file

@ -23,12 +23,12 @@
# Commented for migration to 17, raises error on update
'data/res_partner_relation_type.xml'
],
"assets": {
"web.assets_backend": [
"bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js",
"bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml",
],
},
# "assets": {
# "web.assets_backend": [
# "bemade_odoo_partner_scrapper/static/src/js/odoo_scrapper.js",
# "bemade_odoo_partner_scrapper/static/src/xml/odoo_scrapper_templates.xml",
# ],
# },
'demo': [],
'installable': True,
'application': False,

View file

@ -60,10 +60,10 @@
<xpath expr="//kanban" position="inside">
<field name="odoo_partner_type"/>
</xpath>
<xpath expr="//div[@class='oe_kanban_global_click o_kanban_record_has_image_fill o_res_partner_kanban']" position="attributes">
<xpath expr="//div[hasclass('oe_kanban_global_click') and hasclass('o_kanban_record_has_image_fill') and hasclass('o_res_partner_kanban')]" position="attributes">
<attribute name="t-attf-class">oe_kanban_global_click o_kanban_record_has_image_fill o_res_partner_kanban oe_kanban_color_#{record.color}</attribute>
</xpath>
<xpath expr="//strong[@class='o_kanban_record_title oe_partner_heading']" position="before">
<xpath expr="//strong[hasclass('o_kanban_record_title') and hasclass('oe_partner_heading')]" position="before">
<strong t-if="record.odoo_partner_type.raw_value" class="o_kanban_record_subtitle oe_partner_heading">
<field name="odoo_partner_type"/>
</strong>

View file

@ -1,6 +1,6 @@
{
'name': 'Packing wizard',
'version': '16.0.1.0.0',
'version': '17.0.1.0.0',
'category': 'Extra Tools',
'summary': 'Allow automated packing type creation',
'description': """

View file

@ -59,7 +59,7 @@ class ChooseDeliveryPackage(models.TransientModel):
# if no package type found, create one
if not delivery_package_type:
delivery_package_type = delivery_package_type.create({
'name': f'Box {vals["width"]}x{vals["height"]}x{vals["length"]}',
'name': f'Box {self.width}x{self.height}x{self.length}',
'width': self.width,
'height': self.height,
'packaging_length': self.length,

View file

@ -1,6 +1,6 @@
{
'name': 'Automated Partner Association by Email Domain',
'version': '16.0.0.0.0',
'version': '17.0.0.0.0',
'category': 'Extra Tools',
'summary': 'Automatically associates partners with companies using matching email domains',
'description': """

View file

@ -8,13 +8,13 @@
<field name="partner_to">{{object.id}}</field>
<field name="subject">Select Your Division at {{object.company_id.name}}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial, sans-serif; color: #454748; width: 100%; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0"
style="padding-top: 16px; background-color: #F1F1F1; font-family:Verdana, Arial, sans-serif; color: #454748; width: 100%; border-collapse:separate;">
<tbody>
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<tbody>
<!-- HEADER -->
<tr>
@ -25,8 +25,8 @@
<!-- CONTENT -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="top" style="font-size: 13px;">
@ -55,8 +55,8 @@
<!-- FOOTER -->
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<table border="0" cellpadding="0" cellspacing="0" width="590"
style="min-width: 590px; background-color: white; font-size: 11px; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tbody>
<tr>
<td valign="middle" align="left">
@ -104,4 +104,3 @@
</record>
</data>
</odoo>

View file

@ -14,4 +14,4 @@
</t>
</template>
</data>
</odoo>
</odoo>

View file

@ -6,15 +6,14 @@
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<field name="company_type" position="after">
<label for="is_subdivision" string="Division" attrs="{'invisible': [('is_company', '=', False)]}"/>
<field name="is_subdivision" attrs="{'invisible': [('is_company', '=', False)]}"/>
<label for="is_subdivision" string="Division" invisible="is_company == False"/>
<field name="is_subdivision" invisible="is_company == False"/>
</field>
<field name="website" position="before">
<field name="email_domain" attrs="{'invisible': [('is_company', '=', False)]}"/>
<field name="email_domain" invisible="is_company == False"/>
</field>
<field name="parent_id" position="attributes">
<attribute name="attrs">{'invisible': [('is_company','=', True),('is_subdivision','=', False)]}
</attribute>
<attribute name="invisible">is_company == True and is_subdivision == False</attribute>
</field>
</field>
</record>

View file

@ -18,7 +18,7 @@
#
{
'name': 'Partner Root Ancestor',
'version': '15.0.1.0.0',
'version': '17.0.1.0.0',
'summary': 'Technical module to add the field root_ancestor to res.partner.',
'category': 'Generic Modules/Base',
'author': 'Bemade Inc.',

View file

@ -4,11 +4,13 @@ from odoo import models, fields, api, _, Command
class Partner(models.Model):
_inherit = 'res.partner'
root_ancestor = fields.Many2one(comodel_name='res.partner',
string='Root Ancestor',
compute='_compute_root_ancestor',
store=True,
recursive=True)
root_ancestor = fields.Many2one(
comodel_name='res.partner',
string='Root Ancestor',
compute='_compute_root_ancestor',
store=True,
recursive=True
)
@api.depends('parent_id', 'parent_id.root_ancestor')
def _compute_root_ancestor(self):

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Chatter on the Reordering Rules",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'license': 'GPL-3',
'summary': 'Add chatter on the reordering rules',
@ -15,7 +15,7 @@
],
"data": [
# BV : FOR MIGRATION
# 'views/stock_warehouse_orderpoint.xml',
'views/stock_warehouse_orderpoint.xml',
],
"auto_install": False,
"installable": True,

View file

@ -4,7 +4,7 @@
<record id="view_warehouse_orderpoint_tree_editable_config_chatter" model="ir.ui.view">
<field name="name">stock.warehouse.orderpoint.tree.editable.config.chatter</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable_config"/>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="editable"/>
@ -19,8 +19,8 @@
<field name="arch" type="xml">
<xpath expr="//form/sheet" position="after">
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" />
<field name="message_ids" widget="mail_thread" />
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</xpath>
</field>

View file

@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import models

View file

@ -0,0 +1,23 @@
{
'version': '17.0.0.1',
'summary': 'Search for products by supplier code',
'sequence': 10,
'description': """
This module adds the ability to search for products by supplier code.
""",
'category': 'Inventory/Purchase',
'author': 'Bemade',
'website': 'https://www.bemade.org',
'depends': [
'purchase',
'product'
],
'data': [
'views/product_product_views.xml',
],
'demo': [],
'installable': True,
'application': False,
'auto_install': False,
'license': 'LGPL-3',
}

View file

@ -0,0 +1 @@
from . import product_product

View file

@ -0,0 +1,26 @@
from odoo import models, fields, api
class ProductProduct(models.Model):
_inherit = 'product.product'
supplier_codes = fields.Char(
compute='_compute_supplier_codes',
string='Supplier Codes',
store=True,
search='_search_supplier_codes')
@api.depends('variant_seller_ids', 'variant_seller_ids.product_code')
def _compute_supplier_codes(self):
for product in self:
codes = filter(lambda x: isinstance(x, str), product.variant_seller_ids.mapped('product_code'))
product.supplier_codes = ', '.join(codes)
def _search_supplier_codes(self, operator, value):
if not value:
return []
supplierinfo_ids = self.env['product.supplierinfo'].search([('product_code', operator, value)])
product_ids = supplierinfo_ids.mapped('product_id.id')
return [('id', 'in', product_ids)]

View file

@ -0,0 +1,12 @@
<odoo>
<record id="view_product_product_search" model="ir.ui.view">
<field name="name">product.product.search.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_search_form_view"/>
<field name="arch" type="xml">
<field name="name" position="after">
<field name="supplier_codes"/>
</field>
</field>
</record>
</odoo>

View file

@ -1,16 +1,18 @@
# -*- coding: utf-8 -*-
{
"name": "Force Partner to be a company on SO and PO",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Force Partner to be a company on SO and PO',
"description": """
Force Partner to be a company on SO and PO
""",
Force Partner to be a company on SO and PO
""",
"author": "Bemade",
'website': 'https://www.bemade.org',
"depends": [
'stock','sale',
'purchase',
'stock',
'sale',
],
"data": [
'views/sale_order.xml',

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Add SO Followers to Picking",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Add SO Followers to Picking',
"description": """

View file

@ -18,7 +18,7 @@
#
{
'name': 'Sports Clinic Management',
'version': '16.0.1.5.2',
'version': '17.0.1.5.2',
'summary': 'Manage the patients of a sports medicine clinic.',
'description': """
Adds the notion of sports teams, players (patients), coaches and treatment
@ -42,8 +42,10 @@
'security/sports_clinic_groups.xml',
'security/ir.model.access.csv',
'security/sports_clinic_rules.xml',
'data/sports_clinic_data.xml',
'views/sports_team_views.xml',
'views/sports_clinic_menus.xml',
'views/sports_patient_injury_views.xml',
'views/sports_patient_views.xml',
'views/sports_clinic_portal_views.xml',
'views/res_partner_views.xml',

View file

@ -186,4 +186,4 @@
<field name="users" eval="[Command.link(ref('base.user_demo'))]"/>
</record>
</data>
</odoo>
</odoo>

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="subtype_patient_injury_external_update" model="mail.message.subtype">
<field name="name">Patient File Update (External)</field>
<field name="res_model">sports.patient.injury</field>
<field name="default" eval="True"/>
<field name="internal" eval="False"/>
<field name="sequence" eval="4"/>
<field name="hidden" eval="False"/>
<field name="description"></field>
</record>
<record id="subtype_patient_injury_internal_update" model="mail.message.subtype">
<field name="name">Patient File Update (Internal)</field>
<field name="res_model">sports.patient.injury</field>
<field name="default" eval="True"/>
<field name="internal" eval="True"/>
<field name="sequence" eval="4"/>
<field name="hidden" eval="False"/>
</record>
<record id="mail_template_patient_injury_status_update" model="mail.template">
<field name="name">Patient Status Update</field>
<field name="model_id" ref="bemade_sports_clinic.model_sports_patient_injury"/>
<field name="subject">Patient Injury Status Update for {{ object.patient_name }}</field>
<field name="body_html" type="html">
<p>An update has been posted to the injury record for <t t-out="object.patient_name"/>'s injury<t t-if="object.diagnosis">with
the diagnosis of <strong t-out="object.diagnosis"/></t>. Click
<a t-attf-href="{{ user.company_id.website }}/my/player?player_id={{ object.patient_id.id }}">here</a> to view the injury details.
</p>
</field>
</record>
<record id="mail_template_patient_injury_new_internal_note" model="mail.template">
<field name="name">Patient Status Update</field>
<field name="model_id" ref="bemade_sports_clinic.model_sports_patient_injury"/>
<field name="subject">Internal Note Updated for Patient {{ object.patient_name }}</field>
<field name="body_html" type="html">
<p>A new internal note has been posted to the injury record for <t t-out="object.patient_name"/>'s injury<t t-if="object.diagnosis">with
the diagnosis of <strong t-out="object.diagnosis"/></t>.
</p>
<div>
<p><strong>New Note: </strong></p>
<div><t t-out="object.internal_notes"/></div>
</div>
</field>
</record>
<record id="subtype_patient_external_update" model="mail.message.subtype">
<field name="name">Patient File Update (External)</field>
<field name="res_model">sports.patient</field>
<field name="default" eval="True"/>
<field name="internal" eval="False"/>
<field name="sequence" eval="4"/>
<field name="hidden" eval="False"/>
<field name="description"></field>
</record>
<record id="subtype_patient_internal_update" model="mail.message.subtype">
<field name="name">Patient File Update (Internal)</field>
<field name="res_model">sports.patient</field>
<field name="default" eval="True"/>
<field name="internal" eval="True"/>
<field name="sequence" eval="4"/>
<field name="hidden" eval="False"/>
</record>
<record id="mail_template_patient_status_update" model="mail.template">
<field name="name">Patient Status Update</field>
<field name="model_id" ref="bemade_sports_clinic.model_sports_patient"/>
<field name="subject">Patient Status Update for {{ object.name}}</field>
<field name="body_html" type="html">
<p>An update has been posted to the record for <t t-out="object.name"/>'s. Click
<a t-attf-href="{{ user.company_id.website }}/my/players">here</a> to view the details.
</p>
</field>
</record>
<record id="mail_template_patient_new_internal_note" model="mail.template">
<field name="name">Patient Status Update</field>
<field name="model_id" ref="bemade_sports_clinic.model_sports_patient"/>
<field name="subject">Internal Note Updated for Patient {{ object.name}}</field>
<field name="body_html" type="html">
<p>A new internal note has been posted to the record for <t t-out="object.name"/>'s.
</p>
<div>
<p><strong>New Note: </strong></p>
<div><t t-out="object.team_info_notes"/></div>
</div>
</field>
</record>
</data>
</odoo>

View file

@ -6,8 +6,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-11-07 20:03+0000\n"
"PO-Revision-Date: 2023-11-07 20:03+0000\n"
"POT-Creation-Date: 2024-02-21 00:52+0000\n"
"PO-Revision-Date: 2024-02-21 00:52+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
@ -25,11 +25,6 @@ msgstr ""
msgid "/my/teams"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_patient_injury
msgid "A patient's injury."
msgstr "La blessure d'un patient."
#. module: bemade_sports_clinic
#. odoo-python
#: code:addons/bemade_sports_clinic/models/sports_team.py:0
@ -105,6 +100,11 @@ msgstr "Administrateur"
msgid "Age"
msgstr "Âge"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__allergies
msgid "Allergies"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_attachment_count
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_attachment_count
@ -112,14 +112,9 @@ msgid "Attachment Count"
msgstr "Nombre de pièces jointes"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__practice_ok
msgid "Cleared for Practice"
msgstr "Pratiques autorisées"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__healthy
msgid "Cleared to Play"
msgstr "Matchs autorisés"
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__city
msgid "City"
msgstr "Ville"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_team_staff__role__coach
@ -128,8 +123,9 @@ msgstr "Entraîneur"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_res_partner
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__partner_id
msgid "Contact"
msgstr "Contact"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_contact__contact_type
@ -139,7 +135,12 @@ msgstr "Type de contact"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Contacts"
msgstr "Contacts"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__country_id
msgid "Country"
msgstr "Pays"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__create_uid
@ -159,15 +160,20 @@ msgstr "Créé par"
msgid "Created on"
msgstr "Créé le"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_kanban
msgid "DOB:"
msgstr "DDN :"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__date_of_birth
msgid "Date Of Birth"
msgstr "Date de naissance"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__injury_date_time
msgid "Date and Time of Injury"
msgstr "Date et heure de la blessure"
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__injury_date
msgid "Date of Injury"
msgstr "Date de la blessure"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_injury_view_tree
@ -189,6 +195,11 @@ msgstr "Diagnostique"
msgid "Display Name"
msgstr "Nom pour affichage"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_team_staff__role__doctor
msgid "Doctor"
msgstr "Docteur"
#. module: bemade_sports_clinic
#: model:ir.model.constraint,message:bemade_sports_clinic.constraint_sports_team_staff_team_staff_unique
msgid "Each partner can only be related to a given team once."
@ -301,7 +312,7 @@ msgstr "En santé :"
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__id
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__id
msgid "ID"
msgstr "ID"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__activity_exception_icon
@ -329,6 +340,13 @@ msgstr "Si coché, des nouveaux messages nécessitent votre attention."
msgid "If checked, some messages have a delivery error."
msgstr "Si coché, certains messages ont une erreur de livraison."
#. module: bemade_sports_clinic
#. odoo-python
#: code:addons/bemade_sports_clinic/models/patient_injury.py:0
#, python-format
msgid "If injury date is not set, the N/A box must be checked."
msgstr "Sa la date de la blessure n'est pas remplie, la case S/O doit être cochée."
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__no_play
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_teams
@ -463,7 +481,14 @@ msgstr "Erreur de livraison du message"
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__message_ids
msgid "Messages"
msgstr "Messages"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__mobile
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_contact__mobile
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__mobile
msgid "Mobile"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient_contact__contact_type__mother
@ -476,6 +501,12 @@ msgstr "Mère"
msgid "My Activity Deadline"
msgstr "Date limite de mon activité"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__injury_date_na
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient_injury__parental_consent__na
msgid "N/A"
msgstr "S/O"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__name
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_contact__name
@ -508,13 +539,15 @@ msgstr "Type de la prochaine activité"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__match_status__no
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__practice_status__no
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient_injury__parental_consent__no
msgid "No"
msgstr "Non"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__team_info_notes
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_player_injuries
msgid "Notes"
msgstr "Notes"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__message_needaction_counter
@ -557,6 +590,11 @@ msgstr "Organisation"
msgid "Other"
msgstr "Autre"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Other Contacts"
msgstr "Autres contacts"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_partner__owned_team_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_users__owned_team_ids
@ -571,55 +609,96 @@ msgid "Parent Organization"
msgstr "Organisation parente"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__parental_consent
msgid "Consent for Disclosure to Parent"
msgstr "Consentement pour divulgation au parent"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_patient
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_partner__patient_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_res_users__patient_ids
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_contact__patient_id
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__patient_id
msgid "Patient"
msgstr "Patient(e)"
msgstr "Patient dans une clinique de médecine sportive."
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient Address"
msgstr "Adresse du patient"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__contact_ids
msgid "Patient Contacts"
msgstr "Contacts du patient"
#. module: bemade_sports_clinic
#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_update
msgid "Patient File Update"
msgstr "Mise à jour du dossier du patient"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient Information"
msgstr "Information du patient"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_patient_injury
msgid "Patient Injury"
msgstr "La blessure d'un patient."
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_injury_view_tree
msgid "Patient Injury History"
msgstr "Historique des blessures du patient"
#. module: bemade_sports_clinic
#: model:mail.message.subtype,name:bemade_sports_clinic.subtype_patient_injury_update
msgid "Patient Injury Status Update"
msgstr "Mise à jour du statut de la blessure d'un patient"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient Phone & Email"
msgstr "Téléphone et courriel du patient"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient Record"
msgstr "Dossier du patient"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_patient
msgid "Patient at a sports medicine clinic."
msgstr "Patient dans une clinique de médecine sportive."
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_form
msgid "Patient's Contacts"
msgstr "Contacts du patient"
#. module: bemade_sports_clinic
#: model:mail.message.subtype,description:bemade_sports_clinic.subtype_patient_update
msgid "Patient's file has been updated."
msgstr "Le dossier du patient a été mis à jour."
#. module: bemade_sports_clinic
#: model:mail.message.subtype,description:bemade_sports_clinic.subtype_patient_injury_update
msgid "Patient's injury was updated."
msgstr "Le dossier de la blessure du patient a été mis à jour."
#. module: bemade_sports_clinic
#: model:ir.actions.act_window,name:bemade_sports_clinic.action_view_patient
#: model:ir.ui.menu,name:bemade_sports_clinic.sports_clinic_patients
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_list
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_list_embedded
msgid "Patients"
msgstr "Patients"
msgstr ""
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__mobile
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_contact__mobile
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__mobile
msgid "Mobile"
msgstr "Mobile"
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__phone
msgid "Phone"
msgstr "Téléphone"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__healthy
msgid "Play OK"
msgstr "Matchs autorisés"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team__player_count
@ -635,6 +714,11 @@ msgstr "Nombre de joueurs"
msgid "Players"
msgstr "Joueurs"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__stage__practice_ok
msgid "Practice OK"
msgstr "Pratiques autorisées"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__practice_status
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_players
@ -652,11 +736,6 @@ msgstr "Date prévue de résolution"
msgid "Predicted Return Date"
msgstr "Date prévue de retour"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_team_staff
msgid "Relationship between staff members and their teams."
msgstr "Relation entre les membres du personnel et leurs équipes."
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient_injury__resolution_date
msgid "Resolution Date"
@ -720,6 +799,11 @@ msgstr "Gestion de clinique de médecine sportive"
msgid "Sports Team"
msgstr "Équipe sportive"
#. module: bemade_sports_clinic
#: model:ir.model,name:bemade_sports_clinic.model_sports_team_staff
msgid "Sports Team Staff"
msgstr "Relation entre les membres du personnel et leurs équipes."
#. module: bemade_sports_clinic
#: model:ir.actions.act_window,name:bemade_sports_clinic.action_view_team
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_team_view_list
@ -744,6 +828,11 @@ msgstr "Membre du personnel"
msgid "Stage"
msgstr "Étape"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__state_id
msgid "State"
msgstr "État"
#. module: bemade_sports_clinic
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__activity_state
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__activity_state
@ -758,6 +847,21 @@ msgstr ""
"Aujourd'hui: La date d'échéance est aujourd'hui\n"
"Planifiées: Activités futures."
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.sports_patient_view_kanban
msgid "Status:"
msgstr "Statut :"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__street
msgid "Street"
msgstr "Rue"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__street2
msgid "Street2"
msgstr "Rue2"
#. module: bemade_sports_clinic
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_team_staff__team_id
msgid "Team"
@ -804,6 +908,11 @@ msgstr "La date quand la blessure a été résolue."
msgid "The teams this person works for."
msgstr "Les équipes pour lesquelles cette personne travaille."
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_team_staff__role__therapist
msgid "Therapist"
msgstr "Thérapeute"
#. module: bemade_sports_clinic
#. odoo-python
#: code:addons/bemade_sports_clinic/controllers/team_staff_portal.py:0
@ -818,21 +927,18 @@ msgstr "Ce joueur n'a pu être trouvé."
msgid "This team could not be found."
msgstr "Cette équipe n'a pu être trouvée."
#. module: bemade_sports_clinic
#. odoo-python
#: code:addons/bemade_sports_clinic/models/res_partner.py:0
#, python-format
msgid "To change a patient's name, change it from the patient form."
msgstr "Pour changer le nom d'un patient, changez le à partir du formulaire patient."
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_teams
msgid "Total Players"
msgstr "Total des joueurs"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_team_staff__role__therapist
msgid "Therapist"
msgstr "Thérapeute"
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_team_staff__role__doctor
msgid "Doctor"
msgstr "Docteur"
#. module: bemade_sports_clinic
#: model:res.groups,name:bemade_sports_clinic.group_sports_clinic_treatment_professional
msgid "Treatment Professional"
@ -879,11 +985,22 @@ msgstr "Historique de communication du site web"
#. module: bemade_sports_clinic
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient__return_date
msgid "When the player was cleared by medical staff to return to match play."
msgstr "Quand le joueur a été autorisé par le personnel médical à retourner au jeu."
msgstr ""
"Quand le joueur a été autorisé par le personnel médical à retourner au jeu."
#. module: bemade_sports_clinic
#: model:ir.model.fields,help:bemade_sports_clinic.field_sports_patient_injury__parental_consent
msgid ""
"Whether the patient has given their consent to share injury details with "
"their parents."
msgstr ""
"Indique si le patient a donné son consentement pour que les détails de la blessure soient partagés avec "
"ses parents."
#. module: bemade_sports_clinic
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__match_status__yes
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient__practice_status__yes
#: model:ir.model.fields.selection,name:bemade_sports_clinic.selection__sports_patient_injury__parental_consent__yes
msgid "Yes"
msgstr "Oui"
@ -903,11 +1020,6 @@ msgid "Your Teams"
msgstr "Vos équipes"
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home
msgid "players_count"
msgstr ""
#. module: bemade_sports_clinic
#: model_terms:ir.ui.view,arch_db:bemade_sports_clinic.portal_my_home
msgid "teams_count"
msgstr ""
#: model:ir.model.fields,field_description:bemade_sports_clinic.field_sports_patient__zip
msgid "Zip"
msgstr "Code postal"

View file

@ -0,0 +1,21 @@
from odoo import api, SUPERUSER_ID, Command
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
patient_followers = env['mail.followers'].search([('res_model', '=',
'sports.patient')])
injury_followers = env['mail.followers'].search([('res_model', '=',
'sports.patient.injury')])
for f in patient_followers:
f.write(
{
'subtype_ids':
[Command.link(env.ref('bemade_sports_clinic.subtype_patient_update').id)]
}
)
for f in injury_followers:
f.write(
{
'subtype_ids':
[Command.link(env.ref('bemade_sports_clinic.subtype_patient_injury_update').id)]
})

View file

@ -0,0 +1,22 @@
from odoo import api, SUPERUSER_ID, Command
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
patient_followers = env['mail.followers'].search([('res_model', '=',
'sports.patient')])
injury_followers = env['mail.followers'].search([('res_model', '=',
'sports.patient.injury')])
for f in patient_followers:
if f.partner_id.user_ids.has_group('base.group_user'):
subtype = env.ref('bemade_sports_clinic.subtype_patient_internal_update').id
else:
subtype = env.ref('bemade_sports_clinic.subtype_patient_external_update').id
f.write({'subtype_ids': [Command.link(subtype)]})
for f in injury_followers:
if f.partner_id.user_ids.has_group('base.group_user'):
subtype = env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update').id
else:
subtype = env.ref('bemade_sports_clinic.subtype_patient_injury_external_update').id
f.write({'subtype_ids': [Command.link(subtype)]})

View file

@ -1,4 +1,7 @@
from . import mail_followers
from . import patient
from . import patient_injury
from . import patient_contact
from . import res_partner
from . import sports_team
from . import res_users

View file

@ -0,0 +1,46 @@
from odoo import models, fields, api, _
class MailFollowers(models.Model):
_inherit = "mail.followers"
# This is a user quality of life modification, to avoid duplicated message subtype subscription for internal
# and external notifications (since both subtypes are default=True).
def write(self, vals):
super().write(vals)
subtypes_map = self.__subtypes_map()
if self.res_model in subtypes_map and 'subtype_ids' in vals:
internal_subtype, external_subtype = subtypes_map.get(self.res_model)
if external_subtype in self.subtype_ids and internal_subtype in self.subtype_ids:
self.subtype_ids = self.subtype_ids - external_subtype
@api.model_create_multi
def create(self, vals_list):
recs = super().create(vals_list)
subtypes_map = self.__subtypes_map()
to_fix = {
model:
recs.filtered(
lambda rec: rec.res_model == model
and subtypes[0] in rec.subtype_ids
and subtypes[1] in rec.subtype_ids
) for model, subtypes in subtypes_map.items()
}
for model, records in to_fix.items():
for rec in records:
rec.subtype_ids = rec.subtype_ids - subtypes_map[model][1]
return recs
def __subtypes_map(self):
xml_id_fmt_string = "bemade_sports_clinic.subtype_{0}_{1}_update"
return {
'sports.patient': (
self.env.ref(xml_id_fmt_string.format('patient', 'internal')),
self.env.ref(xml_id_fmt_string.format('patient', 'external')),
),
'sports.patient.injury': (
self.env.ref(xml_id_fmt_string.format('patient_injury', 'internal')),
self.env.ref(xml_id_fmt_string.format('patient_injury', 'external')),
),
}

View file

@ -3,11 +3,26 @@ from odoo.exceptions import ValidationError
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.addons.phone_validation.tools import phone_validation
from typing import Set, Tuple
external_tracking_fields = {
'last_consultation_date',
'match_status',
'practice_status',
'predicted_return_date',
'return_date',
}
internal_tracking_fields = {
'team_info_notes',
'age',
'date_of_birth',
}
class Patient(models.Model):
_name = 'sports.patient'
_description = "Patient at a sports medicine clinic."
_description = "Patient"
_inherit = ['mail.thread', 'mail.activity.mixin']
_order = 'last_name, first_name'
@ -30,43 +45,68 @@ class Patient(models.Model):
date_of_birth = fields.Date(
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional",
tracking=True)
age = fields.Integer(compute='_compute_age',
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional",
tracking=True)
contact_ids = fields.One2many(comodel_name='sports.patient.contact',
inverse_name='patient_id',
string='Patient Contacts',
groups="bemade_sports_clinic.group_sports_clinic_user")
team_ids = fields.Many2many(comodel_name='sports.team',
relation='sports_team_patient_rel',
column1='patient_id',
column2='team_id',
string='Teams', )
match_status = fields.Selection([ # Selection for easy expansion later
('yes', 'Yes'),
('no', 'No'),
], required=True, default='yes', tracking=True)
practice_status = fields.Selection([
('yes', 'Yes'),
('no_contact', 'Yes, no contact'),
('no', 'No')], tracking=True, required=True, default='yes')
injury_ids = fields.One2many(comodel_name='sports.patient.injury',
inverse_name='patient_id',
string='Injuries', )
age = fields.Integer(
compute='_compute_age',
groups="bemade_sports_clinic.group_sports_clinic_treatment_professional"
)
contact_ids = fields.One2many(
comodel_name='sports.patient.contact',
inverse_name='patient_id',
string='Patient Contacts',
groups="bemade_sports_clinic.group_sports_clinic_user"
)
team_ids = fields.Many2many(
comodel_name='sports.team',
relation='sports_team_patient_rel',
column1='patient_id',
column2='team_id',
string='Teams',
)
match_status = fields.Selection( # Selection rather than bool for easy expansion later
selection=[
('yes', 'Yes'),
('no', 'No'),
],
required=True,
default='yes',
tracking=True)
practice_status = fields.Selection(
selection=[
('yes', 'Yes'),
('no_contact', 'Yes, no contact'),
('no', 'No')
],
tracking=True,
required=True,
default='yes',
)
injury_ids = fields.One2many(
comodel_name='sports.patient.injury',
inverse_name='patient_id',
string='Injuries',
)
injured_since = fields.Date(compute='_compute_is_injured')
predicted_return_date = fields.Date(tracking=True)
return_date = fields.Date(tracking=True,
help="When the player was cleared by medical staff to "
"return to match play.")
return_date = fields.Date(
tracking=True,
help="When the player was cleared by medical staff to "
"return to match play."
)
is_injured = fields.Boolean(compute="_compute_is_injured")
stage = fields.Selection(
selection=[('no_play', 'Injured'), ('practice_ok', 'Practice OK'), ('healthy', 'Play OK')],
selection=[
('no_play', 'Injured'),
('practice_ok', 'Practice OK'),
('healthy', 'Play OK')
],
compute='_compute_stage')
last_consultation_date = fields.Date()
last_consultation_date = fields.Date(tracking=True)
active_injury_count = fields.Integer(compute='_compute_active_injury_count')
allergies = fields.Text()
team_info_notes = fields.Html(string="Notes")
team_info_notes = fields.Html(
string="Notes",
tracking=True,
)
def default_get(self, fields_list):
res = super().default_get(fields_list)
@ -187,116 +227,28 @@ class Patient(models.Model):
raise_exception=False
)
def _track_subtype(self, init_values):
return self.env.ref('mail.mt_note')
class PatientContact(models.Model):
_name = 'sports.patient.contact'
_description = "Emergency or other contacts for a patient."
sequence = fields.Integer(required=True, default=0)
name = fields.Char(unaccent=False)
contact_type = fields.Selection(selection=[
('mother', 'Mother'),
('father', 'Father'),
('other', 'Other'),
], required=True)
mobile = fields.Char(unaccent=False, required=True)
patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient')
@api.onchange('mobile')
def _onchange_mobile_validation(self):
if self.mobile:
self.mobile = self._phone_format(self.mobile, force_format="INTERNATIONAL")
def _phone_format(self, number, force_format='E164'):
country = self.patient_id.country_id or self.env.company.country_id
if not country or not number:
return number
return phone_validation.phone_format(
number,
country.code if country else None,
country.phone_code if country else None,
force_format=force_format,
raise_exception=False
)
class PatientInjury(models.Model):
_name = 'sports.patient.injury'
_description = "A patient's injury."
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'diagnosis'
patient_id = fields.Many2one(comodel_name='sports.patient',
string="Patient",
readonly=True,
required=True)
patient_name = fields.Char(related="patient_id.name")
diagnosis = fields.Char(tracking=True)
injury_date = fields.Date(string='Date of Injury',
default=date.today())
injury_date_na = fields.Boolean(string="N/A", default=False)
internal_notes = fields.Html(tracking=True)
external_notes = fields.Html(tracking=True)
treatment_professional_ids = fields.Many2many(comodel_name='res.users',
relation='patient_injury_treatment_pro_rel',
column1='patient_injury_id',
column2='treatment_pro_id', string='Treatment Professionals',
domain=[
('is_treatment_professional', '=',
True)], tracking=True)
predicted_resolution_date = fields.Date(tracking=True)
resolution_date = fields.Date(tracking=True,
help="The date when the injury was actually resolved.")
stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage')
@api.constrains('injury_date_na', 'injury_date')
def constrain_date_blank_only_if_na(self):
for rec in self:
if not rec.injury_date_na and not rec.injury_date:
raise ValidationError(_("If injury date is not set, the N/A box must be checked."))
@api.onchange('injury_date_na')
def _onchange_injury_date_na(self):
for rec in self:
if rec.injury_date_na:
rec.injury_date = None
@api.onchange('injury_date')
def _onchange_injury_date(self):
for rec in self:
if rec.injury_date:
rec.injury_date_na = False
@api.depends('resolution_date')
def _compute_stage(self):
for rec in self:
if rec.resolution_date and rec.resolution_date <= date.today():
rec.stage = 'resolved'
else:
rec.stage = 'active'
def write(self, vals):
super().write(vals)
if 'treatment_professional_ids' in vals:
to_subscribe = (self.treatment_professional_ids.mapped('partner_id')
- self.message_follower_ids.mapped('partner_id'))
self.message_subscribe(to_subscribe.ids)
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for rec in res:
to_subscribe = (rec.treatment_professional_ids.mapped('partner_id')
- rec.message_follower_ids.mapped('partner_id'))
rec.message_subscribe(to_subscribe.ids)
def _track_template(self, changes):
res = super()._track_template(changes)
params = set(changes)
external = bool(external_tracking_fields & params)
if external:
first_external_field = (external_tracking_fields & params).pop()
res[first_external_field] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_status_update'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_external_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
if 'team_info_notes' in changes:
res['team_info_notes'] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_new_internal_note'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_internal_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
return res
def action_view_injury_form(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sports.patient.injury',
'res_id': self.id,
'context': self._context,
}

View file

@ -0,0 +1,35 @@
from odoo import models, fields, api
from odoo.addons.phone_validation.tools import phone_validation
class PatientContact(models.Model):
_name = 'sports.patient.contact'
_description = "Emergency or other contacts for a patient."
sequence = fields.Integer(required=True, default=0)
name = fields.Char(unaccent=False)
contact_type = fields.Selection(selection=[
('mother', 'Mother'),
('father', 'Father'),
('other', 'Other'),
], required=True)
mobile = fields.Char(unaccent=False, required=True)
# TODO: add email here and on views
patient_id = fields.Many2one(comodel_name='sports.patient', string='Patient')
@api.onchange('mobile')
def _onchange_mobile_validation(self):
if self.mobile:
self.mobile = self._phone_format(self.mobile, force_format="INTERNATIONAL")
def _phone_format(self, number, force_format='E164'):
country = self.patient_id.country_id or self.env.company.country_id
if not country or not number:
return number
return phone_validation.phone_format(
number,
country.code if country else None,
country.phone_code if country else None,
force_format=force_format,
raise_exception=False
)

View file

@ -0,0 +1,153 @@
from odoo import models, fields, api, _
from datetime import datetime, date
import pytz
from odoo.exceptions import ValidationError
from typing import Set, Tuple
external_tracking_fields = {
'diagnosis',
'predicted_resolution_date',
'resolution_date',
'external_notes',
}
# Include only fields not already included in external_tracking_fields here
internal_tracking_fields = {
'internal_notes',
'parental_consent',
}
class PatientInjury(models.Model):
_name = 'sports.patient.injury'
_description = "Patient Injury"
_inherit = ['mail.thread', 'mail.activity.mixin']
_rec_name = 'diagnosis'
@api.model
def _today(self):
"""Get the current date in the user's time zone."""
return datetime.now(pytz.timezone(self.env.user.tz or 'GMT'))
# TODO: Find a way to improve notifications send about tracking injury details
# TODO: Add field consentement_parental = fields.Selection(oui, non, non-applicable)
patient_id = fields.Many2one(
comodel_name='sports.patient',
string="Patient",
readonly=True,
required=True
)
patient_name = fields.Char(related="patient_id.name")
diagnosis = fields.Char(tracking=True)
injury_date = fields.Date(
string='Date of Injury',
default=_today,
)
injury_date_na = fields.Boolean(string="N/A", default=False)
internal_notes = fields.Html(tracking=True)
external_notes = fields.Html(tracking=True)
treatment_professional_ids = fields.Many2many(
comodel_name='res.users',
relation='patient_injury_treatment_pro_rel',
column1='patient_injury_id',
column2='treatment_pro_id', string='Treatment Professionals',
domain=[
('is_treatment_professional', '=',
True)], tracking=True
)
predicted_resolution_date = fields.Date(tracking=True)
resolution_date = fields.Date(
tracking=True,
help="The date when the injury was actually resolved."
)
stage = fields.Selection(selection=[('active', 'Active'), ('resolved', 'Resolved')], compute='_compute_stage')
parental_consent = fields.Selection(
string="Consent for Disclosure to Parent",
selection=[
('yes', 'Yes'),
('no', 'No'),
('na', 'N/A')
],
help="Whether the patient has given their consent to share injury details with their parents.",
tracking=True,
)
@api.constrains('injury_date_na', 'injury_date')
def constrain_date_blank_only_if_na(self):
for rec in self:
if not rec.injury_date_na and not rec.injury_date:
raise ValidationError(_("If injury date is not set, the N/A box must be checked."))
@api.onchange('injury_date_na')
def _onchange_injury_date_na(self):
for rec in self:
if rec.injury_date_na:
rec.injury_date = None
@api.onchange('injury_date')
def _onchange_injury_date(self):
for rec in self:
if rec.injury_date:
rec.injury_date_na = False
@api.depends('resolution_date')
def _compute_stage(self):
for rec in self:
if rec.resolution_date and rec.resolution_date <= date.today():
rec.stage = 'resolved'
else:
rec.stage = 'active'
def write(self, vals):
super().write(vals)
if 'treatment_professional_ids' in vals:
to_subscribe = (self.treatment_professional_ids.mapped('partner_id')
- self.message_follower_ids.mapped('partner_id'))
self.message_subscribe(to_subscribe.ids)
@api.model_create_multi
def create(self, vals_list):
res = super().create(vals_list)
for rec in res:
to_subscribe = (rec.treatment_professional_ids.mapped('partner_id')
- rec.message_follower_ids.mapped('partner_id'))
rec.message_subscribe(to_subscribe.ids)
return res
def action_view_injury_form(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sports.patient.injury',
'res_id': self.id,
'context': self._context,
}
def _track_subtype(self, init_values):
return self.env.ref('mail.mt_note')
def _track_template(self, changes):
res = super()._track_template(changes)
params = set(changes)
external = bool(external_tracking_fields & params)
if external:
first_external_field = (external_tracking_fields & params).pop()
res[first_external_field] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_injury_status_update'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_external_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
if 'internal_notes' in changes:
res['internal_notes'] = (
self.env.ref('bemade_sports_clinic.mail_template_patient_injury_new_internal_note'), {
'auto_delete_message': False,
'subtype_id': self.env.ref('bemade_sports_clinic.subtype_patient_injury_internal_update').id,
'email_layout_xmlid': 'mail.mail_notification_light',
}
)
return res

View file

@ -51,7 +51,7 @@ class SportsTeam(models.Model):
class TeamStaff(models.Model):
_name = "sports.team.staff"
_description = "Relationship between staff members and their teams."
_description = "Sports Team Staff"
sequence = fields.Integer()
team_id = fields.Many2one(comodel_name='sports.team', string='Team', required=True)

View file

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="sports_patient_injury_view_form" model="ir.ui.view">
<field name="name">sports.patient.injury.view.form</field>
<field name="model">sports.patient.injury</field>
<field name="arch" type="xml">
<form>
<header>
<field name="stage" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1>
Injury Details for
<field name="patient_name" class="oe_inline"/>
</h1>
</div>
<group>
<group col="2">
<field name="patient_id" invisible="1"/>
<span colspan="2">
<label for="injury_date"/>
<field name="injury_date" widget="date" class="oe_inline"/>
<field name="injury_date_na" class="oe_inline"/>
<label for="injury_date_na"/>
</span>
<field name="diagnosis"/>
<field name="predicted_resolution_date"/>
<field name="resolution_date"/>
</group>
<group>
<field name="parental_consent"/>
<field name="internal_notes" widget="html"/>
<field name="external_notes" widget="html"/>
<field name="treatment_professional_ids"
widget="many2many_tags_avatar"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="sports_patient_injury_view_tree" model="ir.ui.view">
<field name="name">sports.patient.injury.view.tree</field>
<field name="model">sports.patient.injury</field>
<field name="arch" type="xml">
<tree string="Patient Injury History" editable="bottom"
multi_edit="True">
<field name="patient_id" invisible="1"/>
<field name="injury_date" widget="date"/>
<field name="diagnosis"/>
<field name="predicted_resolution_date" widget="date"/>
<field name="resolution_date" widget="date"/>
<field name="treatment_professional_ids"
widget="many2many_avatar_user"/>
<field name="activity_ids" widget="list_activity"/>
<field name="parental_consent"/>
<button name="action_view_injury_form" type="object"
title="Details" string="Details"
context="{'default_patient_id': patient_id}"/>
</tree>
</field>
</record>
</odoo>

View file

@ -184,69 +184,6 @@
</form>
</field>
</record>
<record id="sports_patient_injury_view_form" model="ir.ui.view">
<field name="name">sports.patient.injury.view.form</field>
<field name="model">sports.patient.injury</field>
<field name="arch" type="xml">
<form>
<header>
<field name="stage" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<h1>
Injury Details for
<field name="patient_name" class="oe_inline"/>
</h1>
</div>
<group>
<group col="2">
<field name="patient_id" invisible="1"/>
<span colspan="2">
<label for="injury_date"/>
<field name="injury_date" widget="date" class="oe_inline"/>
<field name="injury_date_na" class="oe_inline"/>
<label for="injury_date_na"/>
</span>
<field name="diagnosis"/>
<field name="predicted_resolution_date"/>
<field name="resolution_date"/>
</group>
<group>
<field name="internal_notes" widget="html"/>
<field name="external_notes" widget="html"/>
<field name="treatment_professional_ids"
widget="many2many_tags_avatar"/>
</group>
</group>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers"/>
<field name="activity_ids" widget="mail_activity"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<record id="sports_patient_injury_view_tree" model="ir.ui.view">
<field name="name">sports.patient.injury.view.tree</field>
<field name="model">sports.patient.injury</field>
<field name="arch" type="xml">
<tree string="Patient Injury History" editable="bottom"
multi_edit="True">
<field name="patient_id" invisible="1"/>
<field name="injury_date" widget="date"/>
<field name="diagnosis"/>
<field name="predicted_resolution_date" widget="date"/>
<field name="resolution_date" widget="date"/>
<field name="treatment_professional_ids"
widget="many2many_avatar_user"/>
<field name="activity_ids" widget="list_activity"/>
<button name="action_view_injury_form" type="object"
title="Details" string="Details"
context="{'default_patient_id': patient_id}"/>
</tree>
</field>
</record>
>
</data>
</odoo>

View file

@ -18,15 +18,21 @@
#
{
'name': 'Stock Quant Valuation',
'version': '16.0.1.0.0',
'summary': 'Adds valuation to stock quant for better inventory adjustment management',
'version': '17.0.1.0.0',
'summary': """
Adds valuation to stock quant for better inventory adjustment management'
""",
'description': '',
'category': 'Inventory',
'author': 'Bemade Inc.',
'website': 'https://www.bemade.org',
'license': 'OPL-1',
'depends': ['stock_account'],
'data': ['views/stock_account_views.xml'],
'depends': [
'stock_account'
],
'data': [
'views/stock_account_views.xml'
],
'demo': [],
'installable': True,
'auto_install': False,

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Time Off Alternative Follower",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Add Alternative Follower When Receiving Message While On Time Off',
"description": """

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
{
"name": "Automatic Quotation Date and Validity Updater",
"version": "15.0.0.0.1",
"version": "17.0.0.0.1",
"category": "Extra Tools",
'summary': 'Automatically updates the sale quotation date and validity date upon sending the quotation',
"description": """

View file

@ -1,6 +1,6 @@
{
'name': 'Temporary Change for Image Full Size',
'version': '16.0.0.0.0',
'version': '17.0.0.0.0',
'category': 'Extra Tools',
'summary': 'Temporary Change for Image Full Size',
'description': """