diff options
| author | HafidBuroiroh <hafidburoiroh09@gmail.com> | 2026-03-06 14:24:00 +0700 |
|---|---|---|
| committer | HafidBuroiroh <hafidburoiroh09@gmail.com> | 2026-03-06 14:24:00 +0700 |
| commit | f687d197ead268040d7f396eb26ea0035a6dac35 (patch) | |
| tree | a8e9cf9efa0cf38a51db89badbe383b5e0857afb /indoteknik_custom | |
| parent | 8d953f913aceb97faa026253b65d6159759f5a62 (diff) | |
<hafid> change request sourcing job order
Diffstat (limited to 'indoteknik_custom')
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/sourcing_job_order.py | 475 | ||||
| -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 | 247 |
5 files changed, 535 insertions, 200 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 9458cc41..d0fd4c22 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -192,7 +192,7 @@ 'views/update_depreciation_move_wizard_view.xml', 'views/commission_internal.xml', 'views/keywords.xml', - 'views/sourcing.xml' + 'views/sourcing.xml', 'views/token_log.xml', 'views/gudang_service.xml', 'views/kartu_stock.xml', diff --git a/indoteknik_custom/models/sourcing_job_order.py b/indoteknik_custom/models/sourcing_job_order.py index e015eaaa..6bb59c62 100644 --- a/indoteknik_custom/models/sourcing_job_order.py +++ b/indoteknik_custom/models/sourcing_job_order.py @@ -158,7 +158,6 @@ class SourcingJobOrder(models.Model): if self.env.uid != self.create_uid.id and not self.env.user.has_group('indoteknik_custom.group_role_merchandiser'): raise UserError("❌ Hanya Sales dan Merchandiser yang boleh mengedit Sourcing Job.") - # --- Simpan data lama sebelum write (buat pembanding) old_data = {} for rec in self: old_data[rec.id] = { @@ -179,18 +178,15 @@ class SourcingJobOrder(models.Model): for rec in self: rec._log_product_assets_upload() - # --- Bandingkan setelah write dan buat log for rec in self: changes = [] old = old_data.get(rec.id, {}) - # === Perubahan di field parent === if old.get('state') != rec.state: changes.append(f"State: <b>{old.get('state')}</b> → <b>{rec.state}</b>") if old.get('approval_sales') != rec.approval_sales: changes.append(f"Approval Status: <b>{old.get('approval_sales')}</b> → <b>{rec.approval_sales}</b>") - # === Perubahan di line === old_lines = old.get('line_data', {}) for line in rec.line_ids: old_line = old_lines.get(line.id) @@ -221,7 +217,6 @@ class SourcingJobOrder(models.Model): joined = "<br/>".join(sub_changes) changes.append(f"<b>{line.product_name}</b>:<br/>{joined}") - # Post ke chatter if changes: message = "<br/><br/>".join(changes) rec.message_post( @@ -278,11 +273,16 @@ class SourcingJobOrderLine(models.Model): order_id = fields.Many2one('sourcing.job.order', string='Job Order', ondelete='cascade') product_id = fields.Many2one('product.product', string='Product', ondelete='cascade') md_person_ids = fields.Many2one('res.users', string='MD Person', ondelete='cascade') - product_name = fields.Char(string='Nama Barang', required=True) + brand_id = fields.Many2one('x_manufactures', string='Manufactures', ondelete='cascade') + so_id = fields.Many2one('sale.order', string='SO Number', tracking=True, readonly=True) + product_name_md = fields.Char(string='Nama Barang') + descriptions_md = fields.Text(string='Deskripsi Barang') + + product_name = fields.Char(string='Nama Barang') + brand = fields.Char(string='Brand') code = fields.Char(string='SKU') budget = fields.Char(string='Expected Price') note = fields.Text(string='Note Sourcing') - brand = fields.Char(string='Brand') attachment_type = fields.Selection([ ('none', 'None'), ('pdf', '.PDF'), @@ -293,6 +293,7 @@ class SourcingJobOrderLine(models.Model): product_attachment_img = fields.Binary(string="Product Attachment") product_attachment_other = fields.Binary(string="Product Attachment") product_attachment_filename = fields.Char(string="Filename") + descriptions = fields.Text(string='Deskripsi / Spesifikasi') reason = fields.Text(string='Reason Unavailable') sla = fields.Char(string='SLA Product') @@ -309,8 +310,7 @@ class SourcingJobOrderLine(models.Model): ('draft', 'Unsource'), ('sourcing', 'On Sourcing'), ('sent', 'Approval Sent'), - ('approve', 'Approved'), - ('done', 'Done Sourcing'), + ('approve', 'Done Sourcing'), ('cancel', 'Unavailable') ], default='draft', tracking=True) product_type = fields.Selection([ @@ -323,16 +323,68 @@ class SourcingJobOrderLine(models.Model): string="Show for Sales", compute="_compute_show_for_sales", ) + show_salesperson = fields.Many2one( + 'res.users', + string="Salesperson", + ) + + so_state = fields.Selection( + [ + ('draft', 'Draft'), + ('cancel', 'Cancel'), + ('sale', 'Sale') + ], + string="SO State", + compute="_compute_so_data" + ) + + so_name = fields.Char( + string="SO Number", + compute="_compute_so_data" + ) + is_md_person = fields.Boolean( + string="Is MD Person", + compute="_compute_is_md_person" + ) + is_receiver = fields.Boolean( + string="Is MD Receiver", + compute="_compute_is_md_person" + ) + + is_given = fields.Boolean(string='Is Given', tracking=True) + given_to_id = fields.Many2one('res.users', string='Given To') + previous_md_id = fields.Many2one('res.users', string='Previous MD') @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) + tax = line.tax_id.amount / 100 + + if line.tax_id.price_include: + subtotal = subtotal / (1 + tax) + line.subtotal = subtotal + @api.depends('order_id.so_id.user_id', 'order_id.so_id.state', 'order_id.so_id.name') + def _compute_so_data(self): + for rec in self: + so = rec.order_id.so_id + if so: + rec.so_state = so.state if so.state in ['draft', 'sale'] else False + rec.so_name = so.name + else: + rec.so_state = False + rec.so_name = False + + def _compute_is_md_person(self): + current_user = self.env.user + for rec in self: + rec.is_md_person = bool(rec.md_person_ids == current_user) + rec.is_receiver = bool(rec.given_to_id == current_user) + @api.constrains('product_type', 'product_category', 'product_class') def _check_required_fields_for_md(self): for rec in self: @@ -356,6 +408,9 @@ class SourcingJobOrderLine(models.Model): order = self.env['sourcing.job.order'].browse(order_id) if order.state == 'taken' and order.line_ids.md_person_ids != self.env.user: raise UserError("❌ SJO sudah taken. Tidak boleh tambah line.") + if order.so_id: + vals['so_id'] = order.so_id.id + vals['show_salesperson'] = order.so_id.user_id.id rec = super().create(vals) return rec @@ -388,7 +443,7 @@ class SourcingJobOrderLine(models.Model): if total == 1: line = lines[0] - if line.state == 'done': + if line.state == 'approve': order.state = 'done' elif line.state == 'cancel': order.state = 'cancel' @@ -399,9 +454,9 @@ class SourcingJobOrderLine(models.Model): states = lines.mapped('state') all_cancel = all(s == 'cancel' for s in states) - all_done_or_cancel = all(s in ['done', 'cancel'] for s in states) - any_done = any(s == 'done' for s in states) - any_progress = any(s not in ['done', 'cancel', 'draft'] for s in states) + all_done_or_cancel = all(s in ['approve', 'cancel'] for s in states) + any_done = any(s == 'approve' for s in states) + any_progress = any(s not in ['approve', 'cancel', 'draft'] for s in states) if all_cancel: order.state = 'cancel' @@ -496,10 +551,12 @@ class SourcingJobOrderLine(models.Model): for line in self: if line.state != 'sourcing': - raise UserError(f"⚠️ Produk '{line.product_name}' bukan status Sourcing.") + raise UserError(f"⚠️ Produk '{line.product_name_md}' bukan status Sourcing.") if ( not line.vendor_id + or not line.product_name_md + or not brand_id or not line.price or line.price <= 0 or not line.tax_id or not line.subtotal or line.subtotal <= 0 @@ -507,7 +564,7 @@ class SourcingJobOrderLine(models.Model): or not line.product_category or not line.product_class ): - raise UserError(f"❌ Data produk '{line.product_name}' belum lengkap.") + raise UserError(f"❌ Data produk '{line.product_name_md}' belum lengkap.") activity_type = self.env.ref('mail.mail_activity_data_todo') @@ -519,10 +576,10 @@ class SourcingJobOrderLine(models.Model): line.activity_schedule( activity_type_id=activity_type.id, user_id=job.create_uid.id, - note=f"{self.env.user.name} meminta approval untuk produk '{line.product_name}' di SJO '{job.name}'.", + note=f"{self.env.user.name} meminta approval untuk produk '{line.product_name_md}' di SJO '{job.name}'.", ) - approved_lines_text += f"<li>{line.product_name} - {line.price or 0}</li>" + approved_lines_text += f"<li>{line.product_name_md} - {line.price or 0}</li>" line.message_post( body=f"📤 <b>Request approval dikirim (Multi)</b>", @@ -543,7 +600,7 @@ class SourcingJobOrderLine(models.Model): title="Multi Request Sent" ) - return {'type': 'ir.actions.client', 'tag': 'reload'} + # return {'type': 'ir.actions.client', 'tag': 'reload'} def action_ask_approval(self): @@ -562,6 +619,8 @@ class SourcingJobOrderLine(models.Model): if ( not line.vendor_id + or not line.product_name_md + or not line.brand_id or not line.price or line.price <= 0 or not line.tax_id or not line.subtotal or line.subtotal <= 0 @@ -578,14 +637,14 @@ class SourcingJobOrderLine(models.Model): line.activity_schedule( activity_type_id=activity_type.id, user_id=job.create_uid.id, - note=f"{self.env.user.name} meminta approval untuk produk '{line.product_name}' di SJO '{job.name}'.", + note=f"{self.env.user.name} meminta approval untuk produk '{line.product_name_md}' di SJO '{job.name}'.", ) line.message_post( body=( f"📤 <b>Request approval dikirim</b><br/>" f"Kepada: <b>{job.create_uid.name}</b><br/>" - f"Produk: <b>{line.product_name}</b>" + f"Produk: <b>{line.product_name_md}</b>" ), subtype_xmlid="mail.mt_comment" ) @@ -594,7 +653,7 @@ class SourcingJobOrderLine(models.Model): body=( f"📤 <b>Request approval line</b><br/>" f"<ul>" - f"<li><b>Produk:</b> {line.product_name}</li>" + f"<li><b>Produk:</b> {line.product_name_md}</li>" f"<li><b>MD:</b> {self.env.user.name}</li>" f"<li><b>Vendor:</b> {line.vendor_id.display_name if line.vendor_id else '-'}</li>" f"<li><b>Harga:</b> {line.price or 0}</li>" @@ -604,7 +663,7 @@ class SourcingJobOrderLine(models.Model): ) self.env.user.notify_success( - message=f"Request approval untuk '{line.product_name}' dikirim ke {job.create_uid.name}", + message=f"Request approval untuk '{line.product_name_md}' dikirim ke {job.create_uid.name}", title="Request Sent", ) @@ -615,7 +674,7 @@ class SourcingJobOrderLine(models.Model): msg_text = ( f"📢 <b>Request Approval Produk</b>\n\n" f"🧾 <b>Sourcing Job:</b> <a href='{url}'>📎 {job.name}</a>\n" - f"📦 <b>Produk:</b> {line.product_name}\n" + f"📦 <b>Produk:</b> {line.product_name_md}\n" f"👤 <b>MD:</b> {self.env.user.name}\n" f"💰 <b>Harga:</b> {line.price or 0}\n" f"📅 <b>Tanggal:</b> {fields.Datetime.now().strftime('%d-%m-%Y %H:%M')}\n\n" @@ -637,13 +696,101 @@ class SourcingJobOrderLine(models.Model): return {'type': 'ir.actions.client', 'tag': 'reload'} def action_approve_approval(self): + ProductProduct = self.env['product.product'] + PurchasePricelist = self.env['purchase.pricelist'] + SaleOrderLine = self.env['sale.order.line'] + for rec in self: job = rec.order_id if job.create_uid != self.env.user: raise UserError("❌ Hanya pembuat Sourcing Job yang bisa approve.") - rec.state = 'approve' + rec.write({'state': 'approve'}) + + product = False + if rec.code: + product = ProductProduct.search([ + ('default_code', '=', rec.code), + ('active', '=', True) + ], limit=1) + + if product: + rec.product_id = product.id + + self.env.user.notify_warning( + message=f"SKU {rec.code} sudah ada. Tidak dibuat ulang.", + title="SKU Exists" + ) + + else: + type_map = { + 'servis': 'service', + 'product': 'product', + 'consu': 'consu', + } + + product = ProductProduct.create({ + 'name': rec.product_name_md, + 'default_code': rec.code or False, + 'description': rec.descriptions_md or '', + 'type': type_map.get(rec.product_type, 'product'), + 'categ_id': rec.product_category.id if rec.product_category else False, + 'x_manufacture': rec.brand_id.id if rec.brand_id else False, + 'standard_price': rec.price or 0, + 'public_categ_ids': [(6, 0, rec.product_class.ids)] if rec.product_class else False, + 'active': True, + 'sourcing_job_id': job.id if job else False, + }) + + if not rec.code: + padded_id = str(product.id).zfill(7) + sku_auto = f"IT.{padded_id}" + product.default_code = sku_auto + rec.code = sku_auto + + rec.product_id = product.id + + jakarta_tz = rec.order_id._get_jakarta_today() + + purchase_price = PurchasePricelist.search([ + ('product_id', '=', product.id), + ('vendor_id', '=', rec.vendor_id.id), + ], order="human_last_update desc", limit=1) + + pricelist_vals = { + 'product_id': product.id, + 'vendor_id': rec.vendor_id.id, + 'product_price': rec.price or 0, + 'include_price': rec.price or 0, + 'taxes_product_id': rec.tax_id.id if rec.tax_id else False, + 'brand_id': product.x_manufacture.id if product.x_manufacture else False, + 'human_last_update': jakarta_tz, + 'is_winner': True, + } + + if not purchase_price: + PurchasePricelist.create(pricelist_vals) + + elif purchase_price.product_price != (rec.price or 0): + purchase_price.write(pricelist_vals) + + if rec.so_id and not rec.exported_to_so: + so = rec.so_id + + so_line_new = SaleOrderLine.new({ + "order_id": so.id, + "product_id": product.id, + "product_uom_qty": rec.quantity or 1, + "price_unit": rec.price or 0, + "name": rec.product_name_md, + }) + + so_line_new.product_id_change() + vals = SaleOrderLine._convert_to_write(so_line_new._cache) + SaleOrderLine.create(vals) + + rec.exported_to_so = True activities = self.env['mail.activity'].search([ ('res_model', '=', rec._name), @@ -663,7 +810,7 @@ class SourcingJobOrderLine(models.Model): body=( f"✅ <b>Approval produk disetujui</b><br/>" f"<ul>" - f"<li><b>Produk:</b> {rec.product_name}</li>" + f"<li><b>Produk:</b> {rec.product_name_md}</li>" f"<li><b>Vendor:</b> {rec.vendor_id.display_name if rec.vendor_id else '-'}</li>" f"<li><b>Harga:</b> {rec.price or 0}</li>" f"<li><b>Disetujui oleh:</b> {self.env.user.name}</li>" @@ -674,17 +821,20 @@ class SourcingJobOrderLine(models.Model): if rec.md_person_ids: rec.md_person_ids.notify_success( - message=f"Produk '{rec.product_name}' telah di-approve sales.", + message=f"Produk '{rec.product_name_md}' telah di-approve sales.", title="Approval Approved" ) self.env.user.notify_success( - message=f"Produk '{rec.product_name}' berhasil di-approve.", + message=f"Produk '{rec.product_name_md}' berhasil di-approve.", title="Approved" ) return {'type': 'ir.actions.client', 'tag': 'reload'} + def action_multi_approve(self): + self.action_approve_approval() + def action_reject_approval(self): self.ensure_one() @@ -703,132 +853,6 @@ class SourcingJobOrderLine(models.Model): } } - def action_convert(self): - ProductProduct = self.env['product.product'] - PurchasePricelist = self.env['purchase.pricelist'] - - for line in self: - job = line.order_id - - if line.md_person_ids and line.md_person_ids != self.env.user: - raise UserError("❌ Hanya MD Person pada line ini yang bisa convert.") - - if line.state != 'approve': - raise UserError("⚠️ Convert hanya bisa setelah sales approve.") - - existing = False - if line.code: - existing = ProductProduct.search([('default_code', '=', line.code)], limit=1) - - if existing: - line.product_id = existing.id - line.state = 'done' - - job.message_post( - body=f"ℹ️ SKU <b>{line.code}</b> sudah ada. Produk existing dipakai.", - subtype_xmlid="mail.mt_comment", - ) - - line.message_post( - body=( - f"ℹ️ <b>SKU sudah ada di sistem</b><br/>" - f"Produk existing: <b>{existing.name}</b><br/>" - f"Tidak dibuat ulang, langsung linked." - ), - subtype_xmlid="mail.mt_comment", - ) - - self.env.user.notify_warning( - message=f"SKU {line.code} sudah ada. Tidak dibuat ulang.", - title="SKU Exists" - ) - continue - - type_map = { - 'servis': 'service', - 'product': 'product', - 'consu': 'consu', - } - - manufactures = self.env['x_manufactures'] - if line.brand: - manufactures = manufactures.search([('x_name', 'ilike', line.brand)], limit=1) - - new_product = ProductProduct.create({ - 'name': line.product_name, - 'default_code': line.code or False, - 'description': line.descriptions or '', - 'type': type_map.get(line.product_type, 'product'), - 'categ_id': line.product_category.id if line.product_category else False, - 'x_manufacture': manufactures.id if manufactures else False, - 'standard_price': line.price or 0, - 'public_categ_ids': [(6, 0, line.product_class.ids)] if line.product_class else False, - 'active': True, - 'sourcing_job_id': job.id if job else False, - }) - - if not line.code: - padded_id = str(new_product.id).zfill(7) - sku_auto = f"IT.{padded_id}" - new_product.default_code = sku_auto - line.code = sku_auto - - line.product_id = new_product.id - - jakarta_tz = line.order_id._get_jakarta_today() - - pricelist_vals = { - 'product_id': new_product.id, - 'vendor_id': line.vendor_id.id, - 'system_price': line.price or 0, - 'product_price': line.price or 0, - 'include_price': line.price or 0, - 'taxes_system_id': line.tax_id.id if line.tax_id else False, - 'taxes_product_id': line.tax_id.id if line.tax_id else False, - 'brand_id': new_product.x_manufacture.id if new_product.x_manufacture else False, - 'system_last_update': jakarta_tz, - 'human_last_update': jakarta_tz, - 'is_winner': True, - } - - PurchasePricelist.create(pricelist_vals) - - job.message_post( - body=( - f"🆕 <b>Produk berhasil dibuat dari sourcing line</b><br/>" - f"<ul>" - f"<li><b>Produk:</b> {new_product.name}</li>" - f"<li><b>SKU:</b> {line.code}</li>" - f"<li><b>Vendor:</b> {line.vendor_id.display_name if line.vendor_id else '-'}</li>" - f"<li><b>Harga:</b> {line.price or 0}</li>" - f"</ul>" - f"<i>Produk sudah masuk ke master product & pricelist.</i>" - ), - subtype_xmlid="mail.mt_comment" - ) - line.message_post( - body=( - f"✅ <b>Produk berhasil dikonversi</b><br/>" - f"<ul>" - f"<li><b>Nama Produk:</b> {new_product.name}</li>" - f"<li><b>SKU:</b> {line.code}</li>" - f"<li><b>Vendor:</b> {line.vendor_id.display_name if line.vendor_id else '-'}</li>" - f"<li><b>Harga Beli:</b> {line.price or 0}</li>" - f"</ul>" - f"<i>Produk sudah masuk ke master product & pricelist.</i>" - ), - subtype_xmlid="mail.mt_comment" - ) - - line.state = 'done' - - self.env.user.notify_success( - message=f"Produk {new_product.name} berhasil dikonversi.", - title="Convert Success" - ) - - return {'type': 'ir.actions.client', 'tag': 'reload'} - def action_cancel(self): for rec in self: if self.env.user != rec.md_person_ids: @@ -872,11 +896,24 @@ class SourcingJobOrderLine(models.Model): if not product: return template = product.product_tmpl_id + attribute_values = product.product_template_attribute_value_ids.mapped( + 'product_attribute_value_id.name' + ) + attribute_values_str = ', '.join(attribute_values) if attribute_values else '' + + # generate line name + line_name = ( + ('[' + product.default_code + '] ' if product.default_code else '') + + (product.name or '') + + (' (' + attribute_values_str + ')' if attribute_values_str else '') + + (' ' + product.short_spesification if product.short_spesification else '') + ) rec.code = product.default_code or rec.code - rec.product_name = product.name or rec.product_name + rec.product_name_md = product.name or rec.product_name_md + rec.descriptions_md = line_name.strip() or rec.descriptions_md rec.product_type = template.type or rec.product_type - rec.brand = product.x_manufacture.x_name or rec.brand + rec.brand_id = product.x_manufacture.id or rec.brand_id rec.product_category = template.categ_id.id or rec.product_category rec.product_class = [(6, 0, template.public_categ_ids.ids)] if template.public_categ_ids else [] @@ -946,6 +983,128 @@ class SourcingJobOrderLine(models.Model): } } + def action_open_give_wizard(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Give To MD', + 'res_model': 'sjo.give.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_line_id': self.id, + } + } + + def action_open_reject_given_wizard(self): + self.ensure_one() + return { + 'type': 'ir.actions.act_window', + 'name': 'Reject Request Give SJO Line', + 'res_model': 'sjo.reject.give.wizard', + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_line_id': self.id, + } + } + + def action_take_given(self): + for rec in self: + if self.env.user != rec.given_to_id: + raise UserError("Hanya MD yang diberikan Request yang bisa Take Sourcing") + + old_owner = rec.previous_md_id.name + new_owner = rec.given_to_id.name + receiver = rec.given_to_id + + rec.with_context(bypass_md_check=True).write({ + 'md_person_ids': rec.given_to_id.id, + 'given_to_id': False, + 'previous_md_id': False, + 'is_given': False, + }) + + rec._unlink_give_activity(receiver) + rec.message_post( + body=f"<b>{new_owner}<b/> Menerima Request Sourcing dari <b>{old_owner}<b/>" + ) + +class SjoGiveWizard(models.TransientModel): + _name = 'sjo.give.wizard' + _description = 'Give SJO Line Wizard' + + line_id = fields.Many2one('sourcing.job.order.line') + md_id = fields.Many2one('res.users', string='Give To', required=True, domain=lambda self: [ + ('groups_id', 'in', self.env.ref('base.group_user').ids), + ('groups_id', 'in', self.env.ref('indoteknik_custom.group_role_merchandiser').ids), + ('active', '=', True) + ]) + + def action_confirm(self): + self.ensure_one() + + line = self.line_id + + if self.env.user != line.md_person_ids: + raise UserError("Hanya Md Target yang bisa Confirm Give Sourcing") + + old_owner = line.md_person_ids.name + new_owner = self.md_id.name + + line.write({ + 'previous_md_id': line.md_person_ids.id, + 'given_to_id': self.md_id.id, + 'is_given': True, + }) + + activity_type = self.env.ref('mail.mail_activity_data_todo') + line.activity_schedule( + activity_type_id=activity_type.id, + user_id=self.md_id.id, + note="SJO Line diberikan ke Anda. Silakan Take atau Reject.", + ) + + line.message_post( + body=f""" + <b>MD {old_owner}</b> Mengirim Request Peralihan Sourcing Ke <b>{new_owner}</b> + """, + subtype_xmlid="mail.mt_comment" + ) + +class SjoRejectGiveWizard(models.TransientModel): + _name = 'sjo.reject.give.wizard' + _description = 'Reject Given SJO Line Wizard' + + line_id = fields.Many2one('sourcing.job.order.line', required=True) + reason = fields.Text(string="Reject Reason", required=True) + + def action_confirm(self): + self.ensure_one() + line = self.line_id + + if self.env.user != line.given_to_id: + raise UserError("Hanya Penerima Request yang bisa Reject Give") + + from_md = line.previous_md_id.name or "-" + receiver = line.given_to_id + rejector = self.env.user.name + + line._unlink_give_activity(receiver) + + line.with_context(bypass_md_check=True).write({ + 'given_to_id': False, + 'is_given': False, + }) + + line.message_post( + body=f""" + Request Peralihan dari <b>{from_md}</b> Rejected by <b>{rejector}</b><br/> + Alasan: {self.reason} + """, + subtype_xmlid="mail.mt_comment" + ) + class WizardExportSJOtoSO(models.TransientModel): _name = "wizard.export.sjo.to.so" _description = "Wizard Export SJO Products to SO" @@ -970,7 +1129,7 @@ class WizardExportSJOtoSO(models.TransientModel): ("order_id", "=", sjo_id), ("product_id", "!=", False), ("exported_to_so", "=", False), - ("state", "=", "done"), # optional: cuma yg done + ("state", "=", "approve"), # optional: cuma yg done ]) res["line_ids"] = [(6, 0, lines.ids)] diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index fed6193a..2a1764f0 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -228,4 +228,6 @@ access_wizard_export_sjo_to_so,wizard.export.sjo.to.so,model_wizard_export_sjo_t access_sourcing_job_order_line_import_wizard,sourcing.job.order.line.import.wizard,model_sourcing_job_order_line_import_wizard,base.group_user,1,1,1,1 access_sourcing_job_order_line_export_wizard,sourcing.job.order.line.export.wizard,model_sourcing_job_order_line_export_wizard,base.group_user,1,1,1,1 access_sourcing_job_order_line_template_wizard,sourcing.job.order.line.template.wizard,model_sourcing_job_order_line_template_wizard,base.group_user,1,1,1,1 +access_sjo_give_wizard_user,sjo.give.wizard user,model_sjo_give_wizard,base.group_user,1,1,1,1 +access_sjo_reject_give_wizard_user,sjo.reject.give.wizard user,model_sjo_reject_give_wizard,base.group_user,1,1,1,1 access_token_log,access.token.log,model_token_log,,1,1,1,1 diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 55e48300..818c5d30 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -241,6 +241,15 @@ <field name="active">True</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 f3bb5b33..ebdbaaf7 100644 --- a/indoteknik_custom/views/sourcing.xml +++ b/indoteknik_custom/views/sourcing.xml @@ -65,8 +65,9 @@ <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" + <tree string="Sourcing Job Orders" create="0" decoration-success="state=='done'" + decoration-info="state=='partial'" decoration-danger="state=='cancel'" decoration-warning="state=='taken'"> <field name="is_priority" optional="hide" readonly="1"/> @@ -91,7 +92,7 @@ <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"> + <form string="Sourcing Job Order" create="0"> <header> <button name="action_cancel" string="Cancel" @@ -152,7 +153,7 @@ <field name="progress_status" decoration-info="progress_status in ['🟡 On Track', '🔵 Ontime']" decoration-success="'🟢' in progress_status" - decoration-danger="'🔴' in progress_status" + decoration-danger="'🔴' in progress_status" decoration-muted="'⚫' in progress_status"/> </group> </group> @@ -168,9 +169,9 @@ <field name="quantity"/> <field name="md_person_ids" widget="many2one_avatar_user"/> <field name="state" readonly="1" widget="badge" - decoration-warning="state in ('sourcing','sent')" - decoration-info="state == 'approve'" - decoration-success="state == 'done'" + decoration-warning="state == 'sourcing'" + decoration-info="state == 'sent'" + decoration-success="state == 'approve'" decoration-danger="state == 'cancel'"/> <field name="note" optional="hide"/> <field name="budget" optional="hide"/> @@ -267,9 +268,9 @@ <page string="Product Line" groups="indoteknik_custom.group_role_sales" attrs="{'invisible': [('has_price_in_lines', '=', False)]}"> <field name="line_sales_view_ids"> <tree create="0" delete="0" - decoration-warning="state in ('sourcing','sent')" - decoration-info="state == 'approve'" - decoration-success="state == 'done'" + decoration-warning="state == 'sourcing'" + decoration-info="state == 'sent'" + decoration-success="state == 'approve'" decoration-danger="state == 'cancel'"> <button name="action_reopen_cancel" type="object" @@ -424,15 +425,17 @@ decoration-info="state in ('sent','approve')"> <field name="order_id" /> <field name="md_person_ids" widget="many2one_avatar_user"/> + <field name="show_salesperson" widget="many2one_avatar_user"/> <field name="brand"/> <field name="product_name"/> + <field name="quantity"/> + <field name="subtotal"/> <field name="descriptions"/> <field name="attachment_type" /> - <field name="quantity"/> <field name="state" widget="badge" decoration-muted="state=='draft'" decoration-warning="state=='sourcing'" - decoration-success="state in ('done','convert')" + decoration-success="state== 'approve'" decoration-danger="state=='cancel'"/> <field name="budget" optional="hide"/> <field name="note" optional="hide"/> @@ -459,6 +462,48 @@ </field> </record> + <record id="view_sjo_give_wizard_form" model="ir.ui.view"> + <field name="name">sjo.give.wizard.form</field> + <field name="model">sjo.give.wizard</field> + <field name="arch" type="xml"> + <form string="Give SJO Line"> + <group> + <field name="md_id"/> + </group> + <footer> + <button string="Confirm" + type="object" + name="action_confirm" + class="btn-primary"/> + <button string="Cancel" + special="cancel" + class="btn-secondary"/> + </footer> + </form> + </field> + </record> + + <record id="view_sjo_reject_give_wizard_form" model="ir.ui.view"> + <field name="name">sjo.reject.give.wizard.form</field> + <field name="model">sjo.reject.give.wizard</field> + <field name="arch" type="xml"> + <form string="Reject SJO Line"> + <group> + <field name="reason" placeholder="Alasan reject..."/> + </group> + <footer> + <button name="action_confirm" + string="Confirm Reject" + type="object" + class="btn-danger"/> + <button string="Cancel" + class="btn-secondary" + special="cancel"/> + </footer> + </form> + </field> + </record> + <record id="view_sourcing_job_order_line_form" model="ir.ui.view"> <field name="name">sourcing.job.order.line.form</field> <field name="model">sourcing.job.order.line</field> @@ -478,14 +523,7 @@ class="btn-secondary" icon="fa-times" groups="indoteknik_custom.group_role_merchandiser" - attrs="{'invisible': [('state', 'in', ['cancel', 'done'])]}"/> - - <button name="action_convert" - string="Convert Lines" - type="object" - class="btn-success" - groups="indoteknik_custom.group_role_merchandiser" - attrs="{'invisible': [('state', '!=', 'approve')]}"/> + attrs="{'invisible': [('state', 'in', ['cancel', 'approve'])]}"/> <button name="action_ask_approval" string="Ask Approval" @@ -506,11 +544,34 @@ groups="indoteknik_custom.group_role_sales" attrs="{'invisible': [('state', '!=', 'sent')]}"/> + <button name="action_open_give_wizard" + type="object" + string="Give to Other" + class="btn-primary" + attrs="{'invisible': ['|', '|', ('state', '!=', 'sourcing'), ('is_md_person', '=', False), ('is_given', '=', True)]}"/> + + <button name="action_take_given" + type="object" + string="Take" + class="btn-primary" + attrs="{'invisible': ['|', + ('is_given','=',False), + ('is_receiver','=',False) + ]}"/> + + <button name="action_open_reject_given_wizard" + type="object" + string="Reject" + attrs="{'invisible': ['|', + ('is_given','=',False), + ('is_receiver','=',False) + ]}"/> + <field name="state" widget="statusbar" - statusbar_visible="draft,sourcing,sent,approve,done,cancel" attrs="{'invisible': [('state', '!=', 'cancel')]}"/> + statusbar_visible="draft,sourcing,sent,approve,cancel" attrs="{'invisible': [('state', '!=', 'cancel')]}"/> <field name="state" widget="statusbar" - statusbar_visible="draft,sourcing,sent,approve,done" attrs="{'invisible': [('state', '=', 'cancel')]}"/> + statusbar_visible="draft,sourcing,sent,approve" attrs="{'invisible': [('state', '=', 'cancel')]}"/> </header> <sheet > <widget name="web_ribbon" @@ -527,30 +588,44 @@ </h1> <group col="2"> <group> - <field name="create_uid" invisible="1"/> + <separator string="MD Edit"/> + <field name="is_md_person" invisible="1"/> + <field name="is_given" invisible="1"/> + <field name="is_receiver" invisible="1"/> <field name="md_person_ids" widget="many2one_avatar_user" readonly="1"/> - <field name="product_id" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="code" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="brand" required="1" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="product_name" required="1" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="descriptions" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="quantity" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="price" required="1" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> + <field name="brand_id" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="product_id" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="descriptions_md" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="code" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="product_name_md" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="price" required="1" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> <field name="now_price" force_save="1" readonly="1"/> - <field name="last_updated_price" readonly="1"/> - <field name="vendor_id" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="tax_id" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="subtotal" readonly="1" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="sla" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> + <field name="last_updated_price" force_save="1"/> + <field name="vendor_id" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="tax_id" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="subtotal" readonly="1" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="product_category" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="product_type" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> + <field name="product_class" widget="many2many_tags" attrs="{'readonly': [('state', 'in', ['cancel', 'approve'])]}"/> </group> <group> - <field name="product_category" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="product_type" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="product_class" widget="many2many_tags" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="note" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="budget" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> - <field name="attachment_type" attrs="{'readonly': [('state', 'in', ['done', 'cancel', 'approve'])]}"/> + <separator string="Sales Input"/> + <field name="create_uid" widget="many2one_avatar_user" readonly="1"/> + <field name="show_salesperson" widget="many2one_avatar_user" readonly="1"/> + <field name="so_id" readonly="1"/> + <field name="so_state" widget="badge" readonly="1" + decoration-success="so_state == 'sale'" + decoration-warning="so_state == 'draft'" + decoration-danger="so_state == 'cancel'"/> + <field name="brand" readonly="1"/> + <field name="product_name" readonly="1"/> + <field name="descriptions" readonly="1"/> + <field name="quantity" readonly="1"/> + <field name="sla" readonly="1"/> + <field name="note" readonly="1"/> + <field name="budget" readonly="1"/> + <field name="attachment_type" readonly="1"/> <field name="product_attachment_pdf" filename="product_attachment_filename" @@ -568,7 +643,7 @@ </group> </group> <notebook> - <page string="Cancel Reason" attrs="{'invisible': [('state', 'in', ['done', 'approve'])]}"> + <page string="Cancel Reason" attrs="{'invisible': [('state', 'in', ['approve'])]}"> <group> <field name="reason"/> </group> @@ -614,4 +689,94 @@ action="action_sourcing_job_order_md" groups="indoteknik_custom.group_role_merchandiser" sequence="91"/> + + <!-- Approval Per line sales --> + <record id="view_sjo_line_approval_search" model="ir.ui.view"> + <field name="name">sourcing.job.order.line.approval.search</field> + <field name="model">sourcing.job.order.line</field> + <field name="arch" type="xml"> + <search string="Search Sourcing Approval"> + + <field name="product_name"/> + <field name="brand"/> + <field name="so_id"/> + + <filter name="filter_sent" + string="Sent" + domain="[('state','=','sent')]"/> + + <group expand="0" string="Group By"> + <filter name="group_so" + string="Sales Order" + context="{'group_by':'so_id'}"/> + </group> + + </search> + </field> + </record> + + <record id="view_sjo_line_approval_tree" model="ir.ui.view"> + <field name="name">sourcing.job.order.line.approval.tree</field> + <field name="model">sourcing.job.order.line</field> + <field name="arch" type="xml"> + <tree string="Approval Sourcing Line" + create="0" + edit="0" + delete="0" + decoration-warning="state in ('sourcing','sent')" + decoration-success="state == 'approve'" + decoration-danger="state == 'cancel'"> + + <field name="so_id"/> + <field name="order_id"/> + <field name="code"/> + <field name="brand"/> + <field name="product_name"/> + <field name="quantity"/> + <field name="price"/> + <field name="vendor_id"/> + <field name="subtotal"/> + <field name="show_salesperson" widget="many2one_avatar_user"/> + + <field name="state" + widget="badge" + decoration-warning="state in ('sourcing','sent')" + decoration-success="state == 'approve'" + decoration-danger="state == 'cancel'"/> + </tree> + </field> + </record> + + <record id="action_multi_approve_sjo_line" model="ir.actions.server"> + <field name="name">Approve Selected Lines</field> + <field name="model_id" ref="model_sourcing_job_order_line"/> + <field name="binding_model_id" ref="model_sourcing_job_order_line"/> + <field name="binding_view_types">list</field> + <field name="state">code</field> + <field name="code"> + records.action_multi_approve() + </field> + </record> + + <record id="action_sourcing_line_approval" model="ir.actions.act_window"> + <field name="name">Approval Sourcing Line</field> + <field name="res_model">sourcing.job.order.line</field> + <field name="view_mode">tree</field> + <field name="view_id" ref="view_sjo_line_approval_tree"/> + <field name="search_view_id" ref="view_sjo_line_approval_search"/> + <field name="context"> + { + 'search_default_filter_sent': 1, + 'search_default_group_so': 1 + } + </field> + <field name="groups_id" eval="[(4, ref('indoteknik_custom.group_role_sales'))]"/> + </record> + + <menuitem id="menu_sourcing_line_approval" + name="Approval Sourcing Line" + parent="indoteknik_custom.menu_monitoring_in_sale" + action="action_sourcing_line_approval" + sequence="110" + groups="indoteknik_custom.group_role_sales"/> </odoo> |
