Merge branch 'Bemade:17.0' into 17.0

This commit is contained in:
Marc Durepos 2024-03-28 17:33:52 -04:00 committed by GitHub
commit 0058948551
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 719 additions and 475 deletions

View file

@ -44,8 +44,8 @@ class Partner(models.Model):
rec.hold_bg = False rec.hold_bg = False
rec.message_post(body=_('Credit hold lifted.')) rec.message_post(body=_('Credit hold lifted.'))
def _execute_followup_partner(self): def _execute_followup_partner(self, options=None):
res = super()._execute_followup_partner() res = super()._execute_followup_partner(options)
if self.followup_status == 'in_need_of_action': if self.followup_status == 'in_need_of_action':
if self.followup_line_id.account_hold: if self.followup_line_id.account_hold:
self.action_credit_hold() self.action_credit_hold()

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<record id="account_followup_followup_line_form_inherit" model="ir.ui.view"> <record id="account_followup_followup_line_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.account_followup_line.form</field> <field name="name">account_credit_hold.account_followup_line.form</field>
<field name="model">account_followup.followup.line</field> <field name="model">account_followup.followup.line</field>
@ -9,44 +8,26 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='send_email']" position="before"> <xpath expr="//field[@name='send_email']" position="before">
<field name="account_hold"/> <field name="account_hold"/>
</xpath> </xpath></field>
</field>
</record> </record>
<record id="customer_statements_form_view_inherit" model="ir.ui.view"> <record id="customer_statements_form_view_inherit" model="ir.ui.view">
<field name="name">customer.statements.form.view.inherit</field> <field name="name">customer.statements.form.view.inherit</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="account_followup.customer_statements_form_view"/> <field name="inherit_id" ref="account_followup.customer_statements_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//button[last()]" position="after"> <xpath expr="//button[last()]" position="after">
<field name="hold_bg" invisible="1"/> <field invisible="1" name="hold_bg"/>
<button <button class="button btn-secondary" invisible="hold_bg == True" name="action_credit_hold" string="Credit Hold" type="object"/>
string="Credit Hold" <button class="button btn-secondary" invisible="hold_bg == False" name="action_lift_credit_hold" string="Lift Credit Hold" type="object"/>
type="object" </xpath></field>
name="action_credit_hold"
class="button btn-secondary"
attrs="{'invisible': [('hold_bg', '=', True)]}"
/>
<button
string="Lift Credit Hold"
type="object"
name="action_lift_credit_hold"
class="button btn-secondary"
attrs="{'invisible': [('hold_bg', '=', False)]}"
/>
</xpath>
</field>
</record> </record>
<record id="action_credit_hold" model="ir.actions.server"> <record id="action_credit_hold" model="ir.actions.server">
<field name="name">action_credit_hold</field> <field name="name">action_credit_hold</field>
<field name="model_id" ref="base.model_res_partner"/> <field name="model_id" ref="base.model_res_partner"/>
<field name="binding_model_id" ref="base.model_res_partner"/> <field name="binding_model_id" ref="base.model_res_partner"/>
<field name="binding_view_types">list,form</field> <field name="binding_view_types">list,form</field>
<field name="state">code</field> <field name="state">code</field>
<field name="code"> <field name="code">records.action_credit_hold()</field>
records.action_credit_hold()
</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -1,19 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<record id="res_partner_form_inherit" model="ir.ui.view"> <record id="res_partner_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.res_partner.form</field> <field name="name">account_credit_hold.res_partner.form</field>
<field name="model">res.partner</field> <field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/> <field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before"> <xpath expr="//div[@name='button_box']" position="before">
<field name="hold_bg" invisible="True" /> <field invisible="True" name="hold_bg"/>
<field name="on_hold" invisible="True" /> <field invisible="True" name="on_hold"/>
<widget name="web_ribbon" title="Credit Hold" <widget bg_color="bg-danger" invisible="on_hold == False" name="web_ribbon" title="Credit Hold"/>
bg_color="bg-danger" attrs="{'invisible': [('on_hold','=',False)]}"/> </xpath></field>
</xpath>
</field>
</record> </record>
<record id="view_partner_property_form_inherit" model="ir.ui.view"> <record id="view_partner_property_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.view_partner_property_form</field> <field name="name">account_credit_hold.view_partner_property_form</field>
@ -22,12 +19,9 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//group[@name='banks']" position="before"> <xpath expr="//group[@name='banks']" position="before">
<group string="Credit Hold"> <group string="Credit Hold">
<field name="postpone_hold_until" <field groups="account.group_account_manager,account.group_account_user" name="postpone_hold_until" readonly="hold_bg == False and postpone_hold_until == False"/>
groups="account.group_account_manager,account.group_account_user"
attrs="{'readonly': [('hold_bg','=',False), ('postpone_hold_until','=',False)]}"/>
</group> </group>
</xpath> </xpath></field>
</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -1,18 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<record id="sale_order_form_inherit" model="ir.ui.view"> <record id="sale_order_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.sale_order.form</field> <field name="name">account_credit_hold.sale_order.form</field>
<field name="model">sale.order</field> <field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/> <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before"> <xpath expr="//div[@name='button_box']" position="before">
<field name="client_on_hold" invisible="True" /> <field invisible="True" name="client_on_hold"/>
<widget name="web_ribbon" title="Credit Hold" <widget bg_color="bg-danger" invisible="client_on_hold == False" name="web_ribbon" title="Credit Hold"/>
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/> </xpath></field>
</xpath>
</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -1,20 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<record id="stock_picking_form_inherit" model="ir.ui.view"> <record id="stock_picking_form_inherit" model="ir.ui.view">
<field name="name">account_credit_hold.stock_picking.form</field> <field name="name">account_credit_hold.stock_picking.form</field>
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/> <field name="inherit_id" ref="stock.view_picking_form"/>
<field name="priority" eval="8"/> <field eval="8" name="priority"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="before"> <xpath expr="//div[@name='button_box']" position="before">
<field name="client_on_hold" invisible="True" /> <field invisible="True" name="client_on_hold"/>
<widget name="web_ribbon" title="Credit Hold" <widget bg_color="bg-danger" invisible="client_on_hold == False" name="web_ribbon" title="Credit Hold"/>
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/> </xpath></field>
</xpath>
</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -18,7 +18,7 @@
# #
{ {
'name': 'Documents Portal Base', 'name': 'Documents Portal Base',
'version': '15.0.1.0.0', 'version': '17.0.1.0.0',
'summary': 'Adds documents to the front-end portal.', 'summary': 'Adds documents to the front-end portal.',
'category': 'Document Management', 'category': 'Document Management',
'author': 'Bemade Inc.', 'author': 'Bemade Inc.',

View file

View file

@ -0,0 +1,33 @@
#
# 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': 'Fix Quality Worksheet',
'version': '17.0.1.0.0',
'summary': 'Fix Quality worksheet bug from Odoo Enterprise',
'description': '',
'category': 'Quality Control',
'author': 'Bemade Inc.',
'website': 'http://www.bemade.org',
'license': 'OPL-1',
'depends': ['quality_control'],
'data': ['reports/worksheet_custom_report_templates.xml'],
'assets': {},
'installable': True,
'auto_install': True,
}

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<template id="worksheet_page" inherit_id="quality_control.worksheet_page">
<xpath expr="//span[@t-field='doc.result']" position="replace">
<span t-field="doc.measure"/>
</xpath>
</template>
</odoo>

View file

@ -55,6 +55,7 @@
'reports/worksheet_custom_report_templates.xml', 'reports/worksheet_custom_report_templates.xml',
'reports/worksheet_custom_reports.xml', 'reports/worksheet_custom_reports.xml',
'wizard/new_task_from_template.xml', 'wizard/new_task_from_template.xml',
'wizard/res_config_settings.xml',
], ],
'assets': { 'assets': {
'web.report_assets_common': [ 'web.report_assets_common': [

View file

@ -26,25 +26,5 @@
<!-- <field name="is_closed" eval="False"/>--> <!-- <field name="is_closed" eval="False"/>-->
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/> <field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
</record> </record>
<!-- Since the Field Service project has no_update="1" we use a workaround here -->
<function model="ir.model.data" name="write">
<function name="search" model="ir.model.data">
<value eval="[('name', '=', 'fsm_project'), ('module', '=', 'industry_fsm'), ('model', '=', 'project.project')]"/>
</function>
<value eval="{'noupdate': False}"/>
</function>
<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'))]"/>
<!-- 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">
<value eval="[('name', '=', 'fsm_project'), ('module', '=', 'industry_fsm'), ('model', '=', 'project.project')]"/>
</function>
<value eval="{'noupdate': True}"/>
</function>
</data> </data>
</odoo> </odoo>

View file

@ -8,3 +8,4 @@ from . import equipment_tag
from . import task from . import task
from . import res_partner from . import res_partner
from . import fsm_visit from . import fsm_visit
from . import res_company

View file

@ -0,0 +1,8 @@
from odoo import models, fields
class Company(models.Model):
_inherit = "res.company"
split_time_from_materials_on_service_work_orders = fields.Boolean(default=False)
create_default_fsm_visit = fields.Boolean(default=False)

View file

@ -115,7 +115,7 @@ class SaleOrderLine(models.Model):
vals['parent_id'] = parent and parent.id vals['parent_id'] = parent and parent.id
vals['user_ids'] = template.assignees.ids vals['user_ids'] = template.assignees.ids
vals['tag_ids'] = template.tags.ids vals['tag_ids'] = template.tags.ids
vals['planned_hours'] = template.planned_hours vals['allocated_hours'] = template.planned_hours
vals['sequence'] = template.sequence vals['sequence'] = template.sequence
if template.equipment_ids: if template.equipment_ids:
vals['equipment_ids'] = template.equipment_ids.ids vals['equipment_ids'] = template.equipment_ids.ids
@ -134,7 +134,7 @@ class SaleOrderLine(models.Model):
task.message_post(body=task_msg) task.message_post(body=task_msg)
if not task.equipment_ids and self.equipment_ids: if not task.equipment_ids and self.equipment_ids:
task.equipment_ids = self.equipment_ids.ids task.equipment_ids = self.equipment_ids.ids
task.planned_hours = self.task_duration task.allocated_hours = self.task_duration
return task return task
def _timesheet_service_generation(self): def _timesheet_service_generation(self):
@ -150,7 +150,7 @@ class SaleOrderLine(models.Model):
project_id = task_ids[0].project_id project_id = task_ids[0].project_id
line.visit_id.task_id = line._generate_task_for_visit_line(project_id) line.visit_id.task_id = line._generate_task_for_visit_line(project_id)
task_ids.write({'parent_id': line.visit_id.task_id.id}) task_ids.write({'parent_id': line.visit_id.task_id.id})
self.mapped('task_id').synchronize_name_fsm() self.task_id.filtered("is_fsm").synchronize_name_fsm()
def _generate_task_for_visit_line(self, project): def _generate_task_for_visit_line(self, project):
self.ensure_one() self.ensure_one()
@ -164,7 +164,7 @@ class SaleOrderLine(models.Model):
'partner_id': self.order_id.partner_shipping_id.id, 'partner_id': self.order_id.partner_shipping_id.id,
'visit_id': self.visit_id.id, 'visit_id': self.visit_id.id,
'date_deadline': self.visit_id.approx_date, 'date_deadline': self.visit_id.approx_date,
'planned_hours': self.task_duration, 'allocated_hours': self.task_duration,
'user_ids': False, # Force to empty or it uses the current user 'user_ids': False, # Force to empty or it uses the current user
}) })
return task return task

