summaryrefslogtreecommitdiff
path: root/indoteknik_custom
diff options
context:
space:
mode:
authorHafidBuroiroh <hafidburoiroh09@gmail.com>2026-03-06 14:24:00 +0700
committerHafidBuroiroh <hafidburoiroh09@gmail.com>2026-03-06 14:24:00 +0700
commitf687d197ead268040d7f396eb26ea0035a6dac35 (patch)
treea8e9cf9efa0cf38a51db89badbe383b5e0857afb /indoteknik_custom
parent8d953f913aceb97faa026253b65d6159759f5a62 (diff)
<hafid> change request sourcing job order
Diffstat (limited to 'indoteknik_custom')
-rwxr-xr-xindoteknik_custom/__manifest__.py2
-rw-r--r--indoteknik_custom/models/sourcing_job_order.py475
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv2
-rw-r--r--indoteknik_custom/views/ir_sequence.xml9
-rw-r--r--indoteknik_custom/views/sourcing.xml247
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>