diff options
| author | HafidBuroiroh <hafidburoiroh09@gmail.com> | 2025-10-24 11:24:58 +0700 |
|---|---|---|
| committer | HafidBuroiroh <hafidburoiroh09@gmail.com> | 2025-10-24 11:24:58 +0700 |
| commit | f4a1e2917d550eb205e33b058f07e7edbf8029c8 (patch) | |
| tree | 757cef98c3c94ce7327fff3bbadd06a269f4fa88 | |
| parent | 51ebcf6f9b57c61efabb9e4e0b4fc8d5bc058512 (diff) | |
<hafid> sjo half
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 3 | ||||
| -rwxr-xr-x | indoteknik_custom/models/__init__.py | 1 | ||||
| -rw-r--r-- | indoteknik_custom/models/sourcing.py | 65 | ||||
| -rw-r--r-- | indoteknik_custom/models/sourcing_job_order.py | 339 | ||||
| -rwxr-xr-x | indoteknik_custom/security/ir.model.access.csv | 2 | ||||
| -rw-r--r-- | indoteknik_custom/views/ir_sequence.xml | 9 | ||||
| -rw-r--r-- | indoteknik_custom/views/sourcing.xml | 182 |
7 files changed, 534 insertions, 67 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index cf7cf1e4..3c3c9a95 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -183,7 +183,8 @@ 'views/letter_receivable.xml', 'views/letter_receivable_mail_template.xml', # 'views/reimburse.xml', - 'views/sj_tele.xml' + 'views/sj_tele.xml', + 'views/sourcing.xml' ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 6dc61277..cb110342 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -160,3 +160,4 @@ from . import update_date_planned_po_wizard from . import unpaid_invoice_view from . import letter_receivable from . import sj_tele +from . import sourcing_job_order diff --git a/indoteknik_custom/models/sourcing.py b/indoteknik_custom/models/sourcing.py deleted file mode 100644 index 62540700..00000000 --- a/indoteknik_custom/models/sourcing.py +++ /dev/null @@ -1,65 +0,0 @@ -from odoo import models, fields, api -from odoo.exceptions import UserError -import requests -import json -import logging, subprocess - -_logger = logging.getLogger(__name__) - -class SourcingOrderJob(models.Model): - _name = 'sourcing.job.order' - _descriptions = 'Sourcing Job Order MD' - _rec_name = 'name' - _inherit = ['mail.thread', 'mail.activity.mixin'] - - name = fields.Char(string='Job Number', default='name', copy=False, readonly=True) - leads_id = fields.Many2One('crm.lead', string='Leads Number') - product_name = fields.Char(string='Nama Barang', required=True) - descriptions = fields.Text(string='Deskripsi / Spesifikasi', required=True) - quantity = fields.Float(string='Quantity Product', required=True) - eta_sales = fiels.Date(string='Expected Ready') - sla_product = fields.Date(string='Product Ready Date') - price = fields.Float(string='Harga Product Vendor') - vendor_id = fields.Many2One('res.partner', string="Vendor") - tax_id = fields.Many2One('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)]) - user_id = fields.Many2One('res.users', string='MD Person') - subtotal = fields.Float(string='Total Purchase', compute='_compute_subtotal_purchase') - state = fields.Selection([ - ('draft', 'Untaken'), - ('taken', 'On Sourcing'), - ('done', 'Complete'), - ('cancel', 'Cancel') - ], string='Status') - - @api.depends('quantity', 'price', 'tax_id') - def _compute_subtotal_purchase(self): - """Menghitung subtotal termasuk pajak.""" - for record in self: - subtotal = (record.quantity or 0.0) * (record.price or 0.0) - if record.tax_id: - tax_amount = subtotal * (record.tax_id.amount / 100) - subtotal += tax_amount - - record.subtotal = subtotal - - @api.model - def create(self, vals): - if not (self.env.user.has_group('indoteknik_custom.group_role_sales') or self.env.user.has_group('indoteknik_custom.group_role_merchandiser')): - raise UserError("❌ Hanya Sales dan Merchandiser yang boleh membuat Sourcing Job.") - - if vals.get('name', 'New') == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('sourcing.job.order') or 'New' - - if self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): - vals['user_id'] = self.env.user.id - vals['state'] = 'taken' - - return super().create(vals) - - def write(self, vals): - if not (self.env.user.has_group('indoteknik_custom.group_role_sales') or self.env.user.has_group('indoteknik_custom.group_role_merchandiser')): - raise UserError("❌ Hanya Sales dan Merchandiser yang boleh membuat Sourcing Job.") - - return super().write(vals) - - diff --git a/indoteknik_custom/models/sourcing_job_order.py b/indoteknik_custom/models/sourcing_job_order.py new file mode 100644 index 00000000..3d4a404e --- /dev/null +++ b/indoteknik_custom/models/sourcing_job_order.py @@ -0,0 +1,339 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import UserError +import logging + +_logger = logging.getLogger(__name__) + + +class SourcingJobOrder(models.Model): + _name = 'sourcing.job.order' + _description = 'Sourcing Job Order MD' + _rec_name = 'name' + _inherit = ['mail.thread', 'mail.activity.mixin'] + + name = fields.Char(string='Job Number', default='New', copy=False, readonly=True) + leads_id = fields.Many2one('crm.lead', string='Leads Number') + user_id = fields.Many2one('res.users', string='MD Person', tracking=True) + state = fields.Selection([ + ('draft', 'Untaken'), + ('taken', 'On Sourcing'), + ('done', 'Complete'), + ('cancel', 'Cancelled') + ], string='Status', default='draft', tracking=True) + approval_sales = fields.Selection([ + ('draft', 'Requested'), + ('approve', 'Approved'), + ], string='Approval Sales', tracking=True) + takeover_request = fields.Many2one( + 'res.users', + string='Takeover Requested By', + readonly=True, + tracking=True, + help='MD yang meminta takeover' + ) + can_request_takeover = fields.Boolean( + compute="_compute_can_request_takeover" + ) + can_approve_takeover = fields.Boolean( + compute="_compute_can_approve_takeover" + ) + + eta_sales = fields.Date(string='Expected Ready') + sla_product = fields.Date(string='Product Ready Date') + cancel_reason = fields.Text(string="Reason for Cancel", tracking=True) + product_assets = fields.Binary(string="Product Assets (PDF)") + + line_ids = fields.One2many('sourcing.job.order.line', 'order_id', string='Products') + + total_amount = fields.Float(string="Total Purchase", compute='_compute_total_amount') + + line_sales_input_ids = fields.One2many( + 'sourcing.job.order.line', 'order_id', + string='Sales Input Lines', + domain=['|', ('price', '=', 0), ('price', '=', False)] + ) + line_md_edit_ids = fields.One2many( + 'sourcing.job.order.line', 'order_id', + string='MD Edit Lines' + ) + line_sales_view_ids = fields.One2many( + 'sourcing.job.order.line', 'order_id', + string='Sales View Lines', + domain=[('price', '>', 0)] + ) + + has_price_in_lines = fields.Boolean( + string='Has Line with Price', + compute='_compute_has_price_in_lines', + ) + + + @api.depends('line_ids.subtotal') + def _compute_total_amount(self): + for rec in self: + rec.total_amount = sum(line.subtotal for line in rec.line_ids) + + @api.depends('line_ids.price', 'line_ids.vendor_id') + def _compute_has_price_in_lines(self): + for rec in self: + # Cek apakah ada minimal satu line yang sudah punya price > 0 dan vendor_id + has_price = any( + (line.price and line.price > 0 and line.vendor_id) + for line in rec.line_ids + ) + rec.has_price_in_lines = bool(has_price) + + @api.depends('user_id', 'takeover_request') + def _compute_can_request_takeover(self): + for rec in self: + current_user = self.env.user + rec.can_request_takeover = ( + rec.user_id and rec.user_id != current_user + ) + + @api.depends('user_id', 'takeover_request') + def _compute_can_approve_takeover(self): + for rec in self: + current_user = self.env.user + rec.can_approve_takeover = ( + rec.user_id == current_user and bool(rec.takeover_request) + ) + + @api.model + def create(self, vals): + """Hanya Sales & Merchandiser yang boleh membuat job.""" + if not (self.env.user.has_group('indoteknik_custom.group_role_sales') or + self.env.user.has_group('indoteknik_custom.group_role_merchandiser')): + raise UserError("❌ Hanya Sales dan Merchandiser yang boleh membuat Sourcing Job.") + + if vals.get('name', 'New') == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('sourcing.job.order') or 'New' + + if self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + vals['user_id'] = self.env.user.id + vals['state'] = 'taken' + + rec = super().create(vals) + return rec + + def write(self, vals): + context_action = self.env.context.get('from_action_take', False) + + if not (self.env.user.has_group('indoteknik_custom.group_role_sales') or + self.env.user.has_group('indoteknik_custom.group_role_merchandiser')): + raise UserError("❌ Hanya Sales dan Merchandiser yang boleh mengedit Sourcing Job.") + + for rec in self: + if not rec.user_id and rec.create_uid != self.env.user and not vals.get('user_id') and not context_action: + raise UserError("❌ SJO ini belum memiliki MD Person. Tidak dapat melakukan edit.") + + if rec.user_id != self.env.user and rec.create_uid != self.env.user and not context_action: + raise UserError("❌ Hanya MD Person dan Creator SJO ini yang bisa melakukan Edit.") + + return super().write(vals) + + def action_take(self): + for rec in self: + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("❌ Hanya Merchandiser yang dapat mengambil Sourcing Job.") + if rec.state != 'draft': + continue + rec.with_context(from_action_take=True).write({ + 'state': 'taken', + 'user_id': self.env.user.id + }) + rec.message_post(body=("Job <b>%s</b> diambil oleh %s") % (rec.name, self.env.user.name)) + + def action_multi_take(self): + untaken = self.filtered(lambda r: r.state == 'draft') + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("❌ Hanya Merchandiser yang bisa mengambil Sourcing Job.") + if not untaken: + raise UserError("Tidak ada record draft untuk diambil.") + + untaken.write({ + 'state': 'taken', + 'user_id': self.env.user.id, + }) + + def action_cancel(self): + for rec in self: + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("❌ Hanya Merchandiser yang dapat mengcancel Sourcing Job.") + + if rec.user_id and rec.user_id != self.env.user: + raise UserError("❌ Hanya MD Person yang memiliki SJO ini yang boleh melakukan Cancel.") + + if not rec.cancel_reason: + raise UserError("⚠️ Isi alasan pembatalan terlebih dahulu.") + + rec.write({'state': 'cancel'}) + rec.message_post(body=("Job <b>%s</b> dibatalkan oleh %s<br/>Alasan: %s") % + (rec.name, self.env.user.name, rec.cancel_reason)) + + def action_request_takeover(self): + for rec in self: + if not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): + raise UserError("❌ Hanya Merchandiser yang dapat Request Takeover Sourcing Job.") + + if rec.takeover_request: + raise UserError(f"SJO ini sudah memiliki request takeover dari {rec.takeover_request.name}. Tunggu approval dulu.") + + rec.takeover_request = self.env.user + + activity_type = self.env.ref('mail.mail_activity_data_todo') + self.env['mail.activity'].create({ + 'activity_type_id': activity_type.id, + 'note': f"{self.env.user.name} meminta approval untuk mengambil alih SJO '{rec.name}'.", + 'res_id': rec.id, + 'res_model_id': self.env['ir.model']._get_id('sourcing.job.order'), + 'user_id': rec.user_id.id, + }) + + rec.message_post( + body=f"<b>{self.env.user.name}</b> mengirimkan request takeover kepada <b>{rec.user_id.name}</b>.", + subtype_xmlid="mail.mt_comment" + ) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Request Sent', + 'message': f"Request takeover telah dikirim ke {rec.user_id.name}.", + 'type': 'success', + 'sticky': False, + } + } + def action_ask_approval(self): + for rec in self: + if rec.user_id != self.env.user: + raise UserError("❌ Hanya MD Person Sourcing Job ini yang dapat Request Approval.") + + rec.approval_sales = 'requested' + + activity_type = self.env.ref('mail.mail_activity_data_todo') + self.env['mail.activity'].create({ + 'activity_type_id': activity_type.id, + 'note': f"{self.env.user.name} meminta approval untuk mengambil alih SJO '{rec.name}'.", + 'res_id': rec.id, + 'res_model_id': self.env['ir.model']._get_id('sourcing.job.order'), + 'user_id': rec.user_id.id, + }) + + rec.message_post( + body=f"<b>{self.env.user.name}</b> mengirimkan request approval kepada <b>{rec.user_id.name}</b>.", + subtype_xmlid="mail.mt_comment" + ) + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Request Sent', + 'message': f"Request takeover telah dikirim ke {rec.user_id.name}.", + 'type': 'success', + 'sticky': False, + } + } + + + + def action_approve_takeover(self): + for rec in self: + if self.env.user != rec.user_id: + raise UserError("Hanya MD person yang saat ini memegang SJO yang dapat menyetujui takeover ini.") + if not rec.takeover_request: + raise UserError("Tidak ada request takeover yang perlu disetujui.") + + new_user = rec.takeover_request + + rec.user_id = new_user + rec.takeover_request = False + + activities = self.env['mail.activity'].search([ + ('res_id', '=', rec.id), + ('res_model', '=', 'sourcing.job.order'), + ('user_id', '=', self.env.user.id) + ]) + activities.unlink() + + rec.message_post( + body=f"Takeover disetujui oleh <b>{self.env.user.name}</b>. Sourcing Job berpindah ke <b>{new_user.name}</b>.", + subtype_xmlid="mail.mt_comment" + ) + def action_reject_takeover(self): + for rec in self: + if self.env.user != rec.user_id: + raise UserError("Hanya MD person yang saat ini memegang SJO yang dapat menolak takeover ini.") + if not rec.takeover_request: + raise UserError("Tidak ada request takeover yang perlu ditolak.") + + requester = rec.takeover_request + rec.takeover_request = False + + activities = self.env['mail.activity'].search([ + ('res_id', '=', rec.id), + ('res_model', '=', 'sourcing.job.order'), + ('user_id', '=', self.env.user.id) + ]) + activities.unlink() + + rec.message_post( + body=f"Takeover dari <b>{requester.name}</b> ditolak oleh <b>{self.env.user.name}</b>.", + subtype_xmlid="mail.mt_comment" + ) + + +class SourcingJobOrderLine(models.Model): + _name = 'sourcing.job.order.line' + _description = 'Sourcing Job Order Line' + + order_id = fields.Many2one('sourcing.job.order', string='Job Order', ondelete='cascade') + product_name = fields.Char(string='Nama Barang', required=True) + code = fields.Char(string='SKU') + descriptions = fields.Text(string='Deskripsi / Spesifikasi') + quantity = fields.Float(string='Quantity Product', required=True) + price = fields.Float(string='Purchase Price') + tax_id = fields.Many2one('account.tax', string='Tax', domain=[('active', '=', True)]) + vendor_id = fields.Many2one('res.partner', string="Vendor") + product_category = fields.Many2one('product.category', string="Product Category") + state = fields.Selection([ + ('draft', 'Unsource'), + ('sourcing', 'On Sourcing'), + ('done', 'Done Sourcing'), + ('cancel', 'Unavailable') + ], default='draft', tracking=True) + product_type = fields.Selection([ + ('consu', 'Consumable'), + ('servis', 'Service'), + ('product', 'Storable Product'), + ], default='product') + subtotal = fields.Float(string='Subtotal', compute='_compute_subtotal') + show_for_sales = fields.Boolean( + string="Show for Sales", + compute="_compute_show_for_sales", + ) + + @api.depends('quantity', 'price', 'tax_id') + def _compute_subtotal(self): + """Menghitung subtotal termasuk pajak.""" + for line in self: + subtotal = (line.quantity or 0.0) * (line.price or 0.0) + if line.tax_id: + subtotal += subtotal * (line.tax_id.amount / 100) + line.subtotal = subtotal + + @api.constrains('product_type', 'product_category') + def _check_required_fields_for_md(self): + for rec in self: + is_md = self.env.user.has_group('indoteknik_custom.group_role_merchandiser') + if is_md and (not rec.product_type or not rec.product_category): + raise ValidationError("MD wajib mengisi Product Type dan Product Category!") + + @api.depends('price', 'vendor_id', 'order_id') + def _compute_show_for_sales(self): + for rec in self: + rec.show_for_sales = bool( + rec.order_id and rec.price not in (None, 0) and rec.vendor_id + ) diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index ea6670eb..17372e48 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -202,3 +202,5 @@ access_unpaid_invoice_view,access.unpaid.invoice.view,model_unpaid_invoice_view, access_surat_piutang_user,surat.piutang user,model_surat_piutang,base.group_user,1,1,1,1 access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,base.group_user,1,1,1,1 access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1 +access_sourcing_job_order,access.sourcing_job_order,model_sourcing_job_order,base.group_system,1,1,1,1 +access_sourcing_job_order_line_user,sourcing.job.order.line,model_sourcing_job_order_line,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 4b8fec53..5ea7324b 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -218,7 +218,14 @@ <field name="number_next">1</field> <field name="number_increment">1</field> </record> - + <record id="seq_sourcing_job_order" model="ir.sequence"> + <field name="name">Sourcing Job Order</field> + <field name="code">sourcing.job.order</field> + <field name="prefix">SJO/%(year)s/</field> + <field name="padding">4</field> + <field name="number_next_actual">1</field> + <field name="number_increment">1</field> + </record> <record id="seq_refund_sale_order" model="ir.sequence"> <field name="name">Refund Sales Order</field> <field name="code">refund.sale.order</field> diff --git a/indoteknik_custom/views/sourcing.xml b/indoteknik_custom/views/sourcing.xml index e69de29b..f9f8f386 100644 --- a/indoteknik_custom/views/sourcing.xml +++ b/indoteknik_custom/views/sourcing.xml @@ -0,0 +1,182 @@ +<?xml version="1.0" encoding="UTF-8"?> +<odoo> + <record id="view_sourcing_job_order_tree" model="ir.ui.view"> + <field name="name">sourcing.job.order.tree</field> + <field name="model">sourcing.job.order</field> + <field name="arch" type="xml"> + <tree string="Sourcing Job Orders" + decoration-success="state=='done'" + decoration-danger="state=='cancel'" + decoration-warning="state=='taken'"> + <field name="name"/> + <!-- <field name="leads_id"/> --> + <field name="user_id" widget="many2one_avatar_user"/> + <field name="state" widget="badge"/> + <field name="create_date"/> + </tree> + </field> + </record> + + <record id="view_sourcing_job_order_form" model="ir.ui.view"> + <field name="name">sourcing.job.order.form</field> + <field name="model">sourcing.job.order</field> + <field name="arch" type="xml"> + <form string="Sourcing Job Order"> + <header> + <button name="action_take" + string="Take" + type="object" + class="btn-primary" + icon="fa-hand-paper-o" + groups="indoteknik_custom.group_role_merchandiser" + attrs="{'invisible': [('state', '!=', 'draft')]}"/> + + <button name="action_cancel" + string="Cancel" + type="object" + class="btn-secondary" + icon="fa-times" + groups="indoteknik_custom.group_role_merchandiser" + attrs="{'invisible': [('state', 'in', ['cancel', 'done'])]}"/> + <button name="action_request_takeover" + string="Request Takeover" + type="object" + class="btn-primary" + groups="indoteknik_custom.group_role_merchandiser" + attrs="{'invisible': [('can_request_takeover', '=', False)]}"/> + + <button name="action_approve_takeover" + string="Approve Takeover" + type="object" + class="btn-success" + groups="indoteknik_custom.group_role_merchandiser" + attrs="{'invisible': [('can_approve_takeover', '=', False)]}"/> + + <button name="action_reject_takeover" + string="Reject Takeover" + type="object" + class="btn-danger" + groups="indoteknik_custom.group_role_merchandiser" + attrs="{'invisible': [('can_approve_takeover', '=', False)]}"/> + + <field name="state" widget="statusbar" + statusbar_visible="draft,taken,done,cancel" + statusbar_colors='{"draft": "blue", "taken": "orange", "done": "green", "cancel": "red"}'/> + </header> + + <sheet> + <h1> + <field name="name" readonly="1"/> + </h1> + + <group> + <group> + <!-- <field name="leads_id"/> --> + <field name="eta_sales"/> + <field name="takeover_request" invisible="1"/> + <field name="can_request_takeover" invisible="1"/> + <field name="can_approve_takeover" invisible="1"/> + <field name="has_price_in_lines" invisible="1"/> + <field name="sla_product" groups="indoteknik_custom.group_role_merchandiser"/> + </group> + <group> + <field name="create_uid" readonly="1" widget="many2one_avatar_user"/> + <field name="user_id" readonly="1" widget="many2one_avatar_user"/> + </group> + </group> + + <notebook> + <page string="Product Line" groups="indoteknik_custom.group_role_sales" attrs="{'invisible': [('has_price_in_lines', '=', True)]}"> + <field name="line_sales_input_ids"> + <tree editable="bottom"> + <field name="product_name"/> + <field name="descriptions"/> + <field name="quantity"/> + </tree> + </field> + </page> + + <!-- MD EDIT --> + <page string="MD Lines" groups="indoteknik_custom.group_role_merchandiser"> + <field name="line_md_edit_ids"> + <tree editable="bottom"> + <field name="code"/> + <field name="product_name"/> + <field name="descriptions"/> + <field name="quantity"/> + <field name="price"/> + <field name="vendor_id"/> + <field name="tax_id"/> + <field name="subtotal"/> + </tree> + </field> + </page> + + <!-- SALES VIEW --> + <page string="Product Line" groups="indoteknik_custom.group_role_sales" attrs="{'invisible': [('has_price_in_lines', '=', False)]}"> + <field name="line_sales_view_ids"> + <tree> + <field name="code" readonly="1"/> + <field name="product_name" readonly="1"/> + <field name="descriptions" readonly="1"/> + <field name="quantity" readonly="1"/> + <field name="price" readonly="1"/> + <field name="vendor_id" readonly="1"/> + <field name="tax_id" readonly="1"/> + <field name="subtotal" readonly="1"/> + </tree> + </field> + </page> + + <page string="Documents"> + <field name="product_assets" widget="pdf_viewer"/> + </page> + + <page string="Cancel Reason" attrs="{'invisible': [('state', 'in', ['done'])]}"> + <group> + <field name="cancel_reason"/> + </group> + </page> + </notebook> + </sheet> + <div class="oe_chatter"> + <field name="message_follower_ids" widget="mail_followers"/> + <field name="message_ids" widget="mail_thread"/> + <field name="activity_ids" widget="mail_activity"/> + </div> + </form> + </field> + </record> + + <record id="action_sourcing_job_order_multi_take" model="ir.actions.server"> + <field name="name">Take Selected Jobs</field> + <field name="model_id" ref="model_sourcing_job_order"/> + <field name="binding_model_id" ref="model_sourcing_job_order"/> + <field name="binding_type">action</field> + <field name="state">code</field> + <field name="code">action = records.action_multi_take()</field> + <field name="groups_id" eval="[(4, ref('indoteknik_custom.group_role_merchandiser'))]"/> + </record> + + <record id="action_sourcing_job_order" model="ir.actions.act_window"> + <field name="name">Sourcing Job Orders</field> + <field name="res_model">sourcing.job.order</field> + <field name="view_mode">tree,form</field> + <field name="help" type="html"> + <p class="o_view_nocontent_smiling_face"> + Buat Sourcing Job Order baru di sini ✨ + </p> + </field> + </record> + + <menuitem id="menu_md_root" + name="MD" + parent="crm.crm_menu_root" + sequence="80"/> + + <menuitem id="menu_sourcing_job_order" + name="Sourcing Job Order" + parent="menu_md_root" + action="action_sourcing_job_order" + sequence="90"/> +</odoo> |
