Merge branch 'Bemade:17.0' into 17.0
This commit is contained in:
commit
0058948551
75 changed files with 719 additions and 475 deletions
|
|
@ -44,8 +44,8 @@ class Partner(models.Model):
|
|||
rec.hold_bg = False
|
||||
rec.message_post(body=_('Credit hold lifted.'))
|
||||
|
||||
def _execute_followup_partner(self):
|
||||
res = super()._execute_followup_partner()
|
||||
def _execute_followup_partner(self, options=None):
|
||||
res = super()._execute_followup_partner(options)
|
||||
if self.followup_status == 'in_need_of_action':
|
||||
if self.followup_line_id.account_hold:
|
||||
self.action_credit_hold()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<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="model">account_followup.followup.line</field>
|
||||
|
|
@ -9,44 +8,26 @@
|
|||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='send_email']" position="before">
|
||||
<field name="account_hold"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</xpath></field>
|
||||
</record>
|
||||
|
||||
<record id="customer_statements_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">customer.statements.form.view.inherit</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="account_followup.customer_statements_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//button[last()]" position="after">
|
||||
<field name="hold_bg" invisible="1"/>
|
||||
<button
|
||||
string="Credit Hold"
|
||||
type="object"
|
||||
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>
|
||||
<field invisible="1" name="hold_bg"/>
|
||||
<button class="button btn-secondary" invisible="hold_bg == True" name="action_credit_hold" string="Credit Hold" type="object"/>
|
||||
<button class="button btn-secondary" invisible="hold_bg == False" name="action_lift_credit_hold" string="Lift Credit Hold" type="object"/>
|
||||
</xpath></field>
|
||||
</record>
|
||||
|
||||
<record id="action_credit_hold" model="ir.actions.server">
|
||||
<field name="name">action_credit_hold</field>
|
||||
<field name="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="state">code</field>
|
||||
<field name="code">
|
||||
records.action_credit_hold()
|
||||
</field>
|
||||
<field name="code">records.action_credit_hold()</field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="res_partner_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.res_partner.form</field>
|
||||
<field name="model">res.partner</field>
|
||||
<field name="inherit_id" ref="base.view_partner_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="hold_bg" invisible="True" />
|
||||
<field name="on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('on_hold','=',False)]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field invisible="True" name="hold_bg"/>
|
||||
<field invisible="True" name="on_hold"/>
|
||||
<widget bg_color="bg-danger" invisible="on_hold == False" name="web_ribbon" title="Credit Hold"/>
|
||||
</xpath></field>
|
||||
</record>
|
||||
<record id="view_partner_property_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.view_partner_property_form</field>
|
||||
|
|
@ -21,13 +18,10 @@
|
|||
<field name="inherit_id" ref="account.view_partner_property_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//group[@name='banks']" position="before">
|
||||
<group string="Credit Hold" >
|
||||
<field name="postpone_hold_until"
|
||||
groups="account.group_account_manager,account.group_account_user"
|
||||
attrs="{'readonly': [('hold_bg','=',False), ('postpone_hold_until','=',False)]}"/>
|
||||
<group string="Credit Hold">
|
||||
<field groups="account.group_account_manager,account.group_account_user" name="postpone_hold_until" readonly="hold_bg == False and postpone_hold_until == False"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</xpath></field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="sale_order_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.sale_order.form</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="inherit_id" ref="sale.view_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="client_on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
|
||||
</xpath>
|
||||
</field>
|
||||
<field invisible="True" name="client_on_hold"/>
|
||||
<widget bg_color="bg-danger" invisible="client_on_hold == False" name="web_ribbon" title="Credit Hold"/>
|
||||
</xpath></field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,20 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
|
||||
<record id="stock_picking_form_inherit" model="ir.ui.view">
|
||||
<field name="name">account_credit_hold.stock_picking.form</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="priority" eval="8"/>
|
||||
<field eval="8" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//div[@name='button_box']" position="before">
|
||||
<field name="client_on_hold" invisible="True" />
|
||||
<widget name="web_ribbon" title="Credit Hold"
|
||||
bg_color="bg-danger" attrs="{'invisible': [('client_on_hold','=',False)] }"/>
|
||||
|
||||
</xpath>
|
||||
</field>
|
||||
<field invisible="True" name="client_on_hold"/>
|
||||
<widget bg_color="bg-danger" invisible="client_on_hold == False" name="web_ribbon" title="Credit Hold"/>
|
||||
</xpath></field>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
{
|
||||
'name': 'Documents Portal Base',
|
||||
'version': '15.0.1.0.0',
|
||||
'version': '17.0.1.0.0',
|
||||
'summary': 'Adds documents to the front-end portal.',
|
||||
'category': 'Document Management',
|
||||
'author': 'Bemade Inc.',
|
||||
|
|
|
|||
0
bemade_fix_quality_report/__init__.py
Normal file
0
bemade_fix_quality_report/__init__.py
Normal file
33
bemade_fix_quality_report/__manifest__.py
Normal file
33
bemade_fix_quality_report/__manifest__.py
Normal 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,
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -55,6 +55,7 @@
|
|||
'reports/worksheet_custom_report_templates.xml',
|
||||
'reports/worksheet_custom_reports.xml',
|
||||
'wizard/new_task_from_template.xml',
|
||||
'wizard/res_config_settings.xml',
|
||||
],
|
||||
'assets': {
|
||||
'web.report_assets_common': [
|
||||
|
|
|
|||
|
|
@ -26,25 +26,5 @@
|
|||
<!-- <field name="is_closed" eval="False"/>-->
|
||||
<field name="project_ids" eval="[(4,ref('industry_fsm.fsm_project'))]"/>
|
||||
</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>
|
||||
</odoo>
|
||||
|
|
@ -8,3 +8,4 @@ from . import equipment_tag
|
|||
from . import task
|
||||
from . import res_partner
|
||||
from . import fsm_visit
|
||||
from . import res_company
|
||||
8
bemade_fsm/models/res_company.py
Normal file
8
bemade_fsm/models/res_company.py
Normal 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)
|
||||
|
|
@ -115,7 +115,7 @@ class SaleOrderLine(models.Model):
|
|||
vals['parent_id'] = parent and parent.id
|
||||
vals['user_ids'] = template.assignees.ids
|
||||
vals['tag_ids'] = template.tags.ids
|
||||
vals['planned_hours'] = template.planned_hours
|
||||
vals['allocated_hours'] = template.planned_hours
|
||||
vals['sequence'] = template.sequence
|
||||
if template.equipment_ids:
|
||||
vals['equipment_ids'] = template.equipment_ids.ids
|
||||
|
|
@ -134,7 +134,7 @@ class SaleOrderLine(models.Model):
|
|||
task.message_post(body=task_msg)
|
||||
if not task.equipment_ids and self.equipment_ids:
|
||||
task.equipment_ids = self.equipment_ids.ids
|
||||
task.planned_hours = self.task_duration
|
||||
task.allocated_hours = self.task_duration
|
||||
return task
|
||||
|
||||
def _timesheet_service_generation(self):
|
||||
|
|
@ -150,7 +150,7 @@ class SaleOrderLine(models.Model):
|
|||
project_id = task_ids[0].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})
|
||||
self.mapped('task_id').synchronize_name_fsm()
|
||||
self.task_id.filtered("is_fsm").synchronize_name_fsm()
|
||||
|
||||
def _generate_task_for_visit_line(self, project):
|
||||
self.ensure_one()
|
||||
|
|
@ -164,7 +164,7 @@ class SaleOrderLine(models.Model):
|
|||
'partner_id': self.order_id.partner_shipping_id.id,
|
||||
'visit_id': self.visit_id.id,
|
||||
'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
|
||||
})
|
||||
return task
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from odoo import fields, models, api, Command, _
|
|||
from odoo.exceptions import ValidationError, UserError
|
||||
from odoo.osv import expression
|
||||
from collections import defaultdict, namedtuple
|
||||
from odoo.addons.project.models.project_task import CLOSED_STATES
|
||||
import re
|
||||
|
||||
|
||||
|
|
@ -82,6 +83,15 @@ class Task(models.Model):
|
|||
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
|
||||
def create(self, vals):
|
||||
res = super().create(vals)
|
||||
|
|
@ -99,9 +109,9 @@ class Task(models.Model):
|
|||
return res
|
||||
|
||||
def write(self, vals):
|
||||
super().write(vals)
|
||||
res = super().write(vals)
|
||||
if not self: # End recursion on empty RecordSet
|
||||
return
|
||||
return res
|
||||
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']})
|
||||
|
|
@ -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
|
||||
# to false on a child task.
|
||||
to_propagate.child_ids.write({'user_ids': vals['user_ids']})
|
||||
return res
|
||||
|
||||
@api.depends('sale_order_id')
|
||||
def _compute_relevant_order_lines(self):
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ class TaskTemplate(models.Model):
|
|||
'parent_id': parent_id,
|
||||
'user_ids': self.assignees.ids,
|
||||
'tag_ids': self.tags.ids,
|
||||
'planned_hours': self.planned_hours,
|
||||
'allocated_hours': self.planned_hours,
|
||||
'sequence': self.sequence,
|
||||
'equipment_ids': [Command.set(self.equipment_ids.ids)] if self.equipment_ids else False,
|
||||
'partner_id': project.partner_id and project.partner_id.id,
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
<data>
|
||||
<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-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"
|
||||
|
|
@ -25,7 +26,7 @@
|
|||
<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">
|
||||
<t t-if="not line.display_type and not line.is_downpayment">
|
||||
<td><span t-field="line.name"/></td>
|
||||
<td class="text-right">
|
||||
<span t-field="line.product_uom_qty"/>
|
||||
|
|
@ -284,14 +285,14 @@
|
|||
</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">
|
||||
t-if="doc.planned_date_begin or doc.date_deadline">
|
||||
<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 t-if="doc.date_deadline"><h6>Planned end: </h6></div>
|
||||
<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>
|
||||
|
|
@ -444,7 +445,12 @@
|
|||
<div class="page">
|
||||
<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_materials_table"/>
|
||||
<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 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_page_signature_block"/>
|
||||
<t t-call="bemade_fsm.workorder_page_tasks_table"/>
|
||||
|
|
@ -461,14 +467,12 @@
|
|||
</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.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> -->
|
||||
<template id="worksheet_custom"
|
||||
inherit_id="industry_fsm_report.worksheet_custom">
|
||||
<xpath expr="//t[@t-call='industry_fsm_report.worksheet_custom_page']"
|
||||
position="replace">
|
||||
<div t-call="bemade_fsm.work_order_page"/>
|
||||
</xpath>
|
||||
</template>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<odoo>
|
||||
<data>
|
||||
<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>
|
||||
<record id="industry_fsm_report.task_custom_report" model="ir.actions.report">
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -5,3 +5,4 @@ from . import test_equipment
|
|||
from . import test_fsm_contact_setting
|
||||
from . import test_fsm_visit
|
||||
from . import test_task
|
||||
from . import test_task_report
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@ from odoo import Command
|
|||
|
||||
@tagged("-at_install", "post_install")
|
||||
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
|
||||
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_sales_user = cls.env.ref('sales_team.group_sale_salesman')
|
||||
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,
|
||||
user_group_project_user.id,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,3 @@ 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
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ class FSMVisitTest(BemadeFSMBaseTest):
|
|||
so.action_confirm()
|
||||
|
||||
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):
|
||||
partner = self._generate_partner()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class TestSalesOrder(BemadeFSMBaseTest):
|
|||
task = sol.task_id
|
||||
self.assertTrue(task)
|
||||
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):
|
||||
partner = self._generate_partner()
|
||||
|
|
|
|||
38
bemade_fsm/tests/test_task_report.py
Normal file
38
bemade_fsm/tests/test_task_report.py
Normal 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.")
|
||||
|
|
@ -1 +1,2 @@
|
|||
from . import new_task_from_template
|
||||
from . import res_config_settings
|
||||
|
|
|
|||
23
bemade_fsm/wizard/res_config_settings.py
Normal file
23
bemade_fsm/wizard/res_config_settings.py
Normal 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
|
||||
22
bemade_fsm/wizard/res_config_settings.xml
Normal file
22
bemade_fsm/wizard/res_config_settings.xml
Normal 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>
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
{
|
||||
'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.',
|
||||
'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
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Mailcow Integration',
|
||||
'version': '1.0.0',
|
||||
'version': '17.0.1.0.1',
|
||||
'category': 'Administration',
|
||||
'summary': 'Module for integrating Mailcow email server with Odoo.',
|
||||
'description': """
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
@ -6,6 +6,7 @@ class MailAlias(models.Model):
|
|||
|
||||
mailcow_id = fields.One2many('mail.mailcow.alias', 'alias_id')
|
||||
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
alias_list = super().create(vals_list)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
||||
res_list = []
|
||||
res = self.env['mail.mailcow.mailbox']
|
||||
|
||||
for vals in vals_list:
|
||||
password = secrets.token_hex(16)
|
||||
|
|
@ -81,10 +81,8 @@ class MailcowMailbox(models.Model):
|
|||
self.api_request(endpoint, method='POST', data=data)
|
||||
_logger.info(f"Mailbox {vals['local_part']}@{vals['domain']} has been created on Mailcow server")
|
||||
|
||||
res = super().create(vals)
|
||||
res_list.append(res)
|
||||
|
||||
return res_list
|
||||
res |= super().create(vals)
|
||||
return res
|
||||
|
||||
def write(self, vals):
|
||||
"""
|
||||
|
|
@ -141,8 +139,8 @@ class MailcowMailbox(models.Model):
|
|||
self.api_request(endpoint, method='POST', data=data)
|
||||
_logger.info(f'Mailbox for user {user.login} has been created on Mailcow server')
|
||||
|
||||
pw_bundle = seld.env['password.bundle'].search([('name', '=', user.name)])
|
||||
self.env['password.key'].create_mailbox_for_user(res)
|
||||
pw_bundle = self.env['password.bundle'].search([('name', '=', user.name)])
|
||||
self.env['password.key'].create_mailbox_for_user(user)
|
||||
|
||||
return self.create({
|
||||
'address': user.login,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
from odoo import fields, models
|
||||
from odoo import fields, models, api, _
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ResConfigSettings(models.TransientModel):
|
||||
_inherit = 'res.config.settings'
|
||||
|
|
@ -23,3 +25,8 @@ class ResConfigSettings(models.TransientModel):
|
|||
string='Create Mailboxes in Mailcow',
|
||||
help='Auto create Mailboxes in Mailcow on creation in Odoo',
|
||||
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."))
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
#
|
||||
{
|
||||
'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.',
|
||||
'description': """Adds a new method for calculating """,
|
||||
'author': 'Bemade Inc.',
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class SaleOrderLine(models.Model):
|
|||
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])
|
||||
reserved = sum([q.reserved_quantity for q in self.move_ids.mapped('move_line_ids').mapped('quant_id')])
|
||||
missing = self.qty_to_deliver - reserved
|
||||
if float_compare(missing, 0.0,
|
||||
precision_rounding=self.product_uom.rounding) == 1:
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin" invisible="True"/>
|
||||
<field name="margin_actual" class="oe_inline"/>
|
||||
</field>
|
||||
<label for="margin" position="replace">
|
||||
<label for="margin_actual" groups="base.group_user"/>
|
||||
</label>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent" invisible="True"/>
|
||||
<field name="margin_percent_actual" nolabel="1" class="oe_inline"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
|
|
@ -24,6 +26,7 @@
|
|||
<field name="inherit_id" ref="sale_margin.sale_margin_sale_order_line"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price" invisible="True"/>
|
||||
<field name="purchase_price_actual" groups="base.group_user"/>
|
||||
</field>
|
||||
</field>
|
||||
|
|
@ -34,12 +37,15 @@
|
|||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<field name="purchase_price" position="replace">
|
||||
<field name="purchase_price" invisible="True"/>
|
||||
<field name="purchase_price_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin" position="replace">
|
||||
<field name="margin" invisible="True"/>
|
||||
<field name="margin_actual" optional="hide"/>
|
||||
</field>
|
||||
<field name="margin_percent" position="replace">
|
||||
<field name="margin_percent" invisible="True"/>
|
||||
<field name="margin_percent_actual" optional="hide"
|
||||
widget="percentage" groups="base.group_user"/>
|
||||
</field>
|
||||
|
|
|
|||
|
|
@ -29,14 +29,14 @@
|
|||
'author': 'Bemade Inc.',
|
||||
'website': 'https://www.bemade.org',
|
||||
'license': 'OPL-1',
|
||||
'depends': [
|
||||
'sale',
|
||||
'account',
|
||||
'bemade_partner_root_ancestor',
|
||||
],
|
||||
'depends': ['sale',
|
||||
'account',
|
||||
'bemade_partner_root_ancestor',
|
||||
],
|
||||
'data': [
|
||||
'views/account_move_views.xml',
|
||||
'views/res_partner_views.xml'
|
||||
'views/res_partner_views.xml',
|
||||
'views/sale_order_views.xml',
|
||||
],
|
||||
'demo': [],
|
||||
'installable': True,
|
||||
|
|
|
|||
|
|
@ -29,6 +29,6 @@ class AccountMove(models.Model):
|
|||
initial_subscribers = self.message_partner_ids.ids
|
||||
final_subscribers = initial_subscribers + self.billing_contacts.ids
|
||||
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)
|
||||
return posted
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ class TestBillingContacts(TransactionCase):
|
|||
'price_unit': cls.product.list_price,
|
||||
'order_id': cls.sale_order.id,
|
||||
'tax_id': False,
|
||||
'qty_delivered_manual': 2,
|
||||
'qty_delivered': 2,
|
||||
'qty_delivered_method': 'manual',
|
||||
})
|
||||
|
||||
def test_billing_contacts_structure(self):
|
||||
|
|
|
|||
20
bemade_multiple_billing_contacts/views/sale_order_views.xml
Normal file
20
bemade_multiple_billing_contacts/views/sale_order_views.xml
Normal 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>
|
||||
0
bemade_open_project_details/__init__.py
Normal file
0
bemade_open_project_details/__init__.py
Normal file
39
bemade_open_project_details/__manifest__.py
Normal file
39
bemade_open_project_details/__manifest__.py
Normal 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,
|
||||
}
|
||||
20
bemade_open_project_details/views/project_project_views.xml
Normal file
20
bemade_open_project_details/views/project_project_views.xml
Normal 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>
|
||||
|
|
@ -10,7 +10,7 @@
|
|||
You need to activate the auto create package feature on the delivery carrier to use this feature.
|
||||
""",
|
||||
'depends': [
|
||||
'delivery'
|
||||
'stock_delivery'
|
||||
],
|
||||
'data': [
|
||||
'views/stock_package_views.xml',
|
||||
|
|
|
|||
|
|
@ -6,18 +6,12 @@
|
|||
<field name="inherit_id" ref="stock.view_quant_package_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<field name="package_type_id" position="replace">
|
||||
<field name="auto_create_package" attrs="{'invisible': 1}"/>
|
||||
<field name="package_type_id"
|
||||
attrs="{'invisible': [('package_type_id', '=', False),('auto_create_package', '=', True)]}"/>
|
||||
<field name="carrier_id" attrs="{'invisible': 1}"/>
|
||||
<field name="provider" attrs="{'invisible': 1}"/>
|
||||
<field name="length"
|
||||
attrs="{'invisible': ['|', ('package_type_id', '!=', False), ('auto_create_package', '!=', True)]}"/>
|
||||
<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>
|
||||
<field invisible="1" name="auto_create_package"/>
|
||||
<field invisible="package_type_id == False and auto_create_package == True" name="package_type_id"/>
|
||||
<field invisible="1" name="carrier_id"/>
|
||||
<field invisible="1" name="provider"/>
|
||||
<field invisible="package_type_id != False or auto_create_package != True" name="length"/>
|
||||
<field invisible="package_type_id != False or auto_create_package != True" name="width"/>
|
||||
<field invisible="package_type_id != False or auto_create_package != True" name="height"/></field></field>
|
||||
</record>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -3,20 +3,14 @@
|
|||
<record id="choose_delivery_package_view_form2" model="ir.ui.view">
|
||||
<field name="name">choose.delivery.package.form2</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="delivery_package_type_id" position="replace">
|
||||
<field name="auto_create_package" attrs="{'invisible': 1}"/>
|
||||
<field name="delivery_package_type_id"
|
||||
domain="[('package_carrier_type', '=', context.get('current_package_carrier_type', 'none'))]"
|
||||
context="{'form_view_ref':'stock.stock_package_type_form'}"
|
||||
attrs="{'invisible': [('auto_create_package', '=', True)]}" />
|
||||
<field name="length" attrs="{'invisible': [('auto_create_package', '=', False)]}"/>
|
||||
<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>
|
||||
<field invisible="1" name="auto_create_package"/>
|
||||
<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"/>
|
||||
<field invisible="auto_create_package == False" name="length"/>
|
||||
<field invisible="auto_create_package == False" name="width"/>
|
||||
<field invisible="auto_create_package == False" name="height"/>
|
||||
<field invisible="1" name="provider"/></field></field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import wizard
|
||||
from . import models
|
||||
from . import models
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||
# for the full list
|
||||
'category': 'Uncategorized',
|
||||
'version': '0.1',
|
||||
'version': '17.0.1.0.0',
|
||||
|
||||
# any module necessary for this one to work correctly
|
||||
'depends': [
|
||||
|
|
@ -26,9 +26,9 @@
|
|||
|
||||
# always loaded
|
||||
'data': [
|
||||
# 'security/ir.model.access.csv',
|
||||
'security/ir.model.access.csv',
|
||||
'views/sale_order_views.xml',
|
||||
'wizard/sale_order_alternative_views.xml',
|
||||
'wizard/sale_order_duplication_wizard_view.xml',
|
||||
],
|
||||
# only loaded in demonstration mode
|
||||
'demo': [
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import controllers
|
||||
|
|
@ -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
|
||||
# })
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import sale_order
|
||||
from . import sale_order
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -1,17 +1,13 @@
|
|||
from odoo import fields, models, api
|
||||
|
||||
|
||||
class ModelName(models.Model):
|
||||
class SaleOrder(models.Model):
|
||||
_inherit = 'sale.order'
|
||||
|
||||
def action_create_alternative(self):
|
||||
return {
|
||||
'name': 'Create Alternative',
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'bemade.quotation.alternative',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {
|
||||
'default_sale_order_id': self.id,
|
||||
}
|
||||
}
|
||||
def action_duplicate_order(self):
|
||||
self.ensure_one()
|
||||
action = self.env.ref('bemade_quotation_alternative.sale_order_duplication_wizard_action').read()[0]
|
||||
action['context'] = {
|
||||
'default_original_order_id': self.id,
|
||||
}
|
||||
return action
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
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,58 +1,62 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data>
|
||||
<!-- 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 -->
|
||||
<!--
|
||||
<data>
|
||||
<record id="view_sale_order_form_inherit" model="ir.ui.view">
|
||||
<field name="name">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">
|
||||
<header>
|
||||
<button class="oe_highlight" invisible="state != 'draft'" name="action_duplicate_order" string="Duplicate Order" type="object"/>
|
||||
</header></field>
|
||||
</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 -->
|
||||
<!--
|
||||
<record model="ir.actions.act_window" id=".repos/bemade-addons/bemade_quotation_alternative.action_window">
|
||||
<field name="name">.repos/bemade-addons/bemade_quotation_alternative window</field>
|
||||
<field name="res_model">.repos/bemade-addons/bemade_quotation_alternative..repos/bemade-addons/bemade_quotation_alternative</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</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">
|
||||
<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="state">code</field>
|
||||
<field name="code">
|
||||
action = {
|
||||
<field name="code">action = {
|
||||
"type": "ir.actions.act_window",
|
||||
"view_mode": "tree,form",
|
||||
"res_model": model._name,
|
||||
}
|
||||
</field>
|
||||
}</field>
|
||||
</record>
|
||||
-->
|
||||
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
<!-- Top menu item -->
|
||||
<!--
|
||||
<menuitem name=".repos/bemade-addons/bemade_quotation_alternative" id=".repos/bemade-addons/bemade_quotation_alternative.menu_root"/>
|
||||
-->
|
||||
<!-- menu categories -->
|
||||
<!--
|
||||
<!-- menu categories -->
|
||||
<!--
|
||||
<menuitem name="Menu 1" id=".repos/bemade-addons/bemade_quotation_alternative.menu_1" parent=".repos/bemade-addons/bemade_quotation_alternative.menu_root"/>
|
||||
<menuitem name="Menu 2" id=".repos/bemade-addons/bemade_quotation_alternative.menu_2" parent=".repos/bemade-addons/bemade_quotation_alternative.menu_root"/>
|
||||
-->
|
||||
<!-- actions -->
|
||||
<!--
|
||||
<!-- actions -->
|
||||
<!--
|
||||
<menuitem name="List" id=".repos/bemade-addons/bemade_quotation_alternative.menu_1_list" parent=".repos/bemade-addons/bemade_quotation_alternative.menu_1"
|
||||
action=".repos/bemade-addons/bemade_quotation_alternative.action_window"/>
|
||||
<menuitem name="Server to list" id=".repos/bemade-addons/bemade_quotation_alternative" parent=".repos/bemade-addons/bemade_quotation_alternative.menu_2"
|
||||
action=".repos/bemade-addons/bemade_quotation_alternative.action_server"/>
|
||||
-->
|
||||
</data>
|
||||
</odoo>
|
||||
</data>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import sale_order_alternative
|
||||
from . import sale_order_duplication_wizard
|
||||
from . import sale_oder_line_duplication_wizard
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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>
|
||||
|
|
@ -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))
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
'sequence': 10,
|
||||
'description': """
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
'website': 'https://www.bemade.org',
|
||||
"depends": [
|
||||
'sale',
|
||||
'purchase'
|
||||
'purchase',
|
||||
'stock',
|
||||
],
|
||||
"data": [
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="res_partner_view_form_sports_teams" model="ir.ui.view">
|
||||
<field name="name">res.partner.view.form.sports.teams</field>
|
||||
|
|
@ -7,14 +7,9 @@
|
|||
<field name="arch" type="xml">
|
||||
<page name="contact_addresses" position="after">
|
||||
<page name="Teams">
|
||||
<field name="owned_team_ids"
|
||||
attrs="{'invisible': [('is_company', '=', False)]}"
|
||||
string="Teams"/>
|
||||
<field name="teams_served_ids"
|
||||
attrs="{'invisible': [('is_company', '=', True)]}"
|
||||
string="Teams Served"/>
|
||||
<field invisible="is_company == False" name="owned_team_ids" string="Teams"/>
|
||||
<field invisible="is_company == True" name="teams_served_ids" string="Teams Served"/>
|
||||
</page>
|
||||
</page>
|
||||
</field>
|
||||
</page></field>
|
||||
</record>
|
||||
</odoo>
|
||||
</odoo>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="teams_view_search" model="ir.ui.view">
|
||||
<field name="name">teams.view.search</field>
|
||||
|
|
@ -9,21 +9,16 @@
|
|||
<field name="parent_id" string="Parent Organization"/>
|
||||
<field name="head_coach_id"/>
|
||||
<field name="head_therapist_id"/>
|
||||
<filter name="groupby_parent_id"
|
||||
context="{'group_by': 'parent_id'}"/>
|
||||
<filter name="groupby_head_coach_id"
|
||||
context="{'group_by': 'head_coach_id'}"/>
|
||||
<filter name="groupby_head_therapist_id"
|
||||
context="{'group_by': 'head_therapist_id'}"/>
|
||||
</search>
|
||||
</field>
|
||||
<filter context="{'group_by': 'parent_id'}" name="groupby_parent_id"/>
|
||||
<filter context="{'group_by': 'head_coach_id'}" name="groupby_head_coach_id"/>
|
||||
<filter context="{'group_by': 'head_therapist_id'}" name="groupby_head_therapist_id"/>
|
||||
</search></field>
|
||||
</record>
|
||||
<record id="sports_team_view_kanban" model="ir.ui.view">
|
||||
<field name="name">sports.team.view.kanban</field>
|
||||
<field name="model">sports.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban default_group_by="parent_id" default_order="name"
|
||||
group_create="false" group_delete="false" archivable="false">
|
||||
<kanban archivable="false" default_group_by="parent_id" default_order="name" group_create="false" group_delete="false">
|
||||
<field name="name"/>
|
||||
<field name="parent_id"/>
|
||||
<field name="head_coach_name"/>
|
||||
|
|
@ -35,7 +30,9 @@
|
|||
<div t-attf-class="oe_kanban_content oe_kanban_global_click">
|
||||
<div>
|
||||
<strong class="o_kanban_record_title">
|
||||
<span><field name="name"/></span>
|
||||
<span>
|
||||
<field name="name"/>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
|
|
@ -44,16 +41,21 @@
|
|||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Head coach: <field
|
||||
name="head_coach_name"/></span>
|
||||
<span>
|
||||
Head coach:
|
||||
<field name="head_coach_name"/>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>Head therapist: <field
|
||||
name="head_therapist_name"/></span>
|
||||
<span>
|
||||
Head therapist:
|
||||
<field name="head_therapist_name"/>
|
||||
</span>
|
||||
</div>
|
||||
<div class="o_kanban_record_bottom">
|
||||
<div class="oe_kanban_bottom_left">
|
||||
<span class="text-success">Healthy:
|
||||
<span class="text-success">
|
||||
Healthy:
|
||||
<field name="healthy_count"/>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -67,8 +69,7 @@
|
|||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</kanban></field>
|
||||
</record>
|
||||
<record id="sports_team_view_list" model="ir.ui.view">
|
||||
<field name="name">sports.team.view.list</field>
|
||||
|
|
@ -81,19 +82,22 @@
|
|||
<field name="head_therapist_id"/>
|
||||
<field name="player_count"/>
|
||||
<field name="injured_count"/>
|
||||
</tree>
|
||||
</field>
|
||||
</tree></field>
|
||||
</record>
|
||||
<record id="sports_team_view_form" model="ir.ui.view">
|
||||
<field name="name">sports.team.view.form</field>
|
||||
<field name="model">sports.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<field name="id" invisible="1"/>
|
||||
<header></header>
|
||||
<field invisible="1" name="id"/>
|
||||
<header/>
|
||||
<sheet>
|
||||
<div class="oe_button_box"></div>
|
||||
<div class="oe_title"><h1><field name="name"/></h1></div>
|
||||
<div class="oe_button_box"/>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group string="Team Information">
|
||||
<group>
|
||||
<field name="parent_id"/>
|
||||
|
|
@ -101,36 +105,22 @@
|
|||
<field name="injured_count"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="staff_ids">
|
||||
<tree>
|
||||
<field name="staff_ids"><tree>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name"/>
|
||||
<field name="role"/>
|
||||
<field name="mobile" widget="phone"/>
|
||||
<field name="has_portal_access" invisible="1"/>
|
||||
<field name="partner_id" invisible="1"/>
|
||||
<button name="action_grant_portal_access"
|
||||
type="object" icon="fa-user-plus"
|
||||
class="oe_highlight"
|
||||
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>
|
||||
<field invisible="1" name="has_portal_access"/>
|
||||
<field invisible="1" name="partner_id"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
</tree></field>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Players"/>
|
||||
<field name="patient_ids" nolabel="1"
|
||||
context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}"/>
|
||||
<field context="{'tree_view_ref': 'bemade_sports_clinic.sports_patient_view_list_embedded'}" name="patient_ids" nolabel="1"/>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</form></field>
|
||||
</record>
|
||||
<record id="sports_team_staff_view_form" model="ir.ui.view">
|
||||
<field name="name">sports.team.staff.view.form</field>
|
||||
|
|
@ -150,7 +140,6 @@
|
|||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</form></field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
|
|||
41
bemade_total_show_currency/__manifest__.py
Normal file
41
bemade_total_show_currency/__manifest__.py
Normal 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,
|
||||
}
|
||||
14
bemade_total_show_currency/views/total_template.xml
Normal file
14
bemade_total_show_currency/views/total_template.xml
Normal 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
1
bemade_utils/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from odoo.addons.bemade_utils.tools import patch_test
|
||||
33
bemade_utils/__manifest__.py
Normal file
33
bemade_utils/__manifest__.py
Normal 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
|
||||
}
|
||||
1
bemade_utils/tests/__init__.py
Normal file
1
bemade_utils/tests/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from . import test_patching_test
|
||||
16
bemade_utils/tests/test_patching_test.py
Normal file
16
bemade_utils/tests/test_patching_test.py
Normal 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)
|
||||
2
bemade_utils/tools/__init__.py
Normal file
2
bemade_utils/tools/__init__.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
from .test import patch_test
|
||||
|
||||
28
bemade_utils/tools/test.py
Normal file
28
bemade_utils/tools/test.py
Normal 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
|
||||
Loading…
Reference in a new issue