Pembuatan Business category untuk semuanya
This commit is contained in:
Vendored
+1
-3
@@ -1,5 +1,3 @@
|
||||
{
|
||||
"python.defaultInterpreterPath": "C:/odoo14c/python/python.exe",
|
||||
"python.debugpyPath": "C:/odoo14c/python/Lib/site-packages",
|
||||
"debugpy.adapterPython": "C:/odoo14c/python/python.exe"
|
||||
"python.defaultInterpreterPath": "C:/odoo14c/python/python.exe"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
# Verifikasi Report Business Category
|
||||
|
||||
## 1) Upgrade Modul
|
||||
Jalankan:
|
||||
|
||||
- `upgrade_business_category_reports.bat`
|
||||
|
||||
## 2) Verifikasi Menu Report
|
||||
|
||||
### Sales
|
||||
- Menu: **Sales > Orders > Sales by Category**
|
||||
- Cek tersedia view: Tree, Pivot, Graph
|
||||
- Cek default grouping: `business_category_id`
|
||||
|
||||
### Purchase
|
||||
- Menu: **Purchase > Purchases > Purchases by Category**
|
||||
- Cek tersedia view: Tree, Pivot, Graph
|
||||
- Cek default grouping: `business_category_id`
|
||||
|
||||
### Expense
|
||||
- Menu: **Expenses > Expenses > Expenses by Category**
|
||||
- Cek tersedia view: Tree, Pivot, Graph
|
||||
- Cek default grouping: `business_category_id`
|
||||
|
||||
### Inventory
|
||||
- Menu: **Inventory > Operations > Inventory by Category**
|
||||
- Cek tersedia view: Tree, Pivot, Graph
|
||||
- Cek default grouping: `business_category_id`
|
||||
|
||||
### CRM Pipeline
|
||||
- Menu: **CRM > Reporting > Pipeline by Category**
|
||||
- Cek domain otomatis: hanya `type = opportunity`
|
||||
- Cek view: Tree, Pivot, Graph, Kanban, Form
|
||||
|
||||
### CRM Activity
|
||||
- Menu: **CRM > Reporting > Activity History**
|
||||
- Cek default grouping: `business_category_id`
|
||||
- Cek view: Tree, Form, Pivot, Graph
|
||||
|
||||
## 3) Verifikasi Keamanan Akses
|
||||
- Login sebagai user category A: pastikan data category B tidak muncul di report.
|
||||
- Login sebagai user category B: pastikan data category A tidak muncul di report.
|
||||
- Login sysadmin: boleh melihat semua sesuai company.
|
||||
|
||||
## 4) Validasi Cepat Runtime
|
||||
Jalankan script:
|
||||
|
||||
- `python test_business_category_access_runtime.py`
|
||||
|
||||
Expected: semua skenario lintas category = **deny**.
|
||||
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
import xmlrpc.client
|
||||
|
||||
URL = "http://localhost:8070"
|
||||
DB = "kanjabung_MRP"
|
||||
USER = "admin"
|
||||
PWD = "admin"
|
||||
|
||||
TARGETS = [
|
||||
"grt_crm_business_category",
|
||||
"grt_sales_business_category",
|
||||
"grt_purchase_business_category",
|
||||
"grt_expense_business_category",
|
||||
"grt_inventory_business_category",
|
||||
]
|
||||
|
||||
common = xmlrpc.client.ServerProxy("%s/xmlrpc/2/common" % URL)
|
||||
uid = common.authenticate(DB, USER, PWD, {})
|
||||
if not uid:
|
||||
raise SystemExit("AUTH_FAILED")
|
||||
|
||||
models = xmlrpc.client.ServerProxy("%s/xmlrpc/2/object" % URL)
|
||||
mods = models.execute_kw(
|
||||
DB,
|
||||
uid,
|
||||
PWD,
|
||||
"ir.module.module",
|
||||
"search_read",
|
||||
[[("name", "in", TARGETS)]],
|
||||
{"fields": ["name", "state", "installed_version"], "order": "name asc"},
|
||||
)
|
||||
|
||||
for m in mods:
|
||||
print("%s | %s | %s" % (m.get("name"), m.get("state"), m.get("installed_version")))
|
||||
@@ -18,6 +18,7 @@ This enables different staging flow per business category through team pipelines
|
||||
"views/assets.xml",
|
||||
"views/crm_activity_history_views.xml",
|
||||
"views/crm_business_category_views.xml",
|
||||
"views/crm_pipeline_report_views.xml",
|
||||
"views/crm_lead_views.xml",
|
||||
"views/crm_menu_views.xml",
|
||||
"views/crm_stage_views.xml",
|
||||
|
||||
@@ -3,29 +3,29 @@
|
||||
<record id="crm_lead_business_category_rule_user" model="ir.rule">
|
||||
<field name="name">CRM Lead by allowed business category</field>
|
||||
<field name="model_id" ref="crm.model_crm_lead"/>
|
||||
<field name="domain_force">[('business_category_id', 'in', user.allowed_business_category_ids.ids)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('crm.group_use_lead'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_team_business_category_rule_user" model="ir.rule">
|
||||
<field name="name">CRM Team by allowed business category</field>
|
||||
<field name="model_id" ref="sales_team.model_crm_team"/>
|
||||
<field name="domain_force">[('business_category_id', 'in', user.allowed_business_category_ids.ids)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('crm.group_use_lead'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
<field name="perm_create" eval="1"/>
|
||||
<field name="perm_unlink" eval="1"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
<field name="perm_create" eval="0"/>
|
||||
<field name="perm_unlink" eval="0"/>
|
||||
</record>
|
||||
|
||||
<record id="crm_business_category_rule_user" model="ir.rule">
|
||||
<field name="name">Business Category by user allow list</field>
|
||||
<field name="model_id" ref="model_crm_business_category"/>
|
||||
<field name="domain_force">[('id', 'in', user.allowed_business_category_ids.ids)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('id', 'in', user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('crm.group_use_lead'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="0"/>
|
||||
@@ -36,7 +36,7 @@
|
||||
<record id="crm_lead_business_category_rule_manager" model="ir.rule">
|
||||
<field name="name">CRM Lead full access for manager</field>
|
||||
<field name="model_id" ref="crm.model_crm_lead"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
@@ -47,7 +47,7 @@
|
||||
<record id="crm_team_business_category_rule_manager" model="ir.rule">
|
||||
<field name="name">CRM Team full access for manager</field>
|
||||
<field name="model_id" ref="sales_team.model_crm_team"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
@@ -58,7 +58,7 @@
|
||||
<record id="crm_business_category_rule_manager" model="ir.rule">
|
||||
<field name="name">Business Category full access for manager</field>
|
||||
<field name="model_id" ref="model_crm_business_category"/>
|
||||
<field name="domain_force">[(1, '=', 1)]</field>
|
||||
<field name="domain_force">[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('sales_team.group_sale_manager'))]"/>
|
||||
<field name="perm_read" eval="1"/>
|
||||
<field name="perm_write" eval="1"/>
|
||||
|
||||
@@ -3,10 +3,10 @@ crm_lead_business_category_rule_user_read_create,CRM Lead read/create by effecti
|
||||
crm_lead_business_category_rule_user_write_own,CRM Lead write own by effective business category,crm.model_crm_lead,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids),'|',('user_id','=',user.id),('create_uid','=',user.id)]",crm.group_use_lead,0,1,0,1
|
||||
crm_team_business_category_rule_user,CRM Team read by effective business category,sales_team.model_crm_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",crm.group_use_lead,1,0,0,0
|
||||
crm_business_category_rule_user,Business Category by user effective list,model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",crm.group_use_lead,1,0,0,0
|
||||
crm_lead_business_category_rule_manager,CRM Lead full access for manager,crm.model_crm_lead,"[('company_id','in',user.company_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_team_business_category_rule_manager,CRM Team full access for manager,sales_team.model_crm_team,"[('company_id','in',user.company_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_business_category_rule_manager,Business Category full access for manager,model_crm_business_category,"[('company_id','in',user.company_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_lead_business_category_rule_manager,CRM Lead full access for manager in effective business category,crm.model_crm_lead,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_team_business_category_rule_manager,CRM Team full access for manager in effective business category,sales_team.model_crm_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_business_category_rule_manager,Business Category access for manager in effective business category,model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_activity_history_rule_user,CRM Activity History by effective business category,model_crm_activity_history,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",crm.group_use_lead,1,0,0,0
|
||||
crm_activity_history_rule_manager,CRM Activity History full access for manager,model_crm_activity_history,"[('company_id','in',user.company_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_activity_history_rule_manager,CRM Activity History full access for manager in effective business category,model_crm_activity_history,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_team_business_category_rule_sysadmin,CRM Team full access for system admin,sales_team.model_crm_team,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
crm_activity_history_rule_sysadmin,CRM Activity History full access for system admin,model_crm_activity_history,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
|
||||
|
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_crm_pipeline_pivot_business_category" model="ir.ui.view">
|
||||
<field name="name">crm.lead.pipeline.pivot.business.category</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Pipeline by Business Category">
|
||||
<field name="business_category_id" type="row"/>
|
||||
<field name="team_id" type="row"/>
|
||||
<field name="stage_id" type="col"/>
|
||||
<field name="create_date" interval="month" type="col"/>
|
||||
<field name="expected_revenue" type="measure"/>
|
||||
<field name="id" type="measure" string="Opportunities"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_crm_pipeline_graph_business_category" model="ir.ui.view">
|
||||
<field name="name">crm.lead.pipeline.graph.business.category</field>
|
||||
<field name="model">crm.lead</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Pipeline by Business Category" type="bar">
|
||||
<field name="business_category_id"/>
|
||||
<field name="expected_revenue" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_crm_pipeline_business_category_report" model="ir.actions.act_window">
|
||||
<field name="name">Pipeline by Business Category</field>
|
||||
<field name="res_model">crm.lead</field>
|
||||
<field name="view_mode">tree,pivot,graph,kanban,form</field>
|
||||
<field name="domain">[('type', '=', 'opportunity')]</field>
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
<field name="search_view_id" ref="view_crm_case_opportunities_filter_business_category"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_crm_pipeline_business_category_report"
|
||||
name="Pipeline by Category"
|
||||
parent="crm.crm_menu_report"
|
||||
action="action_crm_pipeline_business_category_report"
|
||||
sequence="36"
|
||||
groups="crm.group_use_lead,sales_team.group_sale_manager,base.group_system"/>
|
||||
|
||||
<record id="action_crm_activity_history_report" model="ir.actions.act_window">
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "GRT Expense Business Category",
|
||||
"summary": "Business category segregation and expense teams for expenses",
|
||||
"description": """
|
||||
Adds business category handling to Expenses with dedicated Expense Teams,
|
||||
record rules per business category, and automatic analytic account propagation
|
||||
for expense transactions and accounting entries.
|
||||
""",
|
||||
"author": "PT Gagak Rimang Teknologi",
|
||||
"website": "https://rimang.id",
|
||||
"category": "Human Resources/Expenses",
|
||||
"version": "14.0.1.0.0",
|
||||
"depends": [
|
||||
"hr_expense",
|
||||
"account",
|
||||
"grt_crm_business_category",
|
||||
],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"security/ir.rule.csv",
|
||||
"views/crm_business_category_views.xml",
|
||||
"views/expense_team_views.xml",
|
||||
"views/hr_expense_views.xml",
|
||||
"views/expense_report_business_category_views.xml",
|
||||
"views/expense_menu_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
from . import account_move
|
||||
from . import crm_business_category
|
||||
from . import hr_expense
|
||||
from . import expense_team
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,105 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
expense_business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Expense Business Category",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
expense_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Expense Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
|
||||
@api.onchange("expense_business_category_id")
|
||||
def _onchange_expense_business_category_id_analytic(self):
|
||||
for move in self:
|
||||
move.expense_analytic_account_id = move.expense_business_category_id.expense_analytic_account_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
vals_list = [self._prepare_expense_business_category_vals(vals) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = self._prepare_expense_business_category_vals(vals)
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_expense_business_category_vals(self, vals):
|
||||
vals = dict(vals)
|
||||
if vals.get("expense_business_category_id") and not vals.get("expense_analytic_account_id"):
|
||||
category = self.env["crm.business.category"].browse(vals["expense_business_category_id"])
|
||||
if category.expense_analytic_account_id:
|
||||
vals["expense_analytic_account_id"] = category.expense_analytic_account_id.id
|
||||
return vals
|
||||
|
||||
@api.constrains("company_id", "expense_business_category_id", "expense_analytic_account_id")
|
||||
def _check_expense_business_category_analytic_company(self):
|
||||
for move in self:
|
||||
if move.expense_business_category_id and move.expense_business_category_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Journal Entry '%s' uses an Expense Business Category from another company.") % (move.display_name,)
|
||||
)
|
||||
if move.expense_analytic_account_id and move.expense_analytic_account_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Journal Entry '%s' uses an Expense Analytic Account from another company.") % (move.display_name,)
|
||||
)
|
||||
if (
|
||||
move.expense_business_category_id
|
||||
and move.expense_business_category_id.expense_analytic_account_id
|
||||
and move.expense_analytic_account_id
|
||||
and move.expense_business_category_id.expense_analytic_account_id != move.expense_analytic_account_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Journal Entry '%s' must use the Expense Analytic Account configured on its Business Category."
|
||||
)
|
||||
% (move.display_name,)
|
||||
)
|
||||
|
||||
def action_post(self):
|
||||
result = super().action_post()
|
||||
for move in self:
|
||||
analytic = move.expense_analytic_account_id
|
||||
if not analytic:
|
||||
continue
|
||||
lines = move.line_ids.filtered(
|
||||
lambda line: line.account_id.internal_type not in ("receivable", "payable")
|
||||
and not line.analytic_account_id
|
||||
)
|
||||
if lines:
|
||||
lines.write({"analytic_account_id": analytic.id})
|
||||
return result
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("analytic_account_id"):
|
||||
continue
|
||||
move_id = vals.get("move_id")
|
||||
account_id = vals.get("account_id")
|
||||
if not move_id or not account_id:
|
||||
continue
|
||||
move = self.env["account.move"].browse(move_id)
|
||||
account = self.env["account.account"].browse(account_id)
|
||||
if (
|
||||
move
|
||||
and move.expense_analytic_account_id
|
||||
and account
|
||||
and account.internal_type not in ("receivable", "payable")
|
||||
):
|
||||
vals["analytic_account_id"] = move.expense_analytic_account_id.id
|
||||
return super().create(vals_list)
|
||||
@@ -0,0 +1,27 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CrmBusinessCategory(models.Model):
|
||||
_inherit = "crm.business.category"
|
||||
|
||||
expense_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Expense Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
help="Default analytic account used for expense transactions in this business category.",
|
||||
)
|
||||
|
||||
@api.constrains("company_id", "expense_analytic_account_id")
|
||||
def _check_expense_analytic_account_company(self):
|
||||
for category in self:
|
||||
if not category.expense_analytic_account_id or not category.company_id:
|
||||
continue
|
||||
if category.expense_analytic_account_id.company_id != category.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Business Category '%s' must use an Expense Analytic Account from company '%s'."
|
||||
)
|
||||
% (category.name, category.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class ExpenseTeam(models.Model):
|
||||
_name = "expense.team"
|
||||
_description = "Expense Team"
|
||||
_order = "name"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
"res.users",
|
||||
string="Team Leader",
|
||||
help="Responsible user for this Expense Team.",
|
||||
)
|
||||
member_ids = fields.Many2many(
|
||||
"res.users",
|
||||
"expense_team_res_users_rel",
|
||||
"team_id",
|
||||
"user_id",
|
||||
string="Members",
|
||||
)
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
required=True,
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
ondelete="restrict",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
)
|
||||
|
||||
@api.onchange("company_id")
|
||||
def _onchange_company_id_business_category(self):
|
||||
for team in self:
|
||||
if team.business_category_id and team.business_category_id.company_id != team.company_id:
|
||||
team.business_category_id = False
|
||||
|
||||
@api.constrains("company_id", "business_category_id")
|
||||
def _check_business_category_company(self):
|
||||
for team in self:
|
||||
if not team.company_id or not team.business_category_id:
|
||||
continue
|
||||
if team.business_category_id.company_id != team.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Expense Team '%s' must use a Business Category from company '%s'."
|
||||
)
|
||||
% (team.name, team.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,192 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class HrExpense(models.Model):
|
||||
_inherit = "hr.expense"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
tracking=True,
|
||||
)
|
||||
expense_team_id = fields.Many2one(
|
||||
"expense.team",
|
||||
string="Expense Team",
|
||||
domain="[('company_id', '=', company_id), ('business_category_id', '=', business_category_id)]",
|
||||
tracking=True,
|
||||
)
|
||||
expense_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Expense Analytic Account",
|
||||
related="business_category_id.expense_analytic_account_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
@api.onchange("business_category_id")
|
||||
def _onchange_business_category_id_reset_team(self):
|
||||
for expense in self:
|
||||
if expense.expense_team_id and expense.expense_team_id.business_category_id != expense.business_category_id:
|
||||
expense.expense_team_id = False
|
||||
|
||||
@api.onchange("expense_team_id")
|
||||
def _onchange_expense_team_id(self):
|
||||
for expense in self:
|
||||
if expense.expense_team_id and expense.expense_team_id.business_category_id:
|
||||
expense.business_category_id = expense.expense_team_id.business_category_id
|
||||
|
||||
@api.onchange("business_category_id")
|
||||
def _onchange_business_category_id_analytic(self):
|
||||
for expense in self:
|
||||
if "analytic_account_id" in expense._fields:
|
||||
expense.analytic_account_id = expense.business_category_id.expense_analytic_account_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
vals_list = [self._prepare_business_category_vals(vals) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = self._prepare_business_category_vals(vals)
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_business_category_vals(self, vals):
|
||||
vals = dict(vals)
|
||||
if vals.get("expense_team_id") and not vals.get("business_category_id"):
|
||||
team = self.env["expense.team"].browse(vals["expense_team_id"])
|
||||
if team.business_category_id:
|
||||
vals["business_category_id"] = team.business_category_id.id
|
||||
if vals.get("business_category_id") and "analytic_account_id" in self._fields and not vals.get("analytic_account_id"):
|
||||
category = self.env["crm.business.category"].browse(vals["business_category_id"])
|
||||
if category.expense_analytic_account_id:
|
||||
vals["analytic_account_id"] = category.expense_analytic_account_id.id
|
||||
return vals
|
||||
|
||||
@api.constrains("business_category_id", "expense_team_id", "company_id", "expense_analytic_account_id")
|
||||
def _check_business_category_team(self):
|
||||
for expense in self:
|
||||
if expense.business_category_id and expense.business_category_id.company_id != expense.company_id:
|
||||
raise ValidationError(
|
||||
_("Expense '%s' uses a Business Category from another company.") % expense.name
|
||||
)
|
||||
if expense.expense_analytic_account_id and expense.expense_analytic_account_id.company_id != expense.company_id:
|
||||
raise ValidationError(
|
||||
_("Expense '%s' uses an Analytic Account from another company.") % expense.name
|
||||
)
|
||||
if expense.expense_team_id:
|
||||
if expense.expense_team_id.company_id != expense.company_id:
|
||||
raise ValidationError(
|
||||
_("Expense Team '%s' belongs to another company.") % expense.expense_team_id.name
|
||||
)
|
||||
if expense.expense_team_id.business_category_id != expense.business_category_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Expense Team '%s' is linked to business category '%s'. "
|
||||
"Please select a matching business category."
|
||||
)
|
||||
% (expense.expense_team_id.name, expense.expense_team_id.business_category_id.name)
|
||||
)
|
||||
if (
|
||||
"analytic_account_id" in expense._fields
|
||||
and expense.expense_analytic_account_id
|
||||
and expense.analytic_account_id
|
||||
and expense.expense_analytic_account_id != expense.analytic_account_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_("Expense '%s' must use the Expense Analytic Account configured on its Business Category.")
|
||||
% expense.name
|
||||
)
|
||||
|
||||
@api.constrains("business_category_id", "expense_team_id", "employee_id")
|
||||
def _check_current_user_expense_scope(self):
|
||||
current_user = self.env.user
|
||||
if current_user.has_group("base.group_system"):
|
||||
return
|
||||
|
||||
effective_categories = current_user.effective_business_category_ids
|
||||
for expense in self:
|
||||
if expense.business_category_id and expense.business_category_id not in effective_categories:
|
||||
raise ValidationError(
|
||||
_("You can only use Expenses in your registered Business Category.")
|
||||
)
|
||||
if expense.expense_team_id:
|
||||
team_users = expense.expense_team_id.user_id | expense.expense_team_id.member_ids
|
||||
if current_user not in team_users:
|
||||
raise ValidationError(
|
||||
_("You must be registered in the selected Expense Team before using it on an Expense.")
|
||||
)
|
||||
|
||||
|
||||
class HrExpenseSheet(models.Model):
|
||||
_inherit = "hr.expense.sheet"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
compute="_compute_business_category_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
expense_team_id = fields.Many2one(
|
||||
"expense.team",
|
||||
string="Expense Team",
|
||||
compute="_compute_expense_team_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
expense_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Expense Analytic Account",
|
||||
compute="_compute_expense_analytic_account_id",
|
||||
store=True,
|
||||
readonly=False,
|
||||
)
|
||||
|
||||
@api.depends("expense_line_ids.business_category_id")
|
||||
def _compute_business_category_id(self):
|
||||
for sheet in self:
|
||||
categories = sheet.expense_line_ids.mapped("business_category_id")
|
||||
sheet.business_category_id = categories[:1].id if len(categories) == 1 else False
|
||||
|
||||
@api.depends("expense_line_ids.expense_team_id")
|
||||
def _compute_expense_team_id(self):
|
||||
for sheet in self:
|
||||
teams = sheet.expense_line_ids.mapped("expense_team_id")
|
||||
sheet.expense_team_id = teams[:1].id if len(teams) == 1 else False
|
||||
|
||||
@api.depends("expense_line_ids.expense_analytic_account_id")
|
||||
def _compute_expense_analytic_account_id(self):
|
||||
for sheet in self:
|
||||
analytics = sheet.expense_line_ids.mapped("expense_analytic_account_id")
|
||||
sheet.expense_analytic_account_id = analytics[:1].id if len(analytics) == 1 else False
|
||||
|
||||
@api.constrains("expense_line_ids", "business_category_id", "expense_team_id")
|
||||
def _check_expense_lines_same_business_category(self):
|
||||
for sheet in self:
|
||||
categories = sheet.expense_line_ids.mapped("business_category_id")
|
||||
if len(categories) > 1:
|
||||
raise ValidationError(
|
||||
_("Expense Report '%s' cannot contain multiple Business Categories.") % sheet.name
|
||||
)
|
||||
teams = sheet.expense_line_ids.mapped("expense_team_id")
|
||||
if len(teams) > 1:
|
||||
raise ValidationError(
|
||||
_("Expense Report '%s' cannot contain multiple Expense Teams.") % sheet.name
|
||||
)
|
||||
|
||||
def action_sheet_move_create(self):
|
||||
result = super().action_sheet_move_create()
|
||||
for sheet in self:
|
||||
move = getattr(sheet, "account_move_id", False)
|
||||
if move and sheet.business_category_id:
|
||||
move.write(
|
||||
{
|
||||
"expense_business_category_id": sheet.business_category_id.id,
|
||||
"expense_analytic_account_id": sheet.expense_analytic_account_id.id,
|
||||
}
|
||||
)
|
||||
return result
|
||||
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_expense_team_user,expense.team user,model_expense_team,hr_expense.group_hr_expense_user,1,0,0,0
|
||||
access_expense_team_manager,expense.team manager,model_expense_team,hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
access_expense_team_sysadmin,expense.team sysadmin,model_expense_team,base.group_system,1,1,1,1
|
||||
|
@@ -0,0 +1,18 @@
|
||||
id,name,model_id:id,domain_force,groups:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
hr_expense_business_category_rule_user_read_create,Expense read/create by effective business category,hr_expense.model_hr_expense,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,1,0
|
||||
hr_expense_business_category_rule_user_write_own,Expense write own by effective business category,hr_expense.model_hr_expense,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids),'|',('employee_id.user_id','=',user.id),('create_uid','=',user.id)]",hr_expense.group_hr_expense_user,0,1,0,1
|
||||
hr_expense_business_category_rule_manager,Expense full access for expense manager in effective business category,hr_expense.model_hr_expense,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
hr_expense_business_category_rule_sysadmin,Expense full access for system admin,hr_expense.model_hr_expense,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
hr_expense_sheet_business_category_rule_user,Expense Report read by effective business category,hr_expense.model_hr_expense_sheet,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,0,0
|
||||
hr_expense_sheet_business_category_rule_manager,Expense Report full access for expense manager in effective business category,hr_expense.model_hr_expense_sheet,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
hr_expense_sheet_business_category_rule_sysadmin,Expense Report full access for system admin,hr_expense.model_hr_expense_sheet,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
expense_team_business_category_rule_user,Expense Team read by effective business category,model_expense_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,0,0
|
||||
expense_team_business_category_rule_manager,Expense Team full access for expense manager in effective business category,model_expense_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
expense_team_business_category_rule_sysadmin,Expense Team full access for system admin,model_expense_team,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
crm_business_category_rule_expense_user,Business Category by user effective list for expense user,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,0,0
|
||||
crm_business_category_rule_expense_manager,Business Category access for expense manager in effective business category,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
crm_business_category_rule_expense_sysadmin,Business Category full access for system admin,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
account_move_expense_business_category_rule_user,Journal Entry read by effective business category for expense user,account.model_account_move,"[('company_id','in',user.company_ids.ids),('expense_business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,0,0
|
||||
account_move_expense_business_category_rule_manager,Journal Entry access for expense manager in effective business category,account.model_account_move,"[('company_id','in',user.company_ids.ids),('expense_business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
account_move_line_expense_business_category_rule_user,Journal Item read by effective business category for expense user,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.expense_business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_user,1,0,0,0
|
||||
account_move_line_expense_business_category_rule_manager,Journal Item access for expense manager in effective business category,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.expense_business_category_id','in',user.effective_business_category_ids.ids)]",hr_expense.group_hr_expense_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_crm_business_category_form_expense_analytic" model="ir.ui.view">
|
||||
<field name="name">crm.business.category.form.expense.analytic</field>
|
||||
<field name="model">crm.business.category</field>
|
||||
<field name="inherit_id" ref="grt_crm_business_category.view_crm_business_category_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='description']/.." position="before">
|
||||
<group string="Expenses">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="expense_analytic_account_id" domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_expense_team"
|
||||
name="Team Expenses"
|
||||
parent="hr_expense.menu_hr_expense_root"
|
||||
sequence="45"
|
||||
action="action_expense_team"
|
||||
groups="hr_expense.group_hr_expense_manager"/>
|
||||
|
||||
<menuitem id="menu_business_category_from_expense"
|
||||
name="Business Categories"
|
||||
parent="hr_expense.menu_hr_expense_root"
|
||||
sequence="46"
|
||||
action="grt_crm_business_category.action_crm_business_category"
|
||||
groups="hr_expense.group_hr_expense_manager"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_hr_expense_pivot_business_category_report" model="ir.ui.view">
|
||||
<field name="name">hr.expense.pivot.business.category.report</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Expenses by Business Category">
|
||||
<field name="business_category_id" type="row"/>
|
||||
<field name="expense_team_id" type="row"/>
|
||||
<field name="state" type="col"/>
|
||||
<field name="date" interval="month" type="col"/>
|
||||
<field name="total_amount" type="measure"/>
|
||||
<field name="id" type="measure" string="Expenses"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_expense_graph_business_category_report" model="ir.ui.view">
|
||||
<field name="name">hr.expense.graph.business.category.report</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Expenses by Business Category" type="bar">
|
||||
<field name="business_category_id"/>
|
||||
<field name="total_amount" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_hr_expense_business_category_report" model="ir.actions.act_window">
|
||||
<field name="name">Expenses by Business Category</field>
|
||||
<field name="res_model">hr.expense</field>
|
||||
<field name="view_mode">tree,pivot,graph</field>
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
<field name="search_view_id" ref="view_hr_expense_filter_business_category"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_hr_expense_business_category_report"
|
||||
name="Expenses by Category"
|
||||
parent="hr_expense.menu_hr_expense_root"
|
||||
sequence="50"
|
||||
action="action_hr_expense_business_category_report"
|
||||
groups="hr_expense.group_hr_expense_user,hr_expense.group_hr_expense_manager,base.group_system"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_expense_team_tree" model="ir.ui.view">
|
||||
<field name="name">expense.team.tree</field>
|
||||
<field name="model">expense.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_expense_team_form" model="ir.ui.view">
|
||||
<field name="name">expense.team.form</field>
|
||||
<field name="model">expense.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Expense Team">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id" domain="[('company_id', '=', company_id)]"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_expense_team" model="ir.actions.act_window">
|
||||
<field name="name">Team Expenses</field>
|
||||
<field name="res_model">expense.team</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_hr_expense_form_business_category" model="ir.ui.view">
|
||||
<field name="name">hr.expense.form.business.category</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="inherit_id" ref="hr_expense.hr_expense_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="expense_team_id"/>
|
||||
<field name="expense_analytic_account_id" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_expense_tree_business_category" model="ir.ui.view">
|
||||
<field name="name">hr.expense.tree.business.category</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="inherit_id" ref="hr_expense.view_expenses_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="business_category_id" optional="show"/>
|
||||
<field name="expense_team_id" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_expense_filter_business_category" model="ir.ui.view">
|
||||
<field name="name">hr.expense.search.business.category</field>
|
||||
<field name="model">hr.expense</field>
|
||||
<field name="inherit_id" ref="hr_expense.hr_expense_view_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="business_category_id"/>
|
||||
<field name="expense_team_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//filter[@name='my_expenses']" position="after">
|
||||
<filter string="Business Category" name="group_by_business_category" context="{'group_by': 'business_category_id'}"/>
|
||||
<filter string="Expense Team" name="group_by_expense_team" context="{'group_by': 'expense_team_id'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_expense_sheet_form_business_category" model="ir.ui.view">
|
||||
<field name="name">hr.expense.sheet.form.business.category</field>
|
||||
<field name="model">hr.expense.sheet</field>
|
||||
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="business_category_id" readonly="1"/>
|
||||
<field name="expense_team_id" readonly="1"/>
|
||||
<field name="expense_analytic_account_id" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_hr_expense_sheet_tree_business_category" model="ir.ui.view">
|
||||
<field name="name">hr.expense.sheet.tree.business.category</field>
|
||||
<field name="model">hr.expense.sheet</field>
|
||||
<field name="inherit_id" ref="hr_expense.view_hr_expense_sheet_tree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="business_category_id" optional="show"/>
|
||||
<field name="expense_team_id" optional="show"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_form_expense_business_category" model="ir.ui.view">
|
||||
<field name="name">account.move.form.expense.business.category</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_payment_term_id']" position="after">
|
||||
<field name="expense_business_category_id"/>
|
||||
<field name="expense_analytic_account_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,34 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "GRT Inventory Business Category",
|
||||
"summary": "Business category segregation and inventory teams for stock operations",
|
||||
"description": """
|
||||
Adds business category handling to Inventory with dedicated Inventory Teams,
|
||||
warehouse segregation, record rules per business category, and analytic account
|
||||
propagation for stock valuation journal entries.
|
||||
""",
|
||||
"author": "PT Gagak Rimang Teknologi",
|
||||
"website": "https://rimang.id",
|
||||
"category": "Inventory",
|
||||
"version": "14.0.1.0.0",
|
||||
"depends": [
|
||||
"stock_account",
|
||||
"sale_stock",
|
||||
"purchase_stock",
|
||||
"grt_crm_business_category",
|
||||
"grt_sales_business_category",
|
||||
"grt_purchase_business_category",
|
||||
],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"security/ir.rule.csv",
|
||||
"views/crm_business_category_views.xml",
|
||||
"views/stock_team_views.xml",
|
||||
"views/stock_warehouse_views.xml",
|
||||
"views/stock_picking_views.xml",
|
||||
"views/inventory_report_business_category_views.xml",
|
||||
"views/stock_menu_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,5 @@
|
||||
from . import account_move
|
||||
from . import crm_business_category
|
||||
from . import stock_picking
|
||||
from . import stock_team
|
||||
from . import stock_warehouse
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,92 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
inventory_business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Inventory Business Category",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
inventory_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Inventory Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
|
||||
@api.onchange("inventory_business_category_id")
|
||||
def _onchange_inventory_business_category_id_analytic(self):
|
||||
for move in self:
|
||||
move.inventory_analytic_account_id = move.inventory_business_category_id.inventory_analytic_account_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
vals_list = [self._prepare_inventory_business_category_vals(vals) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = self._prepare_inventory_business_category_vals(vals)
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_inventory_business_category_vals(self, vals):
|
||||
vals = dict(vals)
|
||||
if vals.get("inventory_business_category_id") and not vals.get("inventory_analytic_account_id"):
|
||||
category = self.env["crm.business.category"].browse(vals["inventory_business_category_id"])
|
||||
if category.inventory_analytic_account_id:
|
||||
vals["inventory_analytic_account_id"] = category.inventory_analytic_account_id.id
|
||||
return vals
|
||||
|
||||
@api.constrains("company_id", "inventory_business_category_id", "inventory_analytic_account_id")
|
||||
def _check_inventory_business_category_analytic_company(self):
|
||||
for move in self:
|
||||
if move.inventory_business_category_id and move.inventory_business_category_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Journal Entry '%s' uses an Inventory Business Category from another company.") % move.display_name
|
||||
)
|
||||
if move.inventory_analytic_account_id and move.inventory_analytic_account_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Journal Entry '%s' uses an Inventory Analytic Account from another company.") % move.display_name
|
||||
)
|
||||
if (
|
||||
move.inventory_business_category_id
|
||||
and move.inventory_business_category_id.inventory_analytic_account_id
|
||||
and move.inventory_analytic_account_id
|
||||
and move.inventory_business_category_id.inventory_analytic_account_id
|
||||
!= move.inventory_analytic_account_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Journal Entry '%s' must use the Inventory Analytic Account configured on its Business Category."
|
||||
)
|
||||
% move.display_name
|
||||
)
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("analytic_account_id"):
|
||||
continue
|
||||
move_id = vals.get("move_id")
|
||||
account_id = vals.get("account_id")
|
||||
if not move_id or not account_id:
|
||||
continue
|
||||
move = self.env["account.move"].browse(move_id)
|
||||
account = self.env["account.account"].browse(account_id)
|
||||
if (
|
||||
move
|
||||
and move.inventory_analytic_account_id
|
||||
and account
|
||||
and account.internal_type not in ("receivable", "payable")
|
||||
):
|
||||
vals["analytic_account_id"] = move.inventory_analytic_account_id.id
|
||||
return super().create(vals_list)
|
||||
@@ -0,0 +1,27 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CrmBusinessCategory(models.Model):
|
||||
_inherit = "crm.business.category"
|
||||
|
||||
inventory_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Inventory Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
help="Default analytic account used for stock valuation entries in this business category.",
|
||||
)
|
||||
|
||||
@api.constrains("company_id", "inventory_analytic_account_id")
|
||||
def _check_inventory_analytic_account_company(self):
|
||||
for category in self:
|
||||
if not category.inventory_analytic_account_id or not category.company_id:
|
||||
continue
|
||||
if category.inventory_analytic_account_id.company_id != category.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Business Category '%s' must use an Inventory Analytic Account from company '%s'."
|
||||
)
|
||||
% (category.name, category.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,189 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StockPicking(models.Model):
|
||||
_inherit = "stock.picking"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
default=lambda self: self._default_business_category_from_context(),
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
tracking=True,
|
||||
)
|
||||
stock_team_id = fields.Many2one(
|
||||
"stock.team",
|
||||
string="Inventory Team",
|
||||
domain="[('company_id', '=', company_id), ('business_category_id', '=', business_category_id)]",
|
||||
tracking=True,
|
||||
)
|
||||
inventory_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Inventory Analytic Account",
|
||||
related="business_category_id.inventory_analytic_account_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
@api.model
|
||||
def _default_business_category_from_context(self):
|
||||
picking_type_id = self.env.context.get("default_picking_type_id")
|
||||
if picking_type_id:
|
||||
picking_type = self.env["stock.picking.type"].browse(picking_type_id)
|
||||
if picking_type.warehouse_id and picking_type.warehouse_id.business_category_id:
|
||||
return picking_type.warehouse_id.business_category_id.id
|
||||
return self.env["business.category.mixin"]._default_business_category_id()
|
||||
|
||||
@api.onchange("business_category_id")
|
||||
def _onchange_business_category_id_reset_team(self):
|
||||
for picking in self:
|
||||
if picking.stock_team_id and picking.stock_team_id.business_category_id != picking.business_category_id:
|
||||
picking.stock_team_id = False
|
||||
|
||||
@api.onchange("stock_team_id")
|
||||
def _onchange_stock_team_id(self):
|
||||
for picking in self:
|
||||
if picking.stock_team_id and picking.stock_team_id.business_category_id:
|
||||
picking.business_category_id = picking.stock_team_id.business_category_id
|
||||
|
||||
@api.onchange("picking_type_id")
|
||||
def _onchange_picking_type_id_business_category(self):
|
||||
for picking in self:
|
||||
warehouse = picking.picking_type_id.warehouse_id
|
||||
if warehouse and warehouse.business_category_id:
|
||||
picking.business_category_id = warehouse.business_category_id
|
||||
if warehouse and warehouse.stock_team_id and not picking.stock_team_id:
|
||||
picking.stock_team_id = warehouse.stock_team_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
vals_list = [self._prepare_business_category_vals(vals) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = self._prepare_business_category_vals(vals)
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_business_category_vals(self, vals):
|
||||
vals = dict(vals)
|
||||
if vals.get("stock_team_id") and not vals.get("business_category_id"):
|
||||
team = self.env["stock.team"].browse(vals["stock_team_id"])
|
||||
if team.business_category_id:
|
||||
vals["business_category_id"] = team.business_category_id.id
|
||||
if vals.get("picking_type_id") and not vals.get("business_category_id"):
|
||||
picking_type = self.env["stock.picking.type"].browse(vals["picking_type_id"])
|
||||
if picking_type.warehouse_id and picking_type.warehouse_id.business_category_id:
|
||||
vals["business_category_id"] = picking_type.warehouse_id.business_category_id.id
|
||||
return vals
|
||||
|
||||
@api.constrains("business_category_id", "stock_team_id", "company_id")
|
||||
def _check_business_category_team(self):
|
||||
for picking in self:
|
||||
if picking.business_category_id and picking.business_category_id.company_id != picking.company_id:
|
||||
raise ValidationError(
|
||||
_("Transfer '%s' uses a Business Category from another company.") % picking.name
|
||||
)
|
||||
if picking.stock_team_id:
|
||||
if picking.stock_team_id.company_id != picking.company_id:
|
||||
raise ValidationError(
|
||||
_("Inventory Team '%s' belongs to another company.") % picking.stock_team_id.name
|
||||
)
|
||||
if picking.stock_team_id.business_category_id != picking.business_category_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Inventory Team '%s' is linked to business category '%s'. "
|
||||
"Please select a matching business category."
|
||||
)
|
||||
% (picking.stock_team_id.name, picking.stock_team_id.business_category_id.name)
|
||||
)
|
||||
warehouse = picking.picking_type_id.warehouse_id
|
||||
if warehouse and warehouse.business_category_id and picking.business_category_id:
|
||||
if warehouse.business_category_id != picking.business_category_id:
|
||||
raise ValidationError(
|
||||
_("Transfer '%s' must use the same Business Category as its warehouse.") % picking.name
|
||||
)
|
||||
|
||||
@api.constrains("business_category_id", "stock_team_id")
|
||||
def _check_current_user_inventory_scope(self):
|
||||
current_user = self.env.user
|
||||
if current_user.has_group("base.group_system"):
|
||||
return
|
||||
|
||||
effective_categories = current_user.effective_business_category_ids
|
||||
for picking in self:
|
||||
if picking.business_category_id and picking.business_category_id not in effective_categories:
|
||||
raise ValidationError(
|
||||
_("You can only use Inventory Transfers in your registered Business Category.")
|
||||
)
|
||||
if picking.stock_team_id:
|
||||
team_users = picking.stock_team_id.user_id | picking.stock_team_id.member_ids
|
||||
if current_user not in team_users:
|
||||
raise ValidationError(
|
||||
_("You must be registered in the selected Inventory Team before using it on a Transfer.")
|
||||
)
|
||||
|
||||
def button_validate(self):
|
||||
for picking in self:
|
||||
if not picking.business_category_id:
|
||||
raise ValidationError(
|
||||
_("Transfer '%s' requires a Business Category before validation.") % picking.name
|
||||
)
|
||||
return super().button_validate()
|
||||
|
||||
|
||||
class StockMove(models.Model):
|
||||
_inherit = "stock.move"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
related="picking_id.business_category_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
stock_team_id = fields.Many2one(
|
||||
"stock.team",
|
||||
string="Inventory Team",
|
||||
related="picking_id.stock_team_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
inventory_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Inventory Analytic Account",
|
||||
related="picking_id.inventory_analytic_account_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
|
||||
def _get_new_picking_values(self):
|
||||
vals = super()._get_new_picking_values()
|
||||
sale_order = self.sale_line_id.order_id if "sale_line_id" in self._fields else self.env["sale.order"]
|
||||
purchase_order = (
|
||||
self.purchase_line_id.order_id if "purchase_line_id" in self._fields else self.env["purchase.order"]
|
||||
)
|
||||
business_category = False
|
||||
if sale_order and sale_order.business_category_id:
|
||||
business_category = sale_order.business_category_id
|
||||
elif purchase_order and purchase_order.business_category_id:
|
||||
business_category = purchase_order.business_category_id
|
||||
elif self.picking_type_id.warehouse_id and self.picking_type_id.warehouse_id.business_category_id:
|
||||
business_category = self.picking_type_id.warehouse_id.business_category_id
|
||||
if business_category:
|
||||
vals["business_category_id"] = business_category.id
|
||||
warehouse = self.picking_type_id.warehouse_id
|
||||
if warehouse and warehouse.stock_team_id:
|
||||
vals.setdefault("stock_team_id", warehouse.stock_team_id.id)
|
||||
return vals
|
||||
|
||||
def _prepare_account_move_line(self, qty, cost, credit_account_id, debit_account_id, description):
|
||||
res = super()._prepare_account_move_line(qty, cost, credit_account_id, debit_account_id, description)
|
||||
analytic = self.inventory_analytic_account_id
|
||||
if not analytic:
|
||||
return res
|
||||
for line_vals in res:
|
||||
if isinstance(line_vals, dict) and not line_vals.get("analytic_account_id"):
|
||||
line_vals["analytic_account_id"] = analytic.id
|
||||
return res
|
||||
@@ -0,0 +1,56 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StockTeam(models.Model):
|
||||
_name = "stock.team"
|
||||
_description = "Inventory Team"
|
||||
_order = "name"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
"res.users",
|
||||
string="Team Leader",
|
||||
help="Responsible user for this Inventory Team.",
|
||||
)
|
||||
member_ids = fields.Many2many(
|
||||
"res.users",
|
||||
"stock_team_res_users_rel",
|
||||
"team_id",
|
||||
"user_id",
|
||||
string="Members",
|
||||
)
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
required=True,
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
ondelete="restrict",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
)
|
||||
|
||||
@api.onchange("company_id")
|
||||
def _onchange_company_id_business_category(self):
|
||||
for team in self:
|
||||
if team.business_category_id and team.business_category_id.company_id != team.company_id:
|
||||
team.business_category_id = False
|
||||
|
||||
@api.constrains("company_id", "business_category_id")
|
||||
def _check_business_category_company(self):
|
||||
for team in self:
|
||||
if not team.company_id or not team.business_category_id:
|
||||
continue
|
||||
if team.business_category_id.company_id != team.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Inventory Team '%s' must use a Business Category from company '%s'."
|
||||
)
|
||||
% (team.name, team.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class StockWarehouse(models.Model):
|
||||
_inherit = "stock.warehouse"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
ondelete="restrict",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
)
|
||||
stock_team_id = fields.Many2one(
|
||||
"stock.team",
|
||||
string="Inventory Team",
|
||||
domain="[('company_id', '=', company_id), ('business_category_id', '=', business_category_id)]",
|
||||
)
|
||||
|
||||
@api.onchange("business_category_id")
|
||||
def _onchange_business_category_id_reset_stock_team(self):
|
||||
for warehouse in self:
|
||||
if warehouse.stock_team_id and warehouse.stock_team_id.business_category_id != warehouse.business_category_id:
|
||||
warehouse.stock_team_id = False
|
||||
|
||||
@api.onchange("stock_team_id")
|
||||
def _onchange_stock_team_id(self):
|
||||
for warehouse in self:
|
||||
if warehouse.stock_team_id and warehouse.stock_team_id.business_category_id:
|
||||
warehouse.business_category_id = warehouse.stock_team_id.business_category_id
|
||||
|
||||
@api.constrains("company_id", "business_category_id", "stock_team_id")
|
||||
def _check_business_category_team(self):
|
||||
for warehouse in self:
|
||||
if warehouse.business_category_id and warehouse.business_category_id.company_id != warehouse.company_id:
|
||||
raise ValidationError(
|
||||
_("Warehouse '%s' uses a Business Category from another company.") % warehouse.name
|
||||
)
|
||||
if warehouse.stock_team_id:
|
||||
if warehouse.stock_team_id.company_id != warehouse.company_id:
|
||||
raise ValidationError(
|
||||
_("Inventory Team '%s' belongs to another company.") % warehouse.stock_team_id.name
|
||||
)
|
||||
if warehouse.stock_team_id.business_category_id != warehouse.business_category_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Inventory Team '%s' is linked to business category '%s'. "
|
||||
"Please select a matching business category."
|
||||
)
|
||||
% (warehouse.stock_team_id.name, warehouse.stock_team_id.business_category_id.name)
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_stock_team_user,stock.team user,model_stock_team,stock.group_stock_user,1,0,0,0
|
||||
access_stock_team_manager,stock.team manager,model_stock_team,stock.group_stock_manager,1,1,1,1
|
||||
access_stock_team_sysadmin,stock.team sysadmin,model_stock_team,base.group_system,1,1,1,1
|
||||
|
@@ -0,0 +1,21 @@
|
||||
id,name,model_id:id,domain_force,groups:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
stock_picking_business_category_rule_user_read_create,Transfer read/create by effective business category,stock.model_stock_picking,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,1,0
|
||||
stock_picking_business_category_rule_user_write_own,Transfer write own by effective business category,stock.model_stock_picking,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids),'|',('user_id','=',user.id),('create_uid','=',user.id)]",stock.group_stock_user,0,1,0,1
|
||||
stock_picking_business_category_rule_manager,Transfer full access for inventory manager in effective business category,stock.model_stock_picking,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
stock_picking_business_category_rule_sysadmin,Transfer full access for system admin,stock.model_stock_picking,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
stock_move_business_category_rule_user,Stock Move by effective business category,stock.model_stock_move,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
stock_move_business_category_rule_manager,Stock Move full access for inventory manager in effective business category,stock.model_stock_move,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
stock_move_business_category_rule_sysadmin,Stock Move full access for system admin,stock.model_stock_move,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
stock_warehouse_business_category_rule_user,Warehouse by effective business category,stock.model_stock_warehouse,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
stock_warehouse_business_category_rule_manager,Warehouse full access for inventory manager in effective business category,stock.model_stock_warehouse,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
stock_warehouse_business_category_rule_sysadmin,Warehouse full access for system admin,stock.model_stock_warehouse,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
stock_team_business_category_rule_user,Inventory Team read by effective business category,model_stock_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
stock_team_business_category_rule_manager,Inventory Team full access for inventory manager in effective business category,model_stock_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
stock_team_business_category_rule_sysadmin,Inventory Team full access for system admin,model_stock_team,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
crm_business_category_rule_inventory_user,Business Category by user effective list for inventory user,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
crm_business_category_rule_inventory_manager,Business Category access for inventory manager in effective business category,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
crm_business_category_rule_inventory_sysadmin,Business Category full access for system admin,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
account_move_inventory_business_category_rule_user,Journal Entry read by effective business category for inventory user,account.model_account_move,"[('company_id','in',user.company_ids.ids),('inventory_business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
account_move_inventory_business_category_rule_manager,Journal Entry access for inventory manager in effective business category,account.model_account_move,"[('company_id','in',user.company_ids.ids),('inventory_business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
account_move_line_inventory_business_category_rule_user,Journal Item read by effective business category for inventory user,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.inventory_business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_user,1,0,0,0
|
||||
account_move_line_inventory_business_category_rule_manager,Journal Item access for inventory manager in effective business category,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.inventory_business_category_id','in',user.effective_business_category_ids.ids)]",stock.group_stock_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_crm_business_category_form_inventory_analytic" model="ir.ui.view">
|
||||
<field name="name">crm.business.category.form.inventory.analytic</field>
|
||||
<field name="model">crm.business.category</field>
|
||||
<field name="inherit_id" ref="grt_crm_business_category.view_crm_business_category_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='description']/.." position="before">
|
||||
<group string="Inventory">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="inventory_analytic_account_id" domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_stock_picking_pivot_business_category_report" model="ir.ui.view">
|
||||
<field name="name">stock.picking.pivot.business.category.report</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Inventory Transfers by Business Category">
|
||||
<field name="business_category_id" type="row"/>
|
||||
<field name="stock_team_id" type="row"/>
|
||||
<field name="state" type="col"/>
|
||||
<field name="scheduled_date" interval="month" type="col"/>
|
||||
<field name="id" type="measure" string="Transfers"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_picking_graph_business_category_report" model="ir.ui.view">
|
||||
<field name="name">stock.picking.graph.business.category.report</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Inventory Transfers by Business Category" type="bar">
|
||||
<field name="business_category_id"/>
|
||||
<field name="id" type="measure" string="Transfers"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_stock_picking_business_category_report" model="ir.actions.act_window">
|
||||
<field name="name">Inventory Transfers by Business Category</field>
|
||||
<field name="res_model">stock.picking</field>
|
||||
<field name="view_mode">tree,pivot,graph</field>
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_stock_picking_business_category_report"
|
||||
name="Inventory by Category"
|
||||
parent="stock.menu_stock_root"
|
||||
sequence="50"
|
||||
action="action_stock_picking_business_category_report"
|
||||
groups="stock.group_stock_user,stock.group_stock_manager,base.group_system"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_stock_team"
|
||||
name="Team Inventory"
|
||||
parent="stock.menu_stock_root"
|
||||
sequence="45"
|
||||
action="action_stock_team"
|
||||
groups="stock.group_stock_manager"/>
|
||||
|
||||
<menuitem id="menu_business_category_from_inventory"
|
||||
name="Business Categories"
|
||||
parent="stock.menu_stock_root"
|
||||
sequence="46"
|
||||
action="grt_crm_business_category.action_crm_business_category"
|
||||
groups="stock.group_stock_manager"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_picking_form_business_category" model="ir.ui.view">
|
||||
<field name="name">stock.picking.form.business.category</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.view_picking_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='location_dest_id']" position="after">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="stock_team_id"/>
|
||||
<field name="inventory_analytic_account_id" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_picking_search_business_category" model="ir.ui.view">
|
||||
<field name="name">stock.picking.search.business.category</field>
|
||||
<field name="model">stock.picking</field>
|
||||
<field name="inherit_id" ref="stock.vpicktree"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='origin']" position="after">
|
||||
<field name="business_category_id"/>
|
||||
<field name="stock_team_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_search_business_category" model="ir.ui.view">
|
||||
<field name="name">stock.move.search.business.category</field>
|
||||
<field name="model">stock.move</field>
|
||||
<field name="inherit_id" ref="stock.view_move_search"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='product_id']" position="after">
|
||||
<field name="business_category_id"/>
|
||||
<field name="stock_team_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_form_inventory_business_category" model="ir.ui.view">
|
||||
<field name="name">account.move.form.inventory.business.category</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_payment_term_id']" position="after">
|
||||
<field name="inventory_business_category_id"/>
|
||||
<field name="inventory_analytic_account_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_stock_team_tree" model="ir.ui.view">
|
||||
<field name="name">stock.team.tree</field>
|
||||
<field name="model">stock.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_stock_team_form" model="ir.ui.view">
|
||||
<field name="name">stock.team.form</field>
|
||||
<field name="model">stock.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Inventory Team">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id" domain="[('company_id', '=', company_id)]"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_stock_team" model="ir.actions.act_window">
|
||||
<field name="name">Team Inventory</field>
|
||||
<field name="res_model">stock.team</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_warehouse_form_business_category" model="ir.ui.view">
|
||||
<field name="name">stock.warehouse.form.business.category</field>
|
||||
<field name="model">stock.warehouse</field>
|
||||
<field name="inherit_id" ref="stock.view_warehouse"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='company_id']" position="after">
|
||||
<field name="business_category_id"/>
|
||||
<field name="stock_team_id"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1 @@
|
||||
from . import models
|
||||
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
"name": "GRT Purchase Business Category",
|
||||
"summary": "Business category segregation and purchase teams for purchasing",
|
||||
"description": """
|
||||
Adds business category handling to Purchase Orders with dedicated Purchase Teams,
|
||||
record rules per business category, and automatic analytic account propagation
|
||||
for purchasing transactions.
|
||||
""",
|
||||
"author": "PT Gagak Rimang Teknologi",
|
||||
"website": "https://rimang.id",
|
||||
"category": "Purchases",
|
||||
"version": "14.0.1.0.0",
|
||||
"depends": [
|
||||
"purchase",
|
||||
"account",
|
||||
"grt_crm_business_category",
|
||||
],
|
||||
"data": [
|
||||
"security/ir.model.access.csv",
|
||||
"security/ir.rule.csv",
|
||||
"views/crm_business_category_views.xml",
|
||||
"views/purchase_team_views.xml",
|
||||
"views/purchase_order_views.xml",
|
||||
"views/purchase_report_business_category_views.xml",
|
||||
"views/purchase_menu_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
"application": False,
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,4 @@
|
||||
from . import account_move
|
||||
from . import crm_business_category
|
||||
from . import purchase_order
|
||||
from . import purchase_team
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,89 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = "account.move"
|
||||
|
||||
purchase_business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Purchase Business Category",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
purchase_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Purchase Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
copy=False,
|
||||
)
|
||||
|
||||
@api.onchange("purchase_business_category_id")
|
||||
def _onchange_purchase_business_category_id_analytic(self):
|
||||
for move in self:
|
||||
move.purchase_analytic_account_id = move.purchase_business_category_id.purchase_analytic_account_id
|
||||
|
||||
@api.constrains("company_id", "purchase_business_category_id", "purchase_analytic_account_id")
|
||||
def _check_purchase_business_category_analytic_company(self):
|
||||
for move in self:
|
||||
if move.purchase_business_category_id and move.purchase_business_category_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Vendor Bill '%s' uses a Business Category from another company.") % (move.display_name,)
|
||||
)
|
||||
if move.purchase_analytic_account_id and move.purchase_analytic_account_id.company_id != move.company_id:
|
||||
raise ValidationError(
|
||||
_("Vendor Bill '%s' uses an Analytic Account from another company.") % (move.display_name,)
|
||||
)
|
||||
if (
|
||||
move.purchase_business_category_id
|
||||
and move.purchase_business_category_id.purchase_analytic_account_id
|
||||
and move.purchase_analytic_account_id
|
||||
and move.purchase_business_category_id.purchase_analytic_account_id != move.purchase_analytic_account_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Vendor Bill '%s' must use the Purchase Analytic Account configured on its Business Category."
|
||||
)
|
||||
% (move.display_name,)
|
||||
)
|
||||
|
||||
def action_post(self):
|
||||
result = super().action_post()
|
||||
for move in self.filtered(lambda m: m.move_type in ("in_invoice", "in_refund")):
|
||||
analytic = move.purchase_analytic_account_id
|
||||
if not analytic:
|
||||
continue
|
||||
lines = move.line_ids.filtered(
|
||||
lambda line: line.account_id.internal_type not in ("receivable", "payable")
|
||||
and not line.analytic_account_id
|
||||
)
|
||||
if lines:
|
||||
lines.write({"analytic_account_id": analytic.id})
|
||||
return result
|
||||
|
||||
|
||||
class AccountMoveLine(models.Model):
|
||||
_inherit = "account.move.line"
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("analytic_account_id"):
|
||||
continue
|
||||
move_id = vals.get("move_id")
|
||||
account_id = vals.get("account_id")
|
||||
if not move_id or not account_id:
|
||||
continue
|
||||
move = self.env["account.move"].browse(move_id)
|
||||
account = self.env["account.account"].browse(account_id)
|
||||
if (
|
||||
move
|
||||
and move.move_type in ("in_invoice", "in_refund")
|
||||
and move.purchase_analytic_account_id
|
||||
and account
|
||||
and account.internal_type not in ("receivable", "payable")
|
||||
):
|
||||
vals["analytic_account_id"] = move.purchase_analytic_account_id.id
|
||||
return super().create(vals_list)
|
||||
@@ -0,0 +1,27 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class CrmBusinessCategory(models.Model):
|
||||
_inherit = "crm.business.category"
|
||||
|
||||
purchase_analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Purchase Analytic Account",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
help="Default analytic account used for purchasing transactions in this business category.",
|
||||
)
|
||||
|
||||
@api.constrains("company_id", "purchase_analytic_account_id")
|
||||
def _check_purchase_analytic_account_company(self):
|
||||
for category in self:
|
||||
if not category.purchase_analytic_account_id or not category.company_id:
|
||||
continue
|
||||
if category.purchase_analytic_account_id.company_id != category.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Business Category '%s' must use a Purchase Analytic Account from company '%s'."
|
||||
)
|
||||
% (category.name, category.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,177 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class PurchaseOrder(models.Model):
|
||||
_inherit = "purchase.order"
|
||||
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
ondelete="restrict",
|
||||
tracking=True,
|
||||
)
|
||||
analytic_account_id = fields.Many2one(
|
||||
"account.analytic.account",
|
||||
string="Purchase Analytic Account",
|
||||
related="business_category_id.purchase_analytic_account_id",
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
purchase_team_id = fields.Many2one(
|
||||
"purchase.team",
|
||||
string="Purchase Team",
|
||||
domain="[('company_id', '=', company_id), ('business_category_id', '=', business_category_id)]",
|
||||
tracking=True,
|
||||
)
|
||||
|
||||
def _is_purchase_admin_user(self):
|
||||
self.ensure_one()
|
||||
return self.env.user.has_group("purchase.group_purchase_manager") or self.env.user.has_group("base.group_system")
|
||||
|
||||
@api.onchange("business_category_id")
|
||||
def _onchange_business_category_id(self):
|
||||
for order in self:
|
||||
if (
|
||||
order.purchase_team_id
|
||||
and order.purchase_team_id.business_category_id != order.business_category_id
|
||||
):
|
||||
order.purchase_team_id = False
|
||||
|
||||
@api.onchange("purchase_team_id")
|
||||
def _onchange_purchase_team_id(self):
|
||||
for order in self:
|
||||
if order.purchase_team_id and order.purchase_team_id.business_category_id:
|
||||
order.business_category_id = order.purchase_team_id.business_category_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
vals_list = [self._prepare_business_category_vals(vals) for vals in vals_list]
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = self._prepare_business_category_vals(vals)
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_business_category_vals(self, vals):
|
||||
vals = dict(vals)
|
||||
if vals.get("purchase_team_id") and not vals.get("business_category_id"):
|
||||
team = self.env["purchase.team"].browse(vals["purchase_team_id"])
|
||||
if team.business_category_id:
|
||||
vals["business_category_id"] = team.business_category_id.id
|
||||
return vals
|
||||
|
||||
@api.constrains("business_category_id", "purchase_team_id", "company_id", "analytic_account_id")
|
||||
def _check_business_category_team(self):
|
||||
for order in self:
|
||||
if order.business_category_id and order.business_category_id.company_id != order.company_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Order '%s' uses a Business Category from another company.") % order.name
|
||||
)
|
||||
if order.analytic_account_id and order.analytic_account_id.company_id != order.company_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Order '%s' uses an Analytic Account from another company.") % order.name
|
||||
)
|
||||
if not order.purchase_team_id:
|
||||
continue
|
||||
if order.purchase_team_id.company_id != order.company_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Team '%s' belongs to another company.") % order.purchase_team_id.name
|
||||
)
|
||||
if not order.purchase_team_id.business_category_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Team '%s' must have a Business Category before it can be used.")
|
||||
% order.purchase_team_id.name
|
||||
)
|
||||
if (
|
||||
order.business_category_id
|
||||
and order.purchase_team_id.business_category_id != order.business_category_id
|
||||
):
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Purchase Team '%s' is linked to business category '%s'. "
|
||||
"Please select a matching business category."
|
||||
)
|
||||
% (order.purchase_team_id.name, order.purchase_team_id.business_category_id.name)
|
||||
)
|
||||
|
||||
@api.constrains("business_category_id", "purchase_team_id", "user_id")
|
||||
def _check_current_user_purchase_scope(self):
|
||||
current_user = self.env.user
|
||||
if current_user.has_group("base.group_system"):
|
||||
return
|
||||
|
||||
effective_categories = current_user.effective_business_category_ids
|
||||
for order in self:
|
||||
if order.business_category_id and order.business_category_id not in effective_categories:
|
||||
raise ValidationError(
|
||||
_("You can only use Purchase Orders in your registered Business Category.")
|
||||
)
|
||||
|
||||
if order.purchase_team_id and current_user not in (order.purchase_team_id.user_id | order.purchase_team_id.member_ids):
|
||||
raise ValidationError(
|
||||
_("You must be registered in the selected Purchase Team before using it on a Purchase Order.")
|
||||
)
|
||||
|
||||
def button_confirm(self):
|
||||
for order in self:
|
||||
if order.state not in ("draft", "sent", "to approve"):
|
||||
continue
|
||||
if not order.business_category_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Order '%s' requires a Business Category before confirmation.") % order.name
|
||||
)
|
||||
if not order.purchase_team_id:
|
||||
raise ValidationError(
|
||||
_("Purchase Order '%s' requires a Purchase Team before confirmation.") % order.name
|
||||
)
|
||||
return super().button_confirm()
|
||||
|
||||
def _prepare_invoice(self):
|
||||
vals = super()._prepare_invoice()
|
||||
vals["purchase_business_category_id"] = self.business_category_id.id
|
||||
vals["purchase_analytic_account_id"] = self.analytic_account_id.id
|
||||
return vals
|
||||
|
||||
|
||||
class PurchaseOrderLine(models.Model):
|
||||
_inherit = "purchase.order.line"
|
||||
|
||||
@api.onchange("order_id")
|
||||
def _onchange_order_id_set_analytic(self):
|
||||
for line in self:
|
||||
if "account_analytic_id" in line._fields and line.order_id.analytic_account_id:
|
||||
line.account_analytic_id = line.order_id.analytic_account_id
|
||||
|
||||
@api.onchange("product_id")
|
||||
def _onchange_product_id_set_analytic(self):
|
||||
for line in self:
|
||||
if "account_analytic_id" in line._fields and line.order_id.analytic_account_id:
|
||||
line.account_analytic_id = line.order_id.analytic_account_id
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if vals.get("account_analytic_id") or not vals.get("order_id"):
|
||||
continue
|
||||
order = self.env["purchase.order"].browse(vals["order_id"])
|
||||
if order.analytic_account_id:
|
||||
vals["account_analytic_id"] = order.analytic_account_id.id
|
||||
return super().create(vals_list)
|
||||
|
||||
def write(self, vals):
|
||||
vals = dict(vals)
|
||||
if "account_analytic_id" not in vals:
|
||||
analytic = self[:1].order_id.analytic_account_id
|
||||
if analytic:
|
||||
vals["account_analytic_id"] = analytic.id
|
||||
return super().write(vals)
|
||||
|
||||
def _prepare_account_move_line(self, move=False):
|
||||
vals = super()._prepare_account_move_line(move=move)
|
||||
analytic = self.order_id.analytic_account_id
|
||||
if analytic and not vals.get("analytic_account_id"):
|
||||
vals["analytic_account_id"] = analytic.id
|
||||
return vals
|
||||
@@ -0,0 +1,56 @@
|
||||
from odoo import _, api, fields, models
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
class PurchaseTeam(models.Model):
|
||||
_name = "purchase.team"
|
||||
_description = "Purchase Team"
|
||||
_order = "name"
|
||||
|
||||
name = fields.Char(required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
company_id = fields.Many2one(
|
||||
"res.company",
|
||||
string="Company",
|
||||
required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
user_id = fields.Many2one(
|
||||
"res.users",
|
||||
string="Team Leader",
|
||||
help="Responsible user for this Purchase Team.",
|
||||
)
|
||||
member_ids = fields.Many2many(
|
||||
"res.users",
|
||||
"purchase_team_res_users_rel",
|
||||
"team_id",
|
||||
"user_id",
|
||||
string="Members",
|
||||
)
|
||||
business_category_id = fields.Many2one(
|
||||
"crm.business.category",
|
||||
string="Business Category",
|
||||
required=True,
|
||||
default=lambda self: self.env["business.category.mixin"]._default_business_category_id(),
|
||||
ondelete="restrict",
|
||||
domain="[('company_id', '=', company_id)]",
|
||||
)
|
||||
|
||||
@api.onchange("company_id")
|
||||
def _onchange_company_id_business_category(self):
|
||||
for team in self:
|
||||
if team.business_category_id and team.business_category_id.company_id != team.company_id:
|
||||
team.business_category_id = False
|
||||
|
||||
@api.constrains("company_id", "business_category_id")
|
||||
def _check_business_category_company(self):
|
||||
for team in self:
|
||||
if not team.company_id or not team.business_category_id:
|
||||
continue
|
||||
if team.business_category_id.company_id != team.company_id:
|
||||
raise ValidationError(
|
||||
_(
|
||||
"Purchase Team '%s' must use a Business Category from company '%s'."
|
||||
)
|
||||
% (team.name, team.company_id.name)
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_purchase_team_user,purchase.team user,model_purchase_team,purchase.group_purchase_user,1,0,0,0
|
||||
access_purchase_team_manager,purchase.team manager,model_purchase_team,purchase.group_purchase_manager,1,1,1,1
|
||||
access_purchase_team_sysadmin,purchase.team sysadmin,model_purchase_team,base.group_system,1,1,1,1
|
||||
|
@@ -0,0 +1,14 @@
|
||||
id,name,model_id:id,domain_force,groups:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
purchase_order_business_category_rule_user_read_create,Purchase Order read/create by effective business category,purchase.model_purchase_order,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_user,1,0,1,0
|
||||
purchase_order_business_category_rule_user_write_own,Purchase Order write own by effective business category,purchase.model_purchase_order,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids),'|',('user_id','=',user.id),('create_uid','=',user.id)]",purchase.group_purchase_user,0,1,0,1
|
||||
purchase_order_business_category_rule_manager,Purchase Order full access for purchase manager in effective business category,purchase.model_purchase_order,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_manager,1,1,1,1
|
||||
purchase_order_business_category_rule_sysadmin,Purchase Order full access for system admin,purchase.model_purchase_order,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
purchase_team_business_category_rule_user,Purchase Team read by effective business category,model_purchase_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_user,1,0,0,0
|
||||
purchase_team_business_category_rule_manager,Purchase Team full access for purchase manager in effective business category,model_purchase_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_manager,1,1,1,1
|
||||
purchase_team_business_category_rule_sysadmin,Purchase Team full access for system admin,model_purchase_team,"[('company_id','in',user.company_ids.ids)]",base.group_system,1,1,1,1
|
||||
crm_business_category_rule_purchase_user,Business Category by user effective list for purchase user,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_user,1,0,0,0
|
||||
crm_business_category_rule_purchase_manager,Business Category access for purchase manager in effective business category,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_manager,1,1,1,1
|
||||
account_move_purchase_business_category_rule_user,Vendor Bill read by effective business category for purchase user,account.model_account_move,"[('company_id','in',user.company_ids.ids),('purchase_business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_user,1,0,0,0
|
||||
account_move_purchase_business_category_rule_manager,Vendor Bill access for purchase manager in effective business category,account.model_account_move,"[('company_id','in',user.company_ids.ids),('purchase_business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_manager,1,1,1,1
|
||||
account_move_line_purchase_business_category_rule_user,Journal Item read by effective business category for purchase user,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.purchase_business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_user,1,0,0,0
|
||||
account_move_line_purchase_business_category_rule_manager,Journal Item access for purchase manager in effective business category,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.purchase_business_category_id','in',user.effective_business_category_ids.ids)]",purchase.group_purchase_manager,1,1,1,1
|
||||
|
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_crm_business_category_form_purchase_analytic" model="ir.ui.view">
|
||||
<field name="name">crm.business.category.form.purchase.analytic</field>
|
||||
<field name="model">crm.business.category</field>
|
||||
<field name="inherit_id" ref="grt_crm_business_category.view_crm_business_category_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='description']/.." position="before">
|
||||
<group string="Purchasing">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="purchase_analytic_account_id" domain="[('company_id', '=', company_id)]"/>
|
||||
</group>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<menuitem id="menu_purchase_team"
|
||||
name="Team Purchases"
|
||||
parent="purchase.menu_purchase_root"
|
||||
sequence="45"
|
||||
action="action_purchase_team"
|
||||
groups="purchase.group_purchase_manager"/>
|
||||
|
||||
<menuitem id="menu_business_category_from_purchase"
|
||||
name="Business Categories"
|
||||
parent="purchase.menu_purchase_root"
|
||||
sequence="46"
|
||||
action="grt_crm_business_category.action_crm_business_category"
|
||||
groups="purchase.group_purchase_manager"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_purchase_order_form_business_category" model="ir.ui.view">
|
||||
<field name="name">purchase.order.form.business.category</field>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="inherit_id" ref="purchase.purchase_order_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='partner_id']" position="after">
|
||||
<field name="company_id" invisible="1"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="purchase_team_id"/>
|
||||
<field name="analytic_account_id" readonly="1"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_purchase_order_filter_business_category" model="ir.ui.view">
|
||||
<field name="name">purchase.order.search.business.category</field>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="inherit_id" ref="purchase.view_purchase_order_filter"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='user_id']" position="after">
|
||||
<field name="business_category_id"/>
|
||||
<field name="purchase_team_id"/>
|
||||
</xpath>
|
||||
<xpath expr="//search" position="inside">
|
||||
<filter string="Business Category" name="group_by_business_category" context="{'group_by': 'business_category_id'}"/>
|
||||
<filter string="Purchase Team" name="group_by_purchase_team" context="{'group_by': 'purchase_team_id'}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_move_form_purchase_business_category" model="ir.ui.view">
|
||||
<field name="name">account.move.form.purchase.business.category</field>
|
||||
<field name="model">account.move</field>
|
||||
<field name="inherit_id" ref="account.view_move_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='invoice_payment_term_id']" position="after">
|
||||
<field name="purchase_business_category_id" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
||||
<field name="purchase_analytic_account_id" attrs="{'invisible': [('move_type', 'not in', ('in_invoice', 'in_refund'))]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_purchase_order_pivot_business_category_report" model="ir.ui.view">
|
||||
<field name="name">purchase.order.pivot.business.category.report</field>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Purchases by Business Category">
|
||||
<field name="business_category_id" type="row"/>
|
||||
<field name="purchase_team_id" type="row"/>
|
||||
<field name="state" type="col"/>
|
||||
<field name="date_order" interval="month" type="col"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
<field name="id" type="measure" string="Orders"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_purchase_order_graph_business_category_report" model="ir.ui.view">
|
||||
<field name="name">purchase.order.graph.business.category.report</field>
|
||||
<field name="model">purchase.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Purchases by Business Category" type="bar">
|
||||
<field name="business_category_id"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_purchase_order_business_category_report" model="ir.actions.act_window">
|
||||
<field name="name">Purchases by Business Category</field>
|
||||
<field name="res_model">purchase.order</field>
|
||||
<field name="view_mode">tree,pivot,graph</field>
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
<field name="search_view_id" ref="view_purchase_order_filter_business_category"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_purchase_order_business_category_report"
|
||||
name="Purchases by Category"
|
||||
parent="purchase.menu_purchase_root"
|
||||
sequence="50"
|
||||
action="action_purchase_order_business_category_report"
|
||||
groups="purchase.group_purchase_user,purchase.group_purchase_manager,base.group_system"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_purchase_team_tree" model="ir.ui.view">
|
||||
<field name="name">purchase.team.tree</field>
|
||||
<field name="model">purchase.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id"/>
|
||||
<field name="active"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_purchase_team_form" model="ir.ui.view">
|
||||
<field name="name">purchase.team.form</field>
|
||||
<field name="model">purchase.team</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Purchase Team">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="company_id"/>
|
||||
<field name="user_id"/>
|
||||
<field name="member_ids" widget="many2many_tags"/>
|
||||
<field name="business_category_id" domain="[('company_id', '=', company_id)]"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_purchase_team" model="ir.actions.act_window">
|
||||
<field name="name">Team Purchases</field>
|
||||
<field name="res_model">purchase.team</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
@@ -34,6 +34,7 @@ plus a two-step approval flow: Sales Team Leader then Accounting Manager.
|
||||
"views/res_partner_views.xml",
|
||||
"views/crm_team_views.xml",
|
||||
"views/sale_order_views.xml",
|
||||
"views/sale_report_business_category_views.xml",
|
||||
"views/sale_team_menu_views.xml",
|
||||
],
|
||||
"installable": True,
|
||||
|
||||
Binary file not shown.
@@ -195,7 +195,7 @@ class SaleOrder(models.Model):
|
||||
@api.constrains("business_category_id", "team_id", "user_id")
|
||||
def _check_current_user_sales_scope(self):
|
||||
current_user = self.env.user
|
||||
if current_user.has_group("sales_team.group_sale_manager") or current_user.has_group("base.group_system"):
|
||||
if current_user.has_group("base.group_system"):
|
||||
return
|
||||
|
||||
effective_categories = current_user.effective_business_category_ids
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
id,name,model_id:id,domain_force,groups:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
sale_order_business_category_rule_user_read_create,Sale Order read/create by effective business category,sale.model_sale_order,"[('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,1,0
|
||||
sale_order_business_category_rule_user_write_own,Sale Order write own by effective business category,sale.model_sale_order,"[('business_category_id','in',user.effective_business_category_ids.ids),'|',('user_id','=',user.id),('create_uid','=',user.id)]",sales_team.group_sale_salesman,0,1,0,1
|
||||
sale_order_business_category_rule_manager,Sale Order full access for sales manager,sale.model_sale_order,"[(1,'=',1)]",sales_team.group_sale_manager,1,1,1,1
|
||||
sale_order_business_category_rule_manager,Sale Order full access for sales manager in effective business category,sale.model_sale_order,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
sale_order_business_category_rule_sysadmin,Sale Order full access for system admin,sale.model_sale_order,"[(1,'=',1)]",base.group_system,1,1,1,1
|
||||
crm_team_business_category_rule_sales_user,CRM Team read by effective business category for sales user,sales_team.model_crm_team,"[('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
crm_team_business_category_rule_sales_manager,CRM Team full access for sales manager,sales_team.model_crm_team,"[(1,'=',1)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_team_business_category_rule_sales_manager,CRM Team full access for sales manager in effective business category,sales_team.model_crm_team,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_business_category_rule_sales_user,Business Category by user effective list for sales user,grt_crm_business_category.model_crm_business_category,"[('id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
crm_business_category_rule_sales_manager,Business Category full access for sales manager,grt_crm_business_category.model_crm_business_category,"[(1,'=',1)]",sales_team.group_sale_manager,1,1,1,1
|
||||
crm_business_category_rule_sales_manager,Business Category access for sales manager in effective business category,grt_crm_business_category.model_crm_business_category,"[('company_id','in',user.company_ids.ids),('id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
customer_behavior_analysis_rule_sales_user,Customer Behavior Analysis by effective business category,model_customer_behavior_analysis,"[('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
customer_behavior_analysis_rule_sales_manager,Customer Behavior Analysis full access for sales manager,model_customer_behavior_analysis,"[(1,'=',1)]",sales_team.group_sale_manager,1,1,1,1
|
||||
customer_behavior_analysis_rule_sales_manager,Customer Behavior Analysis full access for sales manager in effective business category,model_customer_behavior_analysis,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
account_move_business_category_rule_sales_user,Invoice read by effective business category for sales user,account.model_account_move,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
account_move_business_category_rule_sales_manager,Invoice access for sales manager in effective business category,account.model_account_move,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
account_payment_business_category_rule_sales_user,Payment read by effective business category for sales user,account.model_account_payment,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
account_payment_business_category_rule_sales_manager,Payment access for sales manager in effective business category,account.model_account_payment,"[('company_id','in',user.company_ids.ids),('business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
account_move_line_business_category_rule_sales_user,Journal Item read by effective business category for sales user,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_salesman,1,0,0,0
|
||||
account_move_line_business_category_rule_sales_manager,Journal Item access for sales manager in effective business category,account.model_account_move_line,"[('company_id','in',user.company_ids.ids),('move_id.business_category_id','in',user.effective_business_category_ids.ids)]",sales_team.group_sale_manager,1,1,1,1
|
||||
|
||||
|
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_sale_order_pivot_business_category_report" model="ir.ui.view">
|
||||
<field name="name">sale.order.pivot.business.category.report</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<pivot string="Sales by Business Category">
|
||||
<field name="business_category_id" type="row"/>
|
||||
<field name="team_id" type="row"/>
|
||||
<field name="state" type="col"/>
|
||||
<field name="date_order" interval="month" type="col"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
<field name="id" type="measure" string="Orders"/>
|
||||
</pivot>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_sale_order_graph_business_category_report" model="ir.ui.view">
|
||||
<field name="name">sale.order.graph.business.category.report</field>
|
||||
<field name="model">sale.order</field>
|
||||
<field name="arch" type="xml">
|
||||
<graph string="Sales by Business Category" type="bar">
|
||||
<field name="business_category_id"/>
|
||||
<field name="amount_total" type="measure"/>
|
||||
</graph>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_sale_order_business_category_report" model="ir.actions.act_window">
|
||||
<field name="name">Sales by Business Category</field>
|
||||
<field name="res_model">sale.order</field>
|
||||
<field name="view_mode">tree,pivot,graph</field>
|
||||
<field name="context">{'group_by': 'business_category_id'}</field>
|
||||
<field name="search_view_id" ref="view_sales_order_filter_business_category"/>
|
||||
</record>
|
||||
|
||||
<menuitem id="menu_sale_order_business_category_report"
|
||||
name="Sales by Category"
|
||||
parent="sale.sale_order_menu"
|
||||
sequence="50"
|
||||
action="action_sale_order_business_category_report"
|
||||
groups="sales_team.group_sale_salesman,sales_team.group_sale_manager,base.group_system"/>
|
||||
</odoo>
|
||||
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import xmlrpc.client
|
||||
|
||||
URL = "http://localhost:8070"
|
||||
DB = "kanjabung_MRP"
|
||||
USER = "admin"
|
||||
PWD = "admin"
|
||||
|
||||
common = xmlrpc.client.ServerProxy("%s/xmlrpc/2/common" % URL)
|
||||
uid = common.authenticate(DB, USER, PWD, {})
|
||||
if not uid:
|
||||
raise SystemExit("AUTH_FAILED")
|
||||
models = xmlrpc.client.ServerProxy("%s/xmlrpc/2/object" % URL)
|
||||
|
||||
view_ids = models.execute_kw(
|
||||
DB, uid, PWD,
|
||||
"ir.ui.view", "search_read",
|
||||
[[("model", "=", "hr.expense")]],
|
||||
{"fields": ["id", "name", "key"], "limit": 200, "order": "id asc"},
|
||||
)
|
||||
|
||||
print("hr.expense view candidates:")
|
||||
for v in view_ids:
|
||||
print(v.get("id"), "|", v.get("key"), "|", v.get("name"))
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import xmlrpc.client
|
||||
|
||||
URL = "http://localhost:8070"
|
||||
DB = "kanjabung_MRP"
|
||||
USER = "admin"
|
||||
PWD = "admin"
|
||||
|
||||
common = xmlrpc.client.ServerProxy("%s/xmlrpc/2/common" % URL)
|
||||
uid = common.authenticate(DB, USER, PWD, {})
|
||||
models = xmlrpc.client.ServerProxy("%s/xmlrpc/2/object" % URL)
|
||||
|
||||
imd = models.execute_kw(
|
||||
DB, uid, PWD,
|
||||
"ir.model.data", "search_read",
|
||||
[[("module", "=", "hr_expense"), ("model", "=", "ir.ui.view")]],
|
||||
{"fields": ["name", "res_id"], "limit": 500, "order": "name asc"},
|
||||
)
|
||||
|
||||
print("hr_expense view xmlids:")
|
||||
for row in imd:
|
||||
print("hr_expense.%s -> %s" % (row.get("name"), row.get("res_id")))
|
||||
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import xmlrpc.client
|
||||
|
||||
URL = "http://localhost:8070"
|
||||
DB = "kanjabung_MRP"
|
||||
USER = "admin"
|
||||
PWD = "admin"
|
||||
|
||||
common = xmlrpc.client.ServerProxy("%s/xmlrpc/2/common" % URL)
|
||||
uid = common.authenticate(DB, USER, PWD, {})
|
||||
models = xmlrpc.client.ServerProxy("%s/xmlrpc/2/object" % URL)
|
||||
|
||||
imd = models.execute_kw(
|
||||
DB, uid, PWD,
|
||||
"ir.model.data", "search_read",
|
||||
[[("module", "=", "stock"), ("model", "=", "ir.ui.view")]],
|
||||
{"fields": ["name", "res_id"], "limit": 1200},
|
||||
)
|
||||
|
||||
for row in imd:
|
||||
name = row.get("name")
|
||||
if "move" in name or "picking" in name or "filter" in name:
|
||||
print("stock.%s -> %s" % (name, row.get("res_id")))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user