View file

@ -2,6 +2,7 @@ from odoo import fields, models, api, Command, _
from odoo.exceptions import ValidationError, UserError from odoo.exceptions import ValidationError, UserError
from odoo.osv import expression from odoo.osv import expression
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from odoo.addons.project.models.project_task import CLOSED_STATES
import re import re
@ -82,6 +83,15 @@ class Task(models.Model):
default=False, default=False,
) )
is_closed = fields.Boolean(
compute="_compute_is_closed",
)
def _compute_is_closed(self):
for rec in self:
rec.is_closed = rec.state in CLOSED_STATES
@api.model_create_multi @api.model_create_multi
def create(self, vals): def create(self, vals):
res = super().create(vals) res = super().create(vals)
@ -99,9 +109,9 @@ class Task(models.Model):
return res return res
def write(self, vals): def write(self, vals):
super().write(vals) res = super().write(vals)
if not self: # End recursion on empty RecordSet if not self: # End recursion on empty RecordSet
return return res
if 'propagate_assignment' in vals: if 'propagate_assignment' in vals:
# When a user sets propagate assignment, it should propagate that setting all the way down the chain # 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']}) self.child_ids.write({'propagate_assignment': vals['propagate_assignment']})
@ -110,6 +120,7 @@ class Task(models.Model):
# Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment # Here we use child_ids instead of _get_all_subtasks() so as to allow for setting propagate_assignment
# to false on a child task. # to false on a child task.
to_propagate.child_ids.write({'user_ids': vals['user_ids']}) to_propagate.child_ids.write({'user_ids': vals['user_ids']})
return res
@api.depends('sale_order_id') @api.depends('sale_order_id')
def _compute_relevant_order_lines(self): def _compute_relevant_order_lines(self):

View file

@ -90,7 +90,7 @@ class TaskTemplate(models.Model):
'parent_id': parent_id, 'parent_id': parent_id,
'user_ids': self.assignees.ids, 'user_ids': self.assignees.ids,
'tag_ids': self.tags.ids, 'tag_ids': self.tags.ids,
'planned_hours': self.planned_hours, 'allocated_hours': self.planned_hours,
'sequence': self.sequence, 'sequence': self.sequence,
'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False, 'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False,
'partner_id': project.partner_id and project.partner_id.id, 'partner_id': project.partner_id and project.partner_id.id,

View file

@ -3,8 +3,9 @@
<data> <data>
<template id="workorder_page_materials_table"> <template id="workorder_page_materials_table">
<t t-set="order_lines" <t t-set="order_lines"
t-value="doc.relevant_order_lines.filtered(lambda l: t-value="doc.relevant_order_lines"/>
l.product_id.type != 'service' and not l.is_downpayment and not l.visit_id)"/> <!--.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-if="order_lines">
<t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/> <t t-set="visit_lines" t-value="order_lines.mapped('visit_id')"/>
<t t-set="section_lines" <t t-set="section_lines"
@ -25,7 +26,7 @@
<t t-foreach="order_lines" t-as="line"> <t t-foreach="order_lines" t-as="line">
<t t-set="is_task" t-value="line == doc.sale_line_id"/> <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 ''"> <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"> <t t-if="not line.display_type and not line.is_downpayment">
<td><span t-field="line.name"/></td> <td><span t-field="line.name"/></td>
<td class="text-right"> <td class="text-right">
<span t-field="line.product_uom_qty"/> <span t-field="line.product_uom_qty"/>
@ -284,14 +285,14 @@
</t> </t>
</div> </div>
<div t-attf-class="{{('col-6' if report_type == 'pdf' else 'col-md-6 col-12') + ' mb-3'}}" <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"> t-if="doc.planned_date_begin or doc.date_deadline">
<div t-if="doc.planned_date_begin"><h6>Planned start: </h6></div> <div t-if="doc.planned_date_begin"><h6>Planned start: </h6></div>
<div class="mb-3"> <div class="mb-3">
<div t-out="doc.planned_date_begin.strftime('%Y-%m-%d %H:%M')"/> <div t-out="doc.planned_date_begin.strftime('%Y-%m-%d %H:%M')"/>
</div> </div>
<div t-if="doc.planned_date_end"><h6>Planned end: </h6></div> <div t-if="doc.date_deadline"><h6>Planned end: </h6></div>
<div class="mb-3"> <div class="mb-3">
<div t-out="doc.planned_date_end.strftime('%Y-%m-%d %H:%M')"/> <div t-out="doc.date_deadline.strftime('%Y-%m-%d %H:%M')"/>
</div> </div>
</div> </div>
</div> </div>
@ -444,7 +445,12 @@
<div class="page"> <div class="page">
<t t-call="bemade_fsm.workorder_page_info_block"/> <t t-call="bemade_fsm.workorder_page_info_block"/>
<t t-call="bemade_fsm.workorder_page_timesheet_entries"/> <t t-call="bemade_fsm.workorder_page_timesheet_entries"/>
<t t-if="doc.company_id.split_time_from_materials_on_service_work_orders">
<t t-call="bemade_fsm.workorder_page_materials_table"/> <t t-call="bemade_fsm.workorder_page_materials_table"/>
</t>
<t t-else="">
<t t-call="bemade_fsm.workorder_page_sale_order_table_with_pricing_and_labour"/>
</t>
<t t-call="bemade_fsm.workorder_equipment_summary"/> <t t-call="bemade_fsm.workorder_equipment_summary"/>
<t t-call="bemade_fsm.workorder_page_signature_block"/> <t t-call="bemade_fsm.workorder_page_signature_block"/>
<t t-call="bemade_fsm.workorder_page_tasks_table"/> <t t-call="bemade_fsm.workorder_page_tasks_table"/>
@ -461,14 +467,12 @@
</t> </t>
</t> </t>
</template> </template>
<!-- MD Commented out since this no longer matches the structure of industry_fsm at all --> <template id="worksheet_custom"
<!-- Make the front-end "Sign" page show our new template --> inherit_id="industry_fsm_report.worksheet_custom">
<!-- <template id="portal_my_worksheet" --> <xpath expr="//t[@t-call='industry_fsm_report.worksheet_custom_page']"
<!-- inherit_id="industry_fsm.portal_my_task"> --> position="replace">
<!-- <xpath expr="//div[@t-call='industry_fsm.worksheet_custom_page']" --> <div t-call="bemade_fsm.work_order_page"/>
<!-- position="replace"> --> </xpath>
<!-- <div t-call="bemade_fsm.work_order_page"/> --> </template>
<!-- </xpath> -->
<!-- </template> -->
</data> </data>
</odoo> </odoo>

View file

@ -1,16 +1,12 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<data> <data>
<record id="industry_fsm.task_custom_report" model="ir.actions.report"> <record id="industry_fsm_report.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 %s' % ( <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.planned_date_begin.strftime('%Y-%m-%d') if object.planned_date_begin else time.strftime('%Y-%m-%d'),
object.name object.name
) )
</field> </field>
<field name="report_type">qweb-pdf</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View file

@ -5,3 +5,4 @@ from . import test_equipment
from . import test_fsm_contact_setting from . import test_fsm_contact_setting
from . import test_fsm_visit from . import test_fsm_visit
from . import test_task from . import test_task
from . import test_task_report

View file

@ -4,6 +4,12 @@ from odoo import Command
@tagged("-at_install", "post_install") @tagged("-at_install", "post_install")
class BemadeFSMBaseTest(TransactionCase): class BemadeFSMBaseTest(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.user.groups_id += cls.env.ref("account.group_delivery_invoice_address")
cls.env.company.create_default_fsm_visit = True
@classmethod @classmethod
def _generate_project_manager_user(cls, name, login): def _generate_project_manager_user(cls, name, login):
@ -37,7 +43,11 @@ class BemadeFSMBaseTest(TransactionCase):
user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user') user_group_fsm_user = cls.env.ref('industry_fsm.group_fsm_user')
user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman') user_group_sales_user = cls.env.ref('sales_team.group_sale_salesman')
user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager') user_group_sales_manager = cls.env.ref('sales_team.group_sale_manager')
user_product_customer = cls.env.ref('customer_product_code.group_product_customer_code_user') # TODO: Split this out into a bemade_fsm_customer_product_code module if it's wanted
user_product_customer = cls.env.ref(
'customer_product_code.group_product_customer_code_user',
raise_if_not_found=False
)
group_ids = [user_group_employee.id, group_ids = [user_group_employee.id,
user_group_project_user.id, user_group_project_user.id,

View file

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

View file

@ -89,7 +89,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
so.action_confirm() so.action_confirm()
visit_task = visit.task_id visit_task = visit.task_id
self.assertEqual(visit_task.planned_hours, 8.0) self.assertEqual(visit_task.allocated_hours, 8.0)
def test_adding_visit_creates_one_sale_order_line(self): def test_adding_visit_creates_one_sale_order_line(self):
partner = self._generate_partner() partner = self._generate_partner()

View file

@ -19,7 +19,7 @@ class TestSalesOrder(BemadeFSMBaseTest):
task = sol.task_id task = sol.task_id
self.assertTrue(task) self.assertTrue(task)
self.assertTrue(task_template.name in task.name) self.assertTrue(task_template.name in task.name)
self.assertTrue(task_template.planned_hours == task.planned_hours) self.assertTrue(task_template.planned_hours == task.allocated_hours)
def test_task_template_tree_order_confirmation(self): def test_task_template_tree_order_confirmation(self):
partner = self._generate_partner() partner = self._generate_partner()

View file

@ -0,0 +1,38 @@
from .test_bemade_fsm_common import BemadeFSMBaseTest
from odoo.tests import Form
class TestTaskReport(BemadeFSMBaseTest):
def test_split_time_materials_setting(self):
settings = Form(self.env['res.config.settings'])
settings.module_bemade_fsm_separate_time_on_work_orders = True
settings.save()
so = self._generate_sale_order()
service_product = self._generate_product()
material_product = self._generate_product(
name="Material Product",
product_type='product',
service_tracking='no',
)
sol = self._generate_sale_order_line(sale_order=so, product=service_product)
sol2 = self._generate_sale_order_line(sale_order=so, product=material_product)
so.action_confirm()
task = sol.task_id
html_content = self.env['ir.actions.report']._render(
'industry_fsm_report.worksheet_custom',
[task.id],
)[0].decode('utf-8').split('\n')
strings_to_find = [
"<h2>Materials</h2>",
"<span>Material Product</span>"
]
for line in strings_to_find:
line_found = False
for html_line in html_content:
if line in html_line:
line_found = True
self.assertTrue(line_found, f"{line} should be in file.")

View file

@ -1 +1,2 @@
from . import new_task_from_template from . import new_task_from_template
from . import res_config_settings

View file

@ -0,0 +1,23 @@
from odoo import models, fields, api
class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"
module_bemade_fsm_separate_time_on_work_orders = fields.Boolean("Separate Time from Materials on Work Order")
module_bemade_fsm_create_default_fsm_visit = fields.Boolean("Create Default Visit for FSM Sales Orders")
def set_values(self):
super().set_values()
self.env.company.split_time_from_materials_on_service_work_orders = \
self.module_bemade_fsm_separate_time_on_work_orders
self.env.company.create_default_fsm_visit = self.module_bemade_fsm_create_default_fsm_visit
def get_values(self):
res = super().get_values()
res.update({
'module_bemade_fsm_separate_time_on_work_orders':
self.env.company.split_time_from_materials_on_service_work_orders,
'module_bemade_fsm_create_default_fsm_visit': self.env.company.create_default_fsm_visit
})
return res

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="res_config_settings_view_form" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.bemade.fsm</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="industry_fsm.res_config_settings_view_form"/>
<field name="arch" type="xml">
<setting name="module_industry_fsm_report" position="after">
<setting
name="module_bemade_fsm_separate_time_on_work_orders"
help="Separate blocks for materials and time on work order reports.">
<field name="module_bemade_fsm_separate_time_on_work_orders"/>
</setting>
<setting
name="module_bemade_fsm_create_default_fsm_visit"
help="Create a default FSM visit if a service SO doesn't already contain one at confirmation.">
<field name="module_bemade_fsm_create_default_fsm_visit"/>
</setting>
</setting>
</field>
</record>
</odoo>

View file

@ -18,7 +18,7 @@
# #
{ {
'name': 'Bemade Geo Distance', 'name': 'Bemade Geo Distance',
'version': '15.0.1.0.0', 'version': '17.0.1.0.0',
'summary': 'Utility module for calculating the distance driving between addresses.', 'summary': 'Utility module for calculating the distance driving between addresses.',
'description': """Module for calculating the driving distance between addresses using the Google Route API. 'description': """Module for calculating the driving distance between addresses using the Google Route API.
**IMPORTANT NOTE:** You must have an active Google Route API key configured in the Geo Localization **IMPORTANT NOTE:** You must have an active Google Route API key configured in the Geo Localization

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': 'Mailcow Integration', 'name': 'Mailcow Integration',
'version': '1.0.0', 'version': '17.0.1.0.1',
'category': 'Administration', 'category': 'Administration',
'summary': 'Module for integrating Mailcow email server with Odoo.', 'summary': 'Module for integrating Mailcow email server with Odoo.',
'description': """ 'description': """

View file

@ -0,0 +1,13 @@
from odoo import api, SUPERUSER_ID
def migrate(cr, version):
env = api.Environment(cr, SUPERUSER_ID, {})
settings = env['ir.config_parameter']
create_param = settings.get_param('mailcow.create_mailbox')
sync_param = settings.get_param('mailcow.sync_alias')
base_url = settings.get_param('mailcow.base_url')
api_key = settings.get_param('mailcow.api_key')
if (create_param or sync_param) and (not base_url or not api_key):
(create_param | sync_param).write({'value': False})

View file

@ -6,6 +6,7 @@ class MailAlias(models.Model):
mailcow_id = fields.One2many('mail.mailcow.alias', 'alias_id') mailcow_id = fields.One2many('mail.mailcow.alias', 'alias_id')
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
alias_list = super().create(vals_list) alias_list = super().create(vals_list)

View file

@ -53,7 +53,7 @@ class MailcowMailbox(models.Model):
Overridden create method to add a new mailbox entry on the Mailcow server for each user created in Odoo. Overridden create method to add a new mailbox entry on the Mailcow server for each user created in Odoo.
""" """
res_list = [] res = self.env['mail.mailcow.mailbox']
for vals in vals_list: for vals in vals_list:
password = secrets.token_hex(16) password = secrets.token_hex(16)
@ -81,10 +81,8 @@ class MailcowMailbox(models.Model):
self.api_request(endpoint, method='POST', data=data) self.api_request(endpoint, method='POST', data=data)
_logger.info(f"Mailbox {vals['local_part']}@{vals['domain']} has been created on Mailcow server") _logger.info(f"Mailbox {vals['local_part']}@{vals['domain']} has been created on Mailcow server")
res = super().create(vals) res |= super().create(vals)
res_list.append(res) return res
return res_list
def write(self, vals): def write(self, vals):
""" """
@ -141,8 +139,8 @@ class MailcowMailbox(models.Model):
self.api_request(endpoint, method='POST', data=data) self.api_request(endpoint, method='POST', data=data)
_logger.info(f'Mailbox for user {user.login} has been created on Mailcow server') _logger.info(f'Mailbox for user {user.login} has been created on Mailcow server')
pw_bundle = seld.env['password.bundle'].search([('name', '=', user.name)]) pw_bundle = self.env['password.bundle'].search([('name', '=', user.name)])
self.env['password.key'].create_mailbox_for_user(res) self.env['password.key'].create_mailbox_for_user(user)
return self.create({ return self.create({
'address': user.login, 'address': user.login,

View file

@ -1,4 +1,6 @@
from odoo import fields, models from odoo import fields, models, api, _
from odoo.exceptions import ValidationError
class ResConfigSettings(models.TransientModel): class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings' _inherit = 'res.config.settings'
@ -23,3 +25,8 @@ class ResConfigSettings(models.TransientModel):
string='Create Mailboxes in Mailcow', string='Create Mailboxes in Mailcow',
help='Auto create Mailboxes in Mailcow on creation in Odoo', help='Auto create Mailboxes in Mailcow on creation in Odoo',
config_parameter='mailcow.create_mailbox') config_parameter='mailcow.create_mailbox')
@api.constrains('mailcow_sync_alias', 'mailcow_auto_create')
def require_api_key_and_base_url_to_sync(self):
if not self.mailcow_api_key and self.mailcow_base_url:
raise ValidationError(_("You must set a base URL and API key to enable synchronization."))

View file

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

View file

@ -100,7 +100,7 @@ class SaleOrderLine(models.Model):
if is_order and self.qty_to_deliver == 0: if is_order and self.qty_to_deliver == 0:
return 0 return 0
elif is_order and self.qty_to_deliver > 0: elif is_order and self.qty_to_deliver > 0:
reserved = sum([m.reserved_availability for m in self.move_ids]) reserved = sum([q.reserved_quantity for q in self.move_ids.mapped('move_line_ids').mapped('quant_id')])
missing = self.qty_to_deliver - reserved missing = self.qty_to_deliver - reserved
if float_compare(missing, 0.0, if float_compare(missing, 0.0,
precision_rounding=self.product_uom.rounding) == 1: precision_rounding=self.product_uom.rounding) == 1:

View file

@ -7,12 +7,14 @@
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order"/> <field name="inherit_id" ref="sale_margin.sale_margin_sale_order"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="margin" position="replace"> <field name="margin" position="replace">
<field name="margin" invisible="True"/>
<field name="margin_actual" class="oe_inline"/> <field name="margin_actual" class="oe_inline"/>
</field> </field>
<label for="margin" position="replace"> <label for="margin" position="replace">
<label for="margin_actual" groups="base.group_user"/> <label for="margin_actual" groups="base.group_user"/>
</label> </label>
<field name="margin_percent" position="replace"> <field name="margin_percent" position="replace">
<field name="margin_percent" invisible="True"/>
<field name="margin_percent_actual" nolabel="1" class="oe_inline" <field name="margin_percent_actual" nolabel="1" class="oe_inline"
widget="percentage" groups="base.group_user"/> widget="percentage" groups="base.group_user"/>
</field> </field>
@ -24,6 +26,7 @@
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line"/> <field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="purchase_price" position="replace"> <field name="purchase_price" position="replace">
<field name="purchase_price" invisible="True"/>
<field name="purchase_price_actual" groups="base.group_user"/> <field name="purchase_price_actual" groups="base.group_user"/>
</field> </field>
</field> </field>
@ -34,12 +37,15 @@
<field name="model">sale.order</field> <field name="model">sale.order</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="purchase_price" position="replace"> <field name="purchase_price" position="replace">
<field name="purchase_price" invisible="True"/>
<field name="purchase_price_actual" optional="hide"/> <field name="purchase_price_actual" optional="hide"/>
</field> </field>
<field name="margin" position="replace"> <field name="margin" position="replace">
<field name="margin" invisible="True"/>
<field name="margin_actual" optional="hide"/> <field name="margin_actual" optional="hide"/>
</field> </field>
<field name="margin_percent" position="replace"> <field name="margin_percent" position="replace">
<field name="margin_percent" invisible="True"/>
<field name="margin_percent_actual" optional="hide" <field name="margin_percent_actual" optional="hide"
widget="percentage" groups="base.group_user"/> widget="percentage" groups="base.group_user"/>
</field> </field>

View file

@ -29,14 +29,14 @@
'author': 'Bemade Inc.', 'author': 'Bemade Inc.',
'website': 'https://www.bemade.org', 'website': 'https://www.bemade.org',
'license': 'OPL-1', 'license': 'OPL-1',
'depends': [ 'depends': ['sale',
'sale',
'account', 'account',
'bemade_partner_root_ancestor', 'bemade_partner_root_ancestor',
], ],
'data': [ 'data': [
'views/account_move_views.xml', 'views/account_move_views.xml',
'views/res_partner_views.xml' 'views/res_partner_views.xml',
'views/sale_order_views.xml',
], ],
'demo': [], 'demo': [],
'installable': True, 'installable': True,

View file

@ -29,6 +29,6 @@ class AccountMove(models.Model):
initial_subscribers = self.message_partner_ids.ids initial_subscribers = self.message_partner_ids.ids
final_subscribers = initial_subscribers + self.billing_contacts.ids final_subscribers = initial_subscribers + self.billing_contacts.ids
posted = super()._post() posted = super()._post()
self.message_unsubscribe([s.id for s in self.message_partner_ids if s not in initial_subscribers]) self.message_unsubscribe([s.id for s in self.message_partner_ids if s.id not in initial_subscribers])
self.message_subscribe(final_subscribers) self.message_subscribe(final_subscribers)
return posted return posted

View file

@ -63,7 +63,8 @@ class TestBillingContacts(TransactionCase):
'price_unit': cls.product.list_price, 'price_unit': cls.product.list_price,
'order_id': cls.sale_order.id, 'order_id': cls.sale_order.id,
'tax_id': False, 'tax_id': False,
'qty_delivered_manual': 2, 'qty_delivered': 2,
'qty_delivered_method': 'manual',
}) })
def test_billing_contacts_structure(self): def test_billing_contacts_structure(self):

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_sale_order_form_inherit" model="ir.ui.view">
<field name="name">view_sale_order_form_inherit</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<field name="partner_invoice_id" position="after">
<field name="partner_id" position="after">
<field name="billing_contacts"
widget="many2many_checkboxes"
domain="['|', '|', ('id', 'in', billing_contacts),
('parent_id', '=', partner_invoice_id),
('parent_id', 'parent_of', partner_invoice_id),
('is_company', '=', False)]"/>
</field>
</field>
</field>
</record>
</odoo>

View file

View file

@ -0,0 +1,39 @@
#
# Bemade Inc.
#
# Copyright (C) June 2023 Bemade Inc. (<https://www.bemade.org>).
# Author: mdurepos (Contact : it@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': 'Open Project Details',
'version': '17.0.0.0.1',
'summary': 'Open Project Details',
'description': """
This module adds the ability to view project details from the project form view.
""",
'category': 'Project',
'author': 'Bemade Inc.',
'website': 'https://www.bemade.org',
'license': 'OPL-1',
'depends': [
'project',
],
'data': [
# 'views/project_project_views.xml'
],
'demo': [],
'installable': True,
'auto_install': False,
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- Héritage de la vue Kanban des projets -->
<record id="project_view_project_kanban_inherit" model="ir.ui.view">
<field name="name">project.view.project.kanban.inherit</field>
<field name="model">project.project</field>
<field name="inherit_id" ref="project.view_project_kanban"/>
<field name="arch" type="xml">
<!-- Modification de la visibilité de la gestion des paramètres pour tous les utilisateurs -->
<div class="o_kanban_card_manage_settings row" groups="project.group_project_manager" position="attributes">
<attribute name="groups">base.group_user</attribute>
</div>
<xpath expr="//a[@name='action_view_kanban_project'][@type='edit']" position="replace">
<a class="dropdown-item" role="menuitem" type="open" name="action_view_kanban_project">Open</a>
</xpath>
</field>
</record>
</data>
</odoo>

View file

@ -10,7 +10,7 @@
You need to activate the auto create package feature on the delivery carrier to use this feature. You need to activate the auto create package feature on the delivery carrier to use this feature.
""", """,
'depends': [ 'depends': [
'delivery' 'stock_delivery'
], ],
'data': [ 'data': [
'views/stock_package_views.xml', 'views/stock_package_views.xml',

View file

@ -6,18 +6,12 @@
<field name="inherit_id" ref="stock.view_quant_package_form"/> <field name="inherit_id" ref="stock.view_quant_package_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="package_type_id" position="replace"> <field name="package_type_id" position="replace">
<field name="auto_create_package" attrs="{'invisible': 1}"/> <field invisible="1" name="auto_create_package"/>
<field name="package_type_id" <field invisible="package_type_id == False and auto_create_package == True" name="package_type_id"/>
attrs="{'invisible': [('package_type_id', '=', False),('auto_create_package', '=', True)]}"/> <field invisible="1" name="carrier_id"/>
<field name="carrier_id" attrs="{'invisible': 1}"/> <field invisible="1" name="provider"/>
<field name="provider" attrs="{'invisible': 1}"/> <field invisible="package_type_id != False or auto_create_package != True" name="length"/>
<field name="length" <field invisible="package_type_id != False or auto_create_package != True" name="width"/>
attrs="{'invisible': ['|', ('package_type_id', '!=', False), ('auto_create_package', '!=', True)]}"/> <field invisible="package_type_id != False or auto_create_package != True" name="height"/></field></field>
<field name="width"
attrs="{'invisible': ['|', ('package_type_id', '!=', False), ('auto_create_package', '!=', True)]}"/>
<field name="height"
attrs="{'invisible': ['|', ('package_type_id', '!=', False), ('auto_create_package', '!=', True)]}"/>
</field>
</field>
</record> </record>
</odoo> </odoo>

View file

@ -3,20 +3,14 @@
<record id="choose_delivery_package_view_form2" model="ir.ui.view"> <record id="choose_delivery_package_view_form2" model="ir.ui.view">
<field name="name">choose.delivery.package.form2</field> <field name="name">choose.delivery.package.form2</field>
<field name="model">choose.delivery.package</field> <field name="model">choose.delivery.package</field>
<field name="inherit_id" ref="delivery.choose_delivery_package_view_form"/> <field name="inherit_id" ref="stock_delivery.choose_delivery_package_view_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="delivery_package_type_id" position="replace"> <field name="delivery_package_type_id" position="replace">
<field name="auto_create_package" attrs="{'invisible': 1}"/> <field invisible="1" name="auto_create_package"/>
<field name="delivery_package_type_id" <field context="{'form_view_ref':'stock.stock_package_type_form'}" domain="[('package_carrier_type', '=', context.get('current_package_carrier_type', 'none'))]" invisible="auto_create_package == True" name="delivery_package_type_id"/>
domain="[('package_carrier_type', '=', context.get('current_package_carrier_type', 'none'))]" <field invisible="auto_create_package == False" name="length"/>
context="{'form_view_ref':'stock.stock_package_type_form'}" <field invisible="auto_create_package == False" name="width"/>
attrs="{'invisible': [('auto_create_package', '=', True)]}" /> <field invisible="auto_create_package == False" name="height"/>
<field name="length" attrs="{'invisible': [('auto_create_package', '=', False)]}"/> <field invisible="1" name="provider"/></field></field>
<field name="width" attrs="{'invisible': [('auto_create_package', '=', False)]}"/>
<field name="height" attrs="{'invisible': [('auto_create_package', '=', False)]}"/>
<field name="provider" attrs="{'invisible': 1}"/>
</field>
</field>
</record> </record>
</odoo> </odoo>

View file

@ -17,7 +17,7 @@
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml # Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list # for the full list
'category': 'Uncategorized', 'category': 'Uncategorized',
'version': '0.1', 'version': '17.0.1.0.0',
# any module necessary for this one to work correctly # any module necessary for this one to work correctly
'depends': [ 'depends': [
@ -26,9 +26,9 @@
# always loaded # always loaded
'data': [ 'data': [
# 'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/sale_order_views.xml', 'views/sale_order_views.xml',
'wizard/sale_order_alternative_views.xml', 'wizard/sale_order_duplication_wizard_view.xml',
], ],
# only loaded in demonstration mode # only loaded in demonstration mode
'demo': [ 'demo': [

View file

@ -1,3 +0,0 @@
# -*- coding: utf-8 -*-
from . import controllers

View file

@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class .repos/bemade-addons/bemadeQuotationAlternative(http.Controller):
# @http.route('/.repos/bemade-addons/bemade_quotation_alternative/.repos/bemade-addons/bemade_quotation_alternative', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/.repos/bemade-addons/bemade_quotation_alternative/.repos/bemade-addons/bemade_quotation_alternative/objects', auth='public')
# def list(self, **kw):
# return http.request.render('.repos/bemade-addons/bemade_quotation_alternative.listing', {
# 'root': '/.repos/bemade-addons/bemade_quotation_alternative/.repos/bemade-addons/bemade_quotation_alternative',
# 'objects': http.request.env['.repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative'].search([]),
# })
# @http.route('/.repos/bemade-addons/bemade_quotation_alternative/.repos/bemade-addons/bemade_quotation_alternative/objects/<model(".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('.repos/bemade-addons/bemade_quotation_alternative.object', {
# 'object': obj
# })

View file

@ -1,30 +0,0 @@
<odoo>
<data>
<!--
<record id="object0" model=".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model=".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model=".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model=".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model=".repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>

View file

@ -1,18 +0,0 @@
# -*- coding: utf-8 -*-
# from odoo import models, fields, api
# class .repos/bemade-addons/bemade_quotation_alternative(models.Model):
# _name = '.repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative'
# _description = '.repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative'
# name = fields.Char()
# value = fields.Integer()
# value2 = fields.Float(compute="_value_pc", store=True)
# description = fields.Text()
#
# @api.depends('value')
# def _value_pc(self):
# for record in self:
# record.value2 = float(record.value) / 100

View file

@ -1,17 +1,13 @@
from odoo import fields, models, api from odoo import fields, models, api
class ModelName(models.Model): class SaleOrder(models.Model):
_inherit = 'sale.order' _inherit = 'sale.order'
def action_create_alternative(self): def action_duplicate_order(self):
return { self.ensure_one()
'name': 'Create Alternative', action = self.env.ref('bemade_quotation_alternative.sale_order_duplication_wizard_action').read()[0]
'type': 'ir.actions.act_window', action['context'] = {
'res_model': 'bemade.quotation.alternative', 'default_original_order_id': self.id,
'view_mode': 'form',
'target': 'new',
'context': {
'default_sale_order_id': self.id,
}
} }
return action

View file

@ -1,2 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative,.repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative,model_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative,base.group_user,1,1,1,1 bemade_quotation_alternative.access_sale_order_duplication_wizard,access_sale_order_duplication_wizard,bemade_quotation_alternative.model_sale_order_duplication_wizard,base.group_user,1,1,1,1
bemade_quotation_alternative.access_sale_order_line_duplication_wizard,access_sale_order_line_duplication_wizard,bemade_quotation_alternative.model_sale_order_line_duplication_wizard,base.group_user,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative bemade_quotation_alternative.access_sale_order_duplication_wizard .repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative access_sale_order_duplication_wizard model_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative bemade_quotation_alternative.model_sale_order_duplication_wizard base.group_user 1 1 1 1
3 bemade_quotation_alternative.access_sale_order_line_duplication_wizard access_sale_order_line_duplication_wizard bemade_quotation_alternative.model_sale_order_line_duplication_wizard base.group_user 1 1 1 1

View file

@ -1,18 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<data> <data>
<!-- explicit list view definition --> <record id="view_sale_order_form_inherit" model="ir.ui.view">
<field name="name">sale.order.form.inherit</field>
<record model="ir.ui.view" id="bemade_quotation_alternative_form">
<field name="name">bemade_quotation_alternative form</field>
<field name="model">sale.order</field> <field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/> <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<header position="inside"> <header>
<button name="action_create_alternative" type="object" string="Create Alternative" class="oe_highlight" /> <button class="oe_highlight" invisible="state != 'draft'" name="action_duplicate_order" string="Duplicate Order" type="object"/>
</header> </header></field>
</field>
</record> </record>
<!-- explicit list view definition -->
<!-- <record model="ir.ui.view" id="bemade_quotation_alternative_form">-->
<!-- <field name="name">bemade_quotation_alternative form</field>-->
<!-- <field name="model">sale.order</field>-->
<!-- <field name="inherit_id" ref="sale.view_order_form"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <header position="inside">-->
<!-- <button name="action_create_alternative" type="object" string="Create Alternative" class="oe_highlight" />-->
<!-- </header>-->
<!--</field>-->
<!-- </record>-->
<!-- actions opening views on models --> <!-- actions opening views on models -->
<!-- <!--
<record model="ir.actions.act_window" id=".repos/bemade-addons/bemade_quotation_alternative.action_window"> <record model="ir.actions.act_window" id=".repos/bemade-addons/bemade_quotation_alternative.action_window">
@ -21,23 +29,19 @@
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
</record> </record>
--> -->
<!-- server action to the one above --> <!-- server action to the one above -->
<!-- <!--
<record model="ir.actions.server" id=".repos/bemade-addons/bemade_quotation_alternative.action_server"> <record model="ir.actions.server" id=".repos/bemade-addons/bemade_quotation_alternative.action_server">
<field name="name">.repos/bemade-addons/bemade_quotation_alternative server</field> <field name="name">.repos/bemade-addons/bemade_quotation_alternative server</field>
<field name="model_id" ref="model_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative"/> <field name="model_id" ref="model_.repos/bemade-addons/bemade_quotation_alternative_.repos/bemade-addons/bemade_quotation_alternative"/>
<field name="state">code</field> <field name="state">code</field>
<field name="code"> <field name="code">action = {
action = {
"type": "ir.actions.act_window", "type": "ir.actions.act_window",
"view_mode": "tree,form", "view_mode": "tree,form",
"res_model": model._name, "res_model": model._name,
} }</field>
</field>
</record> </record>
--> -->
<!-- Top menu item --> <!-- Top menu item -->
<!-- <!--
<menuitem name=".repos/bemade-addons/bemade_quotation_alternative" id=".repos/bemade-addons/bemade_quotation_alternative.menu_root"/> <menuitem name=".repos/bemade-addons/bemade_quotation_alternative" id=".repos/bemade-addons/bemade_quotation_alternative.menu_root"/>

View file

@ -1,24 +0,0 @@
<odoo>
<data>
<!--
<template id="listing">
<ul>
<li t-foreach="objects" t-as="object">
<a t-attf-href="#{ root }/objects/#{ object.id }">
<t t-esc="object.display_name"/>
</a>
</li>
</ul>
</template>
<template id="object">
<h1><t t-esc="object.display_name"/></h1>
<dl>
<t t-foreach="object._fields" t-as="field">
<dt><t t-esc="field"/></dt>
<dd><t t-esc="object[field]"/></dd>
</t>
</dl>
</template>
-->
</data>
</odoo>

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import sale_order_alternative from . import sale_order_duplication_wizard
from . import sale_oder_line_duplication_wizard

View file

@ -0,0 +1,9 @@
from odoo import models, fields
class SaleOrderLineDuplicationWizard(models.TransientModel):
_name = 'sale.order.line.duplication.wizard'
_description = 'Wizard for selecting sale order lines to duplicate'
wizard_id = fields.Many2one('sale.order.duplication.wizard', required=True, ondelete='cascade', string="Wizard")
sale_order_line_id = fields.Many2one('sale.order.line', string="Sale Order Line", required=True)
to_duplicate = fields.Boolean(string="Duplicate?", default=True)

View file

@ -1,59 +0,0 @@
from odoo import fields, models, api
import string
class AlternativeQuotation(models.TransientModel):
_name = 'sale.order.alternative'
_description = 'Quotation Alternative'
name = fields.Char()
original_sale_order_id = fields.Many2one('sale.order')
clone_all_lines = fields.Boolean('Clone all lines', default=True)
origin = fields.Text('Why this alternative ?')
internal_note = fields.Text('Internal Note')
def action_create_alternative(self):
name = self.original_sale_order_id.name
if name and name[-1] not in string.ascii_letters:
self.original_sale_order_id.name = name + "A"
name = self.name + "B"
else:
# Assuming 'all_names' is a list of all names
# Add a domain to filter names starting with 'name' (less the last letter)
all_names = [record.name for record in self.env['sale.order'].search([('name', 'like', name[:-1] + '%')])]
if name in all_names:
# Get the index of the current name in the list
index = all_names.index(name)
# If this is not the last name in the list, get the next one
if index < len(all_names) - 1:
name = all_names[index + 1]
else:
# If this is the last name, just add 'B' to it
name = name + "B"
new_quot = quot.original_sale_order_id.copy({
'name': quot.name,
'origin': quot.original_sale_order_id.name,
'alternative_sale_order_id': quot.original_sale_order_id.id,
'alternative': True,
'alternative_origine': quot.origine,
'alternative_internal_note': quot.internal_note,
# # Lines and line based computes
# order_line = fields.One2many(
# comodel_name='sale.order.alternative.line',
# inverse_name='order_id',
# string="Order Lines",
# copy=True, auto_join=True)
#
# class AlternativeQuotation(models.TransientModel):
# _name = 'sale.order.alternative.line'
# # _inherit = 'sale.order.line'
# _description = 'Quotation Alternative Line'
#
# order_id = fields.Many2one(
# comodel_name='sale.order.alternative',
# string="Alternative Quotation Reference",
# required=True, ondelete='cascade', index=True, copy=False)

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sale_order_alternative_form_view" model="ir.ui.view">
<field name="name">bemade_quotation_alternative.sale_order_alternative.form</field>
<field name="model">sale.order.alternative</field>
<field name="arch" type="xml">
<form string="Alternative Quotation">
<sheet>
<div class="oe_title">
<label for="original_sale_order_id"/>
<h1>
<field name="original_sale_order_id" placeholder="Original"/>
</h1>
</div>
<group>
<group>
<field name="order_line"/>
</group>
<group>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- <record id="ModelName_act_window" model="ir.actions.act_window">-->
<!-- <field name="name">ModelTitle</field>-->
<!-- <field name="type">ir.actions.act_window</field>-->
<!-- <field name="res_model">ProjectName.ModelName</field>-->
<!-- <field name="view_type">form</field>-->
<!-- <field name="view_mode">tree,form</field>-->
<!-- <field name="help" type="html">-->
<!-- <p class="oe_view_nocontent_create">-->
<!-- There is no examples click here to add new ModelTitle.-->
<!-- </p>-->
<!-- </field>-->
<!-- </record>-->
<!-- <menuitem name="ModelTitle" id="ModelName_menu" parent="your_root_menu" action="ModelName_act_window"/>-->
</data>
</odoo>

View file

@ -0,0 +1,78 @@
from odoo import models, fields, api
class SaleOrderDuplicationWizard(models.TransientModel):
_name = 'sale.order.duplication.wizard'
_description = 'Wizard for duplicating a sale order'
original_order_id = fields.Many2one('sale.order', string='Original Order', required=True)
new_quot = fields.Char(string='New Quotation Name', compute='_compute_new_quot', store=True)
duplicate_all_lines = fields.Boolean(string='Duplicate All Lines?', default=True)
lines_to_duplicate = fields.One2many(
'sale.order.line.duplication.wizard', 'wizard_id',
string="Lines to Duplicate",
context={'default_original_order_id': original_order_id},
)
purpose = fields.Text(string='Purpose')
note = fields.Html(string='Note')
@api.model
def default_get(self, fields_list):
res = super(SaleOrderDuplicationWizard, self).default_get(fields_list)
if 'default_original_order_id' in self.env.context:
original_order_id = self.env.context['default_original_order_id']
original_order = self.env['sale.order'].browse(original_order_id)
lines_vals = []
for line in original_order.order_line:
lines_vals.append((0, 0, {'sale_order_line_id': line.id}))
res.update({
'lines_to_duplicate': lines_vals,
'purpose': original_order.purpose if 'purpose' in fields_list else '',
'note': original_order.note if 'note' in fields_list else '',
})
return res
def action_duplicate_order(self):
self.ensure_one()
# Duplication de la commande de vente
new_order = self.original_order_id.copy({
'purpose': self.purpose,
'note': self.note,
# Assurez-vous que 'new_quot' est défini correctement dans votre modèle
'name': self.new_quot,
})
if not self.duplicate_all_lines:
new_order.order_line.unlink()
for line_wiz in self.lines_to_duplicate.filtered('to_duplicate'):
line_wiz.sale_order_line_id.copy({'order_id': new_order.id})
# Préparation et envoi des messages de notification dans le chatter
user_name = self.env.user.name
now = fields.Datetime.now()
# Message pour la commande originale
original_msg_body = f"A new quotation <a href='#' data-oe-model='sale.order' data-oe-id='{new_order.id}'>#{new_order.name}</a> created by {user_name} duplicating this Quotation."
self.original_order_id.message_post(body=original_msg_body)
# Message pour la nouvelle commande dupliquée
new_msg_body = f"This quotation has been created by {user_name} duplicating the original Quotation <a href='#' data-oe-model='sale.order' data-oe-id='{self.original_order_id.id}'>#{self.original_order_id.name}</a>."
new_order.message_post(body=new_msg_body)
return {
'type': 'ir.actions.act_window',
'name': 'Duplicated Order',
'res_model': 'sale.order',
'res_id': new_order.id,
'view_mode': 'form',
'target': 'current',
}
@api.depends('original_order_id')
def _compute_new_quot(self):
for rec in self:
original_order_name = rec.original_order_id.name.split('-')[
0] if '-' in rec.original_order_id.name else rec.original_order_id.name
other_quotes = self.env['sale.order'].search([('name', 'like', original_order_name + '%')])
rec.new_quot = original_order_name + '-REV' + str(len(other_quotes))

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="sale_order_duplication_wizard_form_view" model="ir.ui.view">
<field name="name">sale.order.duplication.wizard.form</field>
<field name="model">sale.order.duplication.wizard</field>
<field name="arch" type="xml">
<form string="Duplicate Sale Order">
<group>
<group>
<field invisible="1" name="original_order_id"/>
<field invisible="1" name="new_quot"/>
<field name="purpose"/>
</group>
<group>
<field name="duplicate_all_lines"/>
</group>
</group>
<field invisible="duplicate_all_lines == True" name="lines_to_duplicate">
<tree editable="bottom">
<field name="to_duplicate"/>
<field name="sale_order_line_id" options="{'no_create': True}"/>
</tree></field>
<field name="note"/>
<footer>
<button class="btn-primary" name="action_duplicate_order" string="Duplicate" type="object"/>
<button class="btn-default" special="cancel" string="Cancel"/>
</footer>
</form></field>
</record>
<record id="sale_order_duplication_wizard_action" model="ir.actions.act_window">
<field name="name">Duplicate Sale Order</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sale.order.duplication.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
</data>
</odoo>

View file

@ -1,5 +1,6 @@
{ {
'version': '17.0.0.1', 'name': 'Bemade Search Supplier Code',
'version': '17.0.1.0.1',
'summary': 'Search for products by supplier code', 'summary': 'Search for products by supplier code',
'sequence': 10, 'sequence': 10,
'description': """ 'description': """

View file

@ -11,7 +11,8 @@
'website': 'https://www.bemade.org', 'website': 'https://www.bemade.org',
"depends": [ "depends": [
'sale', 'sale',
'purchase' 'purchase',
'stock',
], ],
"data": [ "data": [
], ],

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record id="res_partner_view_form_sports_teams" model="ir.ui.view"> <record id="res_partner_view_form_sports_teams" model="ir.ui.view">
<field name="name">res.partner.view.form.sports.teams</field> <field name="name">res.partner.view.form.sports.teams</field>
@ -7,14 +7,9 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<page name="contact_addresses" position="after"> <page name="contact_addresses" position="after">
<page name="Teams"> <page name="Teams">
<field name="owned_team_ids" <field invisible="is_company == False" name="owned_team_ids" string="Teams"/>
attrs="{'invisible': [('is_company', '=', False)]}" <field invisible="is_company == True" name="teams_served_ids" string="Teams Served"/>
string="Teams"/>
<field name="teams_served_ids"
attrs="{'invisible': [('is_company', '=', True)]}"
string="Teams Served"/>
</page> </page>
</page> </page></field>
</field>
</record> </record>
</odoo> </odoo>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record id="teams_view_search" model="ir.ui.view"> <record id="teams_view_search" model="ir.ui.view">
<field name="name">teams.view.search</field> <field name="name">teams.view.search</field>
@ -9,21 +9,16 @@
<field name="parent_id" string="Parent Organization"/> <field name="parent_id" string="Parent Organization"/>
<field name="head_coach_id"/> <field name="head_coach_id"/>
<field name="head_therapist_id"/> <field name="head_therapist_id"/>
<filter name="groupby_parent_id" <filter context="{'group_by': 'parent_id'}" name="groupby_parent_id"/>
context="{'group_by': 'parent_id'}"/> <filter context="{'group_by': 'head_coach_id'}" name="groupby_head_coach_id"/>
<filter name="groupby_head_coach_id" <filter context="{'group_by': 'head_therapist_id'}" name="groupby_head_therapist_id"/>
context="{'group_by': 'head_coach_id'}"/> </search></field>
<filter name="groupby_head_therapist_id"
context="{'group_by': 'head_therapist_id'}"/>
</search>
</field>
</record> </record>
<record id="sports_team_view_kanban" model="ir.ui.view"> <record id="sports_team_view_kanban" model="ir.ui.view">
<field name="name">sports.team.view.kanban</field> <field name="name">sports.team.view.kanban</field>
<field name="model">sports.team</field> <field name="model">sports.team</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<kanban default_group_by="parent_id" default_order="name" <kanban archivable="false" default_group_by="parent_id" default_order="name" group_create="false" group_delete="false">
group_create="false" group_delete="false" archivable="false">
<field name="name"/> <field name="name"/>
<field name="parent_id"/> <field name="parent_id"/>
<field name="head_coach_name"/> <field name="head_coach_name"/>
@ -35,7 +30,9 @@
<div t-attf-class="oe_kanban_content oe_kanban_global_click"> <div t-attf-class="oe_kanban_content oe_kanban_global_click">
<div> <div>
<strong class="o_kanban_record_title"> <strong class="o_kanban_record_title">
<span><field name="name"/></span> <span>
<field name="name"/>
</span>
</strong> </strong>
</div> </div>
<div> <div>
@ -44,16 +41,21 @@
</span> </span>
</div> </div>
<div> <div>
<span>Head coach: <field <span>
name="head_coach_name"/></span> Head coach:
<field name="head_coach_name"/>
</span>
</div> </div>
<div> <div>
<span>Head therapist: <field <span>
name="head_therapist_name"/></span> Head therapist:
<field name="head_therapist_name"/>
</span>
</div> </div>
<div class="o_kanban_record_bottom"> <div class="o_kanban_record_bottom">
<div class="oe_kanban_bottom_left"> <div class="oe_kanban_bottom_left">
<span class="text-success">Healthy: <span class="text-success">
Healthy:
<field name="healthy_count"/> <field name="healthy_count"/>
</span> </span>
</div> </div>
@ -67,8 +69,7 @@
</div> </div>
</t> </t>
</templates> </templates>
</kanban> </kanban></field>
</field>
</record> </record>
<record id="sports_team_view_list" model="ir.ui.view"> <record id="sports_team_view_list" model="ir.ui.view">
<field name="name">sports.team.view.list</field> <field name="name">sports.team.view.list</field>
@ -81,19 +82,22 @@
<field name="head_therapist_id"/> <field name="head_therapist_id"/>
<field name="player_count"/> <field name="player_count"/>
<field name="injured_count"/> <field name="injured_count"/>
</tree> </tree></field>
</field>
</record> </record>
<record id="sports_team_view_form" model="ir.ui.view"> <record id="sports_team_view_form" model="ir.ui.view">
<field name="name">sports.team.view.form</field> <field name="name">sports.team.view.form</field>
<field name="model">sports.team</field> <field name="model">sports.team</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<field name="id" invisible="1"/> <field invisible="1" name="id"/>
<header></header> <header/>
<sheet> <sheet>
<div class="oe_button_box"></div> <div class="oe_button_box"/>
<div class="oe_title"><h1><field name="name"/></h1></div> <div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group string="Team Information"> <group string="Team Information">
<group> <group>
<field name="parent_id"/> <field name="parent_id"/>
@ -101,36 +105,22 @@
<field name="injured_count"/> <field name="injured_count"/>
</group> </group>
<group> <group>
<field name="staff_ids"> <field name="staff_ids"><tree>
<tree>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="name"/> <field name="name"/>
<field name="role"/> <field name="role"/>
<field name="mobile" widget="phone"/> <field name="mobile" widget="phone"/>
<field name="has_portal_access" invisible="1"/> <field invisible="1" name="has_portal_access"/>
<field name="partner_id" invisible="1"/> <field invisible="1" name="partner_id"/>
<button name="action_grant_portal_access" <button class="oe_highlight" groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system" icon="fa-user-plus" invisible="has_portal_access == True" name="action_grant_portal_access" title="Grant Portal Access" type="object"/>
type="object" icon="fa-user-plus" <button class="oe_highlight" groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system" icon="fa-ban" invisible="has_portal_access == False" name="action_revoke_portal_access" title="Revoke Portal Access" type="object"/>
class="oe_highlight" </tree></field>
attrs="{'invisible': [('has_portal_access', '=', True)]}"
title="Grant Portal Access"
groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system"/>
<button name="action_revoke_portal_access" type="object" icon="fa-ban"
class="oe_highlight"
attrs="{'invisible': [('has_portal_access', '=', False)]}"
title="Revoke Portal Access"
groups="bemade_sports_clinic.group_sports_clinic_admin,base.group_system"/>
</tree>
</field>
</group> </group>
</group> </group>
<separator string="Players"/> <separator string="Players"/>
<field name="patient_ids" nolabel="1" <field context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}" name="patient_ids" nolabel="1"/>
context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}"/>
</sheet> </sheet>
</form> </form></field>
</field>
</record> </record>
<record id="sports_team_staff_view_form" model="ir.ui.view"> <record id="sports_team_staff_view_form" model="ir.ui.view">
<field name="name">sports.team.staff.view.form</field> <field name="name">sports.team.staff.view.form</field>
@ -150,7 +140,6 @@
</group> </group>
</group> </group>
</sheet> </sheet>
</form> </form></field>
</field>
</record> </record>
</odoo> </odoo>

View file

@ -0,0 +1,41 @@
#
# Bemade Inc.
#
# Copyright (C) June 2023 Bemade Inc. (<https://www.bemade.org>).
# Author: mdurepos (Contact : it@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': 'Show Currency in total',
'version': '17.0.0.0.1',
'summary': 'Show Currency in total',
'description': """
This module adds the ability to view currency in total in all pdf.
""",
'category': 'Account',
'author': 'Bemade Inc.',
'website': 'https://www.bemade.org',
'license': 'OPL-1',
'depends': [
'sale',
'purchase',
'account'
],
'data': [
'views/total_template.xml'
],
'demo': [],
'installable': True,
'auto_install': False,
}

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<data>
<!-- Héritage de la vue pour ajouter la devise à côté du total -->
<template id="document_tax_totals_inherit" inherit_id="account.document_tax_totals">
<xpath expr="//tr[contains(@class, 'o_total')]/td[1]/strong" position="after">
<!-- Add the currency symbol in parentheses next to "Total" -->
<t t-if="tax_totals['currency_symbol']">
<span> (<t t-esc="tax_totals['currency_symbol']"/>)</span>
</t>
</xpath>
</template>
</data>
</odoo>

1
bemade_utils/__init__.py Normal file
View file

@ -0,0 +1 @@
from odoo.addons.bemade_utils.tools import patch_test

View file

@ -0,0 +1,33 @@
#
# 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': 'Bemade App Utilities',
'version': '17.0.1.0.0',
'summary': 'Utilities commonly used in Bemade modules.',
'description': 'Adds utilities such as an annotation for patching tests when modifying Odoo behaviour.',
'category': 'Technical',
'author': 'Bemade Inc.',
'website': 'http://www.bemade.org',
'license': 'OPL-1',
'depends': [],
'data': [],
'assets': {},
'installable': True,
'auto_install': False
}

View file

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

View file

@ -0,0 +1,16 @@
from odoo.tests import TransactionCase
from addons.bemade_utils import patch_test
class TestA(TransactionCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
def test_method_a(self):
self.assertFalse(True)
@patch_test(TestA.test_method_a)
def test_redefining_test(self):
self.assertTrue(True)

View file

@ -0,0 +1,2 @@
from .test import patch_test

View file

@ -0,0 +1,28 @@
import importlib
from functools import wraps
def patch_test(original_method):
"""
A decorator that patches an Odoo test method with a new one.
The original_method is a direct reference to the method to be patched.
"""
def decorator(new_method):
@wraps(new_method)
def wrapper(*args, **kwargs):
return new_method(*args, **kwargs)
# Extract module and class names
module_name = original_method.__module__
class_name = original_method.__qualname__.split('.')[0]
# Import the module
module = importlib.import_module(module_name)
# Get the class
cls = getattr(module, class_name)
# Replace the original method with the new one
setattr(cls, original_method.__name__, wrapper)
return wrapper
return decorator