summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-05-16 11:29:04 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-05-16 11:29:04 +0700
commit05305c6bf12da58cfadac7f1a901ef825d09cb61 (patch)
tree7f6f28397b4ad871131f7f28ea698117bcf1fe0f
parentc61cb9fcd3d01b4ffafaa6446f9fd68b09a88ff7 (diff)
parent4360e1fd9f3af2c18b19463773047d9939716069 (diff)
(andri) resolved confict di SO view
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py2
-rw-r--r--indoteknik_custom/models/approval_invoice_date.py2
-rw-r--r--indoteknik_custom/models/barcoding_product.py34
-rw-r--r--indoteknik_custom/models/mrp_production.py4
-rw-r--r--indoteknik_custom/models/product_pricelist.py6
-rwxr-xr-xindoteknik_custom/models/product_template.py631
-rwxr-xr-xindoteknik_custom/models/purchase_order.py125
-rwxr-xr-xindoteknik_custom/models/purchase_pricelist.py80
-rwxr-xr-xindoteknik_custom/models/sale_order.py510
-rw-r--r--indoteknik_custom/models/shipment_group.py9
-rw-r--r--indoteknik_custom/models/stock_picking.py4
-rw-r--r--indoteknik_custom/views/barcoding_product.xml5
-rwxr-xr-xindoteknik_custom/views/purchase_pricelist.xml7
-rwxr-xr-xindoteknik_custom/views/sale_order.xml492
-rw-r--r--indoteknik_custom/views/shipment_group.xml3
15 files changed, 1411 insertions, 503 deletions
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index c48c2372..4a3f40e2 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -96,7 +96,7 @@ class DueExtension(models.Model):
sales = self.env['sale.order'].browse(self.order_id.id)
- sales.action_confirm()
+ sales.with_context({'due_approve': True}).action_confirm()
self.order_id.due_id = self.id
self.approve_by = self.env.user.id
self.date_approve = datetime.utcnow()
diff --git a/indoteknik_custom/models/approval_invoice_date.py b/indoteknik_custom/models/approval_invoice_date.py
index 48546e55..b58d55d4 100644
--- a/indoteknik_custom/models/approval_invoice_date.py
+++ b/indoteknik_custom/models/approval_invoice_date.py
@@ -31,7 +31,7 @@ class ApprovalInvoiceDate(models.Model):
def button_approve(self):
if not self.env.user.is_accounting:
raise UserError("Hanya Accounting Yang Bisa Approve")
- self.move_id.invoice_date = self.date_doc_do
+ self.move_id.invoice_date = self.date_doc_do.date()
self.state = 'done'
self.approve_date = datetime.utcnow()
self.approve_by = self.env.user.id
diff --git a/indoteknik_custom/models/barcoding_product.py b/indoteknik_custom/models/barcoding_product.py
index 204c6128..335b481a 100644
--- a/indoteknik_custom/models/barcoding_product.py
+++ b/indoteknik_custom/models/barcoding_product.py
@@ -12,7 +12,7 @@ class BarcodingProduct(models.Model):
barcoding_product_line = fields.One2many('barcoding.product.line', 'barcoding_product_id', string='Barcoding Product Lines', auto_join=True)
product_id = fields.Many2one('product.product', string="Product", tracking=3)
quantity = fields.Float(string="Quantity", tracking=3)
- type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product')], string='Type', default='print')
+ type = fields.Selection([('print', 'Print Barcode'), ('barcoding', 'Add Barcode To Product'), ('barcoding_box', 'Add Barcode Box To Product'), ('multiparts', 'Multiparts Product')], string='Type', default='print')
barcode = fields.Char(string="Barcode")
qty_pcs_box = fields.Char(string="Quantity Pcs Box")
@@ -40,16 +40,29 @@ class BarcodingProduct(models.Model):
@api.onchange('product_id', 'quantity')
def _onchange_product_or_quantity(self):
- """Update barcoding_product_line based on product_id and quantity"""
if self.product_id and self.quantity > 0:
- # Clear existing lines
self.barcoding_product_line = [(5, 0, 0)]
- # Add a new line with the current product and quantity
- self.barcoding_product_line = [(0, 0, {
- 'product_id': self.product_id.id,
- 'barcoding_product_id': self.id,
- }) for _ in range(int(self.quantity))]
+ lines = []
+ for i in range(int(self.quantity)):
+ lines.append((0, 0, {
+ 'product_id': self.product_id.id,
+ 'barcoding_product_id': self.id,
+ 'sequence_with_total': f"{i+1}/{int(self.quantity)}"
+ }))
+ self.barcoding_product_line = lines
+
+ def write(self, vals):
+ res = super().write(vals)
+ if 'quantity' in vals and self.type == 'multiparts':
+ self._update_sequence_with_total()
+ return res
+
+ def _update_sequence_with_total(self):
+ for rec in self:
+ total = int(rec.quantity)
+ for index, line in enumerate(rec.barcoding_product_line, start=1):
+ line.sequence_with_total = f"{index}/{total}"
class BarcodingProductLine(models.Model):
@@ -59,4 +72,7 @@ class BarcodingProductLine(models.Model):
barcoding_product_id = fields.Many2one('barcoding.product', string='Barcoding Product Ref', required=True, ondelete='cascade', index=True, copy=False)
product_id = fields.Many2one('product.product', string="Product")
- qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant') \ No newline at end of file
+ qr_code_variant = fields.Binary("QR Code Variant", related='product_id.qr_code_variant')
+ sequence_with_total = fields.Char(
+ string="Sequence"
+ ) \ No newline at end of file
diff --git a/indoteknik_custom/models/mrp_production.py b/indoteknik_custom/models/mrp_production.py
index 8179fe56..58c2512c 100644
--- a/indoteknik_custom/models/mrp_production.py
+++ b/indoteknik_custom/models/mrp_production.py
@@ -244,6 +244,10 @@ class CheckBomProduct(models.Model):
@api.constrains('production_id', 'product_id')
def _check_product_bom_validation(self):
for record in self:
+ if record.production_id.sale_order.state not in ['sale', 'done']:
+ raise UserError((
+ "SO harus diconfirm terlebih dahulu."
+ ))
if not record.production_id or not record.product_id:
continue
diff --git a/indoteknik_custom/models/product_pricelist.py b/indoteknik_custom/models/product_pricelist.py
index ea3ee6cf..94a9b239 100644
--- a/indoteknik_custom/models/product_pricelist.py
+++ b/indoteknik_custom/models/product_pricelist.py
@@ -3,8 +3,8 @@ from datetime import datetime, timedelta
class ProductPricelist(models.Model):
- _inherit = 'product.pricelist'
-
+ _inherit = 'product.pricelist'
+
is_flash_sale = fields.Boolean(string='Flash Sale', default=False)
is_show_program = fields.Boolean(string='Show Program', default=False)
banner = fields.Binary(string='Banner')
@@ -56,7 +56,7 @@ class ProductPricelist(models.Model):
return tier_name
class ProductPricelistItem(models.Model):
- _inherit = 'product.pricelist.item'
+ _inherit = 'product.pricelist.item'
manufacture_id = fields.Many2one('x_manufactures', string='Manufacture')
computed_price = fields.Float(string='Computed Price') \ No newline at end of file
diff --git a/indoteknik_custom/models/product_template.py b/indoteknik_custom/models/product_template.py
index a09570f4..17805c6c 100755
--- a/indoteknik_custom/models/product_template.py
+++ b/indoteknik_custom/models/product_template.py
@@ -388,12 +388,509 @@ class ProductTemplate(models.Model):
self.env['token.storage'].create([values])
return values
- def write(self, vals):
- # for rec in self:
- # if rec.id == 224484:
- # raise UserError('Tidak dapat mengubah produk sementara')
+ # ==============================
+ def get_vendor_name(self, rec):
+ """Get formatted name for vendor/supplier"""
+ return rec.name.name if rec.name else f"ID {rec.id}"
+
+ def get_attribute_line_name(self, rec):
+ """Get formatted name for attribute line"""
+ if rec.attribute_id and rec.value_ids:
+ values = ", ".join(rec.value_ids.mapped('name'))
+ return f"{rec.attribute_id.name}: {values}"
+ return f"ID {rec.id}"
+
+ def _get_vendor_field_label(self, field_name):
+ """Get human-readable label for vendor fields"""
+ field_labels = {
+ 'name': 'Vendor',
+ 'currency_id': 'Currency',
+ 'product_uom': 'Unit of Measure',
+ 'price': 'Price',
+ 'delay': 'Delivery Lead Time',
+ 'product_id': 'Product Variant',
+ 'product_name': 'Vendor Product Name',
+ 'product_code': 'Vendor Product Code',
+ 'date_start': 'Start Date',
+ 'date_end': 'End Date',
+ 'min_qty': 'Quantity'
+ }
+ return field_labels.get(field_name, field_name.replace('_', ' ').title())
+
+ # ==============================
+
+ def _collect_old_values(self, vals):
+ """Collect old values before write"""
+ return {
+ record.id: {
+ field_name: record[field_name]
+ for field_name in vals.keys()
+ if field_name in record._fields
+ }
+ for record in self
+ }
+
+ def _prepare_attribute_line_info(self):
+ """Prepare attribute line info for logging and update comparison"""
+ line_info = {}
+ for line in self.attribute_line_ids:
+ line_info[line.id] = {
+ 'name': self.get_attribute_line_name(line),
+ 'attribute_id': line.attribute_id.id if line.attribute_id else None,
+ 'attribute_name': line.attribute_id.name if line.attribute_id else None,
+ 'value_ids': [(v.id, v.name) for v in line.value_ids],
+ 'value_names': ", ".join(line.value_ids.mapped('name'))
+ }
+ return line_info
+
+ def _prepare_vendor_info(self):
+ """Prepare vendor info for logging before they are deleted"""
+ vendor_info = {}
+ for seller in self.seller_ids:
+ vendor_info[seller.id] = {
+ 'name': self.get_vendor_name(seller),
+ 'price': seller.price,
+ 'min_qty': seller.min_qty,
+ 'delay': seller.delay,
+ 'product_name': seller.product_name,
+ 'product_code': seller.product_code,
+ 'currency_id': seller.currency_id.id if seller.currency_id else None,
+ 'product_uom': seller.product_uom.id if seller.product_uom else None,
+ 'product_id': seller.product_id.id if seller.product_id else None,
+ 'date_start': seller.date_start,
+ 'date_end': seller.date_end,
+ }
+ return vendor_info
+
+ # ==========================
+
+ def _get_context_with_all_info(self, vals):
+ """Get context with all necessary info (attributes and vendors)"""
+ context = dict(self.env.context)
- return super(ProductTemplate, self).write(vals)
+ # Check for attribute line changes
+ if 'attribute_line_ids' in vals:
+ attribute_line_info = {}
+ for product in self:
+ product_line_info = product._prepare_attribute_line_info()
+ attribute_line_info.update(product_line_info)
+ context['attribute_line_info'] = attribute_line_info
+
+ # Check for vendor changes - store both for deletion and for comparing old values
+ if 'seller_ids' in vals:
+ vendor_info = {}
+ vendor_old_values = {}
+ for product in self:
+ # For deletion logging
+ product_vendor_info = product._prepare_vendor_info()
+ vendor_info.update(product_vendor_info)
+
+ # For update comparison
+ product_vendor_old = product._prepare_vendor_info()
+ vendor_old_values.update(product_vendor_old)
+
+ context['vendor_info'] = vendor_info
+ context['vendor_old_values'] = vendor_old_values
+
+ return context
+
+ # ========================
+
+ def _log_image_changes(self, field_name, old_val, new_val):
+ """Log image field changes"""
+ label_map = {
+ 'image_1920': 'Main Image',
+ 'image_carousel_lines': 'Carousel Images',
+ 'product_template_image_ids': 'Extra Product Media',
+ }
+
+ label = label_map.get(field_name, field_name)
+
+ if old_val == 'None' and new_val != 'None':
+ return f"<li><b>{label}</b>: image added</li>"
+ elif old_val != 'None' and new_val == 'None':
+ return f"<li><b>{label}</b>: image removed</li>"
+ elif old_val != new_val:
+ return f"<li><b>{label}</b>: image updated</li>"
+ return None
+
+ def _log_attribute_line_changes(self, commands):
+ """Log changes to attribute lines with complete information"""
+ # Get stored info from context
+ stored_info = self.env.context.get('attribute_line_info', {})
+
+ for cmd in commands:
+ if cmd[0] == 0: # Add
+ new = self.env['product.template.attribute.line'].new(cmd[2])
+ attribute_name = new.attribute_id.name if new.attribute_id else 'Attribute'
+ values = ", ".join(new.value_ids.mapped('name')) if new.value_ids else ''
+
+ message = f"<b>Product Attribute</b>:<br/>{attribute_name} added<br/>"
+ if values:
+ message += f"Values: '{values}'"
+ self.message_post(body=message)
+
+ elif cmd[0] == 1: # Update
+ rec_id = cmd[1]
+ vals = cmd[2]
+
+ # Get old values from context
+ old_data = stored_info.get(rec_id, {})
+ if not old_data:
+ # Fallback: get current record
+ rec = self.env['product.template.attribute.line'].browse(rec_id)
+ if not rec.exists():
+ continue
+ old_data = {
+ 'name': self.get_attribute_line_name(rec),
+ 'attribute_id': rec.attribute_id.id if rec.attribute_id else None,
+ 'attribute_name': rec.attribute_id.name if rec.attribute_id else None,
+ 'value_ids': [(v.id, v.name) for v in rec.value_ids],
+ 'value_names': ", ".join(rec.value_ids.mapped('name'))
+ }
+
+ changes = []
+ attribute_name = old_data.get('attribute_name', 'Attribute')
+
+ # Check for attribute change
+ if 'attribute_id' in vals:
+ old_attr = old_data.get('attribute_name', '-')
+ new_attr = self.env['product.attribute'].browse(vals['attribute_id']).name
+ if old_attr != new_attr:
+ attribute_name = new_attr # Update attribute name for display
+ changes.append(f"Attribute changed from '{old_attr}' to '{new_attr}'")
+
+ # Check for value changes
+ if 'value_ids' in vals:
+ old_vals = old_data.get('value_names', '')
+
+ # Parse the command for value_ids
+ new_value_ids = []
+ for value_cmd in vals['value_ids']:
+ if isinstance(value_cmd, (list, tuple)):
+ if value_cmd[0] == 6: # Replace all
+ new_value_ids = value_cmd[2]
+ elif value_cmd[0] == 4: # Add
+ new_value_ids.append(value_cmd[1])
+ elif value_cmd[0] == 3: # Remove
+ # This is more complex, would need current state
+ pass
+
+ # Get new value names
+ if new_value_ids:
+ new_values = self.env['product.attribute.value'].browse(new_value_ids)
+ new_vals = ", ".join(new_values.mapped('name'))
+ else:
+ new_vals = ""
+
+ if old_vals != new_vals:
+ changes.append(f"Values: '{old_vals}' → '{new_vals}'")
+
+ if changes:
+ # Format with attribute name
+ message = f"<b>Product Attribute</b>:<br/>{attribute_name} updated<br/>"
+ message += "<br/>".join(changes)
+ self.message_post(body=message)
+
+ elif cmd[0] in (2, 3): # Remove
+ # Use info from stored data
+ line_data = stored_info.get(cmd[1])
+ if line_data:
+ attribute_name = line_data.get('attribute_name', 'Attribute')
+ values = line_data.get('value_names', '')
+ else:
+ rec = self.env['product.template.attribute.line'].browse(cmd[1])
+ if rec.exists():
+ attribute_name = rec.attribute_id.name if rec.attribute_id else 'Attribute'
+ values = ", ".join(rec.value_ids.mapped('name')) if rec.value_ids else ''
+ else:
+ attribute_name = 'Attribute'
+ values = f"ID {cmd[1]}"
+
+ message = f"<b>Product Attribute</b>:<br/>{attribute_name} removed<br/>"
+ if values:
+ message += f"Values: '{values}'"
+ self.message_post(body=message)
+
+ elif cmd[0] == 5: # Clear all
+ self.message_post(body=f"<b>Product Attribute</b>:<br/>All attributes removed")
+
+ def _log_vendor_pricelist_changes(self, commands):
+ """Log changes to vendor pricelist with complete information"""
+ # Get stored info from context
+ stored_info = self.env.context.get('vendor_info', {})
+ old_values_info = self.env.context.get('vendor_old_values', {})
+
+ for cmd in commands:
+ if cmd[0] == 0: # Add
+ vals = cmd[2]
+
+ # Create temporary record to get proper display values
+ temp_values = vals.copy()
+ temp_values['product_tmpl_id'] = self.id
+ new = self.env['product.supplierinfo'].new(temp_values)
+
+ name = self.get_vendor_name(new)
+ details = []
+
+ if 'price' in vals and vals['price'] is not None:
+ details.append(f"<li>Price: {vals['price']}</li>")
+ if 'min_qty' in vals and vals['min_qty'] is not None:
+ details.append(f"<li>Quantity: {vals['min_qty']}</li>")
+ if 'delay' in vals and vals['delay'] is not None:
+ details.append(f"<li>Delivery Lead Time: {vals['delay']}</li>")
+ if 'product_name' in vals and vals['product_name']:
+ details.append(f"<li>Vendor Product Name: {vals['product_name']}</li>")
+ if 'product_code' in vals and vals['product_code']:
+ details.append(f"<li>Vendor Product Code: {vals['product_code']}</li>")
+ if 'currency_id' in vals and vals['currency_id']:
+ currency = self.env['res.currency'].browse(vals['currency_id'])
+ details.append(f"<li>Currency: {currency.name}</li>")
+ if 'product_uom' in vals and vals['product_uom']:
+ uom = self.env['uom.uom'].browse(vals['product_uom'])
+ details.append(f"<li>Unit of Measure: {uom.name}</li>")
+
+ if details:
+ detail_str = f" with:<ul>{''.join(details)}</ul>"
+ else:
+ detail_str = ""
+
+ self.message_post(body=f"<b>Vendor Pricelist</b>: added '{name}'{detail_str}")
+
+ elif cmd[0] == 1: # Update
+ rec_id = cmd[1]
+ vals = cmd[2]
+
+ # Get old values from context
+ old_data = old_values_info.get(rec_id, {})
+ if not old_data:
+ # Fallback: get current record
+ rec = self.env['product.supplierinfo'].browse(rec_id)
+ if not rec.exists():
+ continue
+ old_data = {
+ 'name': self.get_vendor_name(rec),
+ 'price': rec.price,
+ 'min_qty': rec.min_qty,
+ 'delay': rec.delay,
+ 'product_name': rec.product_name,
+ 'product_code': rec.product_code,
+ 'currency_id': rec.currency_id.id if rec.currency_id else None,
+ 'product_uom': rec.product_uom.id if rec.product_uom else None,
+ 'product_id': rec.product_id.id if rec.product_id else None,
+ 'date_start': rec.date_start,
+ 'date_end': rec.date_end,
+ }
+
+ name = old_data.get('name', f'ID {rec_id}')
+ changes = []
+
+ # Check each field in vals for changes
+ for field, new_value in vals.items():
+ if field == 'name':
+ # Special handling for vendor name change
+ if new_value != old_data.get('name'):
+ old_name = old_data.get('name', 'None')
+ new_name = self.env['res.partner'].browse(new_value).name if new_value else 'None'
+ changes.append(f"<li>Vendor: {old_name} → {new_name}</li>")
+ continue
+
+ old_value = old_data.get(field)
+
+ # Format values based on field type
+ if field == 'currency_id':
+ if old_value != new_value:
+ old_str = self.env['res.currency'].browse(old_value).name if old_value else 'None'
+ new_str = self.env['res.currency'].browse(new_value).name if new_value else 'None'
+ else:
+ continue
+ elif field == 'product_uom':
+ if old_value != new_value:
+ old_str = self.env['uom.uom'].browse(old_value).name if old_value else 'None'
+ new_str = self.env['uom.uom'].browse(new_value).name if new_value else 'None'
+ else:
+ continue
+ elif field == 'product_id':
+ if old_value != new_value:
+ old_str = self.env['product.product'].browse(old_value).display_name if old_value else 'None'
+ new_str = self.env['product.product'].browse(new_value).display_name if new_value else 'None'
+ else:
+ continue
+ elif field in ['date_start', 'date_end']:
+ if str(old_value) != str(new_value):
+ old_str = old_value.strftime('%Y-%m-%d') if old_value else 'None'
+ new_str = new_value if new_value else 'None'
+ else:
+ continue
+ else:
+ # For numeric and other fields
+ if field in ['price', 'min_qty', 'delay']:
+ # Compare numeric values properly
+ old_num = float(old_value) if old_value is not None else 0.0
+ new_num = float(new_value) if new_value is not None else 0.0
+
+ if field == 'delay': # Integer field
+ old_num = int(old_num)
+ new_num = int(new_num)
+
+ if old_num == new_num:
+ continue
+
+ old_str = str(old_value) if old_value is not None else 'None'
+ new_str = str(new_value) if new_value is not None else 'None'
+ else:
+ # String and other types
+ if str(old_value) == str(new_value):
+ continue
+ old_str = str(old_value) if old_value is not None else 'None'
+ new_str = str(new_value) if new_value is not None else 'None'
+
+ label = self._get_vendor_field_label(field)
+ changes.append(f"<li>{label}: {old_str} → {new_str}</li>")
+
+ if changes:
+ changes_str = f"<ul>{''.join(changes)}</ul>"
+ self.message_post(body=f"<b>Vendor Pricelist</b>: updated '{name}':{changes_str}")
+
+ elif cmd[0] in (2, 3): # Remove
+ vendor_data = stored_info.get(cmd[1])
+ if vendor_data:
+ name = vendor_data['name']
+ details = []
+
+ if vendor_data.get('price'):
+ details.append(f"<li>Price: {vendor_data['price']}</li>")
+ if vendor_data.get('min_qty'):
+ details.append(f"<li>Quantity: {vendor_data['min_qty']}</li>")
+ if vendor_data.get('product_name'):
+ details.append(f"<li>Product Name: {vendor_data['product_name']}</li>")
+ if vendor_data.get('delay'):
+ details.append(f"<li>Delivery Lead Time: {vendor_data['delay']}</li>")
+
+ if details:
+ detail_str = f"<ul>{''.join(details)}</ul>"
+ else:
+ detail_str = ""
+ else:
+ rec = self.env['product.supplierinfo'].browse(cmd[1])
+ if rec.exists():
+ name = self.get_vendor_name(rec)
+ details = []
+ if rec.price:
+ details.append(f"<li>Price: {rec.price}</li>")
+ if rec.min_qty:
+ details.append(f"<li>Quantity: {rec.min_qty}</li>")
+ if rec.product_name:
+ details.append(f"<li>Product Name: {rec.product_name}</li>")
+
+ if details:
+ detail_str = f"<ul>{''.join(details)}</ul>"
+ else:
+ detail_str = ""
+ else:
+ name = f"ID {cmd[1]}"
+ detail_str = ""
+
+ self.message_post(body=f"<b>Vendor Pricelist</b>: removed '{name}'{detail_str}")
+
+ elif cmd[0] == 5: # Clear all
+ self.message_post(body=f"<b>Vendor Pricelist</b>: all removed")
+
+ def _log_field_changes_product(self, vals, old_values):
+ """Log general field changes for product template"""
+ exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
+ image_fields = ['image_1920', 'image_carousel_lines', 'product_template_image_ids']
+
+ for record in self:
+ changes = []
+
+ for field_name in vals:
+ if field_name not in record._fields or field_name in exclude_fields:
+ continue
+
+ field = record._fields[field_name]
+
+ # Handle image fields specially
+ if field_name in image_fields:
+ old_val = 'None' if not old_values.get(record.id, {}).get(field_name) else 'Yes'
+ new_val = 'None' if not record[field_name] else 'Yes'
+ image_msg = record._log_image_changes(field_name, old_val, new_val)
+ if image_msg:
+ changes.append(image_msg)
+ continue
+
+ # Handle vendor fields
+ if field_name == 'seller_ids':
+ commands = vals[field_name]
+ if isinstance(commands, list):
+ record._log_vendor_pricelist_changes(commands)
+ continue
+
+ # Handle attribute lines
+ if field_name == 'attribute_line_ids':
+ commands = vals[field_name]
+ if isinstance(commands, list):
+ record._log_attribute_line_changes(commands)
+ continue
+
+ # Handle other fields
+ def stringify(val):
+ if val in [None, False]:
+ return 'None'
+ if isinstance(field, fields.Selection):
+ selection = field.selection(record) if callable(field.selection) else field.selection
+ return dict(selection).get(val, str(val))
+ if isinstance(field, fields.Boolean):
+ return 'Yes' if val else 'No'
+ if isinstance(field, fields.Many2one):
+ if isinstance(val, int):
+ rec = record.env[field.comodel_name].browse(val)
+ return rec.display_name if rec.exists() else str(val)
+ elif isinstance(val, models.BaseModel):
+ return val.display_name
+ return str(val)
+ if isinstance(field, fields.Many2many):
+ records = val if isinstance(val, models.BaseModel) else record[field.name]
+ if not records:
+ return 'None'
+ for attr in ['name', 'x_name', 'display_name']:
+ if hasattr(records[0], attr):
+ return ", ".join(records.mapped(attr))
+ return ", ".join(str(r.id) for r in records)
+ return str(val)
+
+ old_val_str = stringify(old_values.get(record.id, {}).get(field_name))
+ new_val_str = stringify(record[field_name])
+
+ if old_val_str != new_val_str:
+ field_label = field.string or field_name
+ changes.append(f"<li><b>{field_label}</b>: '{old_val_str}' → '{new_val_str}'</li>")
+
+ if changes:
+ record.message_post(body=f"<b>Updated:</b><ul>{''.join(changes)}</ul>")
+
+ # log changes to product variants
+ variant_message = f"<b>Updated:</b><ul>{''.join(changes)}</ul>"
+ for variant in record.product_variant_ids:
+ variant.message_post(body=variant_message)
+
+ # simpan data lama dan log perubahan field
+ def write(self, vals):
+ context = self._get_context_with_all_info(vals)
+ if context != self.env.context:
+ self = self.with_context(**context)
+ old_values = self._collect_old_values(vals)
+ result = super().write(vals)
+ # Log changes
+ self._log_field_changes_product(vals, old_values)
+ return result
+
+ # def write(self, vals):
+ # # for rec in self:
+ # # if rec.id == 224484:
+ # # raise UserError('Tidak dapat mengubah produk sementara')
+ # self._log_field_changes(vals)
+ # return super(ProductTemplate, self).write(vals)
class ProductProduct(models.Model):
_inherit = "product.product"
@@ -712,6 +1209,130 @@ class ProductProduct(models.Model):
], limit=1)
return pricelist
+ # simpan data lama
+ def _collect_old_values(self, vals):
+ return {
+ record.id: {
+ field_name: record[field_name]
+ for field_name in vals.keys()
+ if field_name in record._fields
+ }
+ for record in self
+ }
+
+ # log perubahan field
+ def _log_field_changes_product_variants(self, vals, old_values):
+ exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
+ # for image fields, use custom labels
+ custom_labels = {
+ 'image_1920': 'Main Image',
+ 'image_carousel_lines': 'Carousel Images',
+ 'product_template_image_ids': 'Extra Product Media',
+ }
+
+ template_changes = {}
+
+ for record in self:
+ changes = []
+ for field_name in vals:
+ if field_name not in record._fields or field_name in exclude_fields:
+ continue
+
+ field = record._fields[field_name]
+ field_label = custom_labels.get(field_name, field.string or field_name)
+ old_value = old_values.get(record.id, {}).get(field_name)
+ new_value = record[field_name] # nilai setelah write
+
+ def stringify(val, field, record):
+ if val in [None, False]:
+ return 'None'
+ if isinstance(field, fields.Selection):
+ selection = field.selection
+ if callable(selection):
+ selection = selection(record)
+ return dict(selection).get(val, str(val))
+ if isinstance(field, fields.Boolean):
+ return 'Yes' if val else 'No'
+ if isinstance(field, fields.Many2one):
+ if isinstance(val, int):
+ rec = record.env[field.comodel_name].browse(val)
+ return rec.display_name if rec.exists() else str(val)
+ elif isinstance(val, models.BaseModel):
+ return val.display_name
+ return str(val)
+ if isinstance(field, fields.Many2many):
+ records = val if isinstance(val, models.BaseModel) else record[field.name]
+ if not records:
+ return 'None'
+ for attr in ['name', 'x_name', 'display_name']:
+ if hasattr(records[0], attr):
+ return ", ".join(records.mapped(attr))
+ return ", ".join(str(r.id) for r in records)
+ if isinstance(field, fields.One2many):
+ records = val if isinstance(val, models.BaseModel) else record[field.name]
+ if not records:
+ return 'None'
+ return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})"
+ return str(val)
+
+ old_val_str = stringify(old_value, field, record)
+ new_val_str = stringify(new_value, field, record)
+
+ if old_val_str != new_val_str:
+ if field_name in custom_labels: # handle image field
+ if old_val_str == 'None' and new_val_str != 'None':
+ changes.append(f"<li><b>{field_label}</b>: image added</li>")
+ elif old_val_str != 'None' and new_val_str == 'None':
+ changes.append(f"<li><b>{field_label}</b>: image removed</li>")
+ else:
+ changes.append(f"<li><b>{field_label}</b>: image updated</li>")
+ else:
+ changes.append(f"<li><b>{field_label}</b>: '{old_val_str}' → '{new_val_str}'</li>")
+
+ if changes:
+ # Post message to variant
+ variant_message = "<b>Updated:</b><ul>%s</ul>" % "".join(changes)
+ record.message_post(body=variant_message)
+
+ # Group changes by template for posting to template
+ template_id = record.product_tmpl_id.id
+ if template_id not in template_changes:
+ template_changes[template_id] = {}
+
+ # Store variant information including ID for creating clickable link
+ template_changes[template_id][record.id] = {
+ 'name': record.display_name or f"Variant {record.id}",
+ 'changes': changes
+ }
+
+ # Post grouped messages to templates with clickable links using your format
+ for template_id, variants_data in template_changes.items():
+ template = self.env['product.template'].browse(template_id)
+ if template.exists():
+ template_message = "<b>Variant Updates:</b><br/>"
+
+ for variant_id, variant_data in variants_data.items():
+ # Create clickable link using your format
+ variant_link = f"<a href=\"#id={variant_id}&model=product.product&view_type=form\">{variant_data['name']}</a><br/>"
+ template_message += f"{variant_link}<ul>{''.join(variant_data['changes'])}</ul><br/>"
+
+ template.message_post(body=template_message)
+
+ # simpan data lama dan log perubahan field
+ def write(self, vals):
+ tracked_fields = [
+ 'default_code', 'name', 'weight', 'x_manufacture',
+ 'public_categ_ids', 'search_rank', 'search_rank_weekly',
+ 'image_1920', 'unpublished', 'image_carousel_lines'
+ ]
+
+ if any(field in vals for field in tracked_fields):
+ old_values = self._collect_old_values(vals)
+ result = super().write(vals)
+ self._log_field_changes_product_variants(vals, old_values)
+ return result
+ else:
+ return super().write(vals)
class OutstandingMove(models.Model):
_name = 'v.move.outstanding'
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 98b367d0..c5ba5792 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -562,7 +562,9 @@ class PurchaseOrder(models.Model):
i = 0
for line in self.order_line:
i += 1
- current_time = datetime.utcnow()
+
+ utc_time = fields.Datetime.now()
+ current_time = utc_time.astimezone(timezone('Asia/Jakarta')).strftime('%Y-%m-%d %H:%M:%S')
# print(i, len(self.order_line))
price_unit = line.price_unit
@@ -580,10 +582,11 @@ class PurchaseOrder(models.Model):
purchase_pricelist = self.env['purchase.pricelist'].search([
('product_id', '=', line.product_id.id),
('vendor_id', '=', line.order_id.partner_id.id)
- ])
- purchase_pricelist = purchase_pricelist.with_context(update_by='system')
+ ])
+
if not purchase_pricelist:
- purchase_pricelist.create([{
+ # Buat pricelist baru dengan context
+ new_pricelist = self.env['purchase.pricelist'].with_context(update_by='system').create([{
'vendor_id': line.order_id.partner_id.id,
'product_id': line.product_id.id,
'product_price': 0,
@@ -591,12 +594,51 @@ class PurchaseOrder(models.Model):
'system_price': price_unit,
'system_last_update': current_time
}])
+
+ # Buat lognote untuk pricelist baru
+ message = f"""
+ <b>New Purchase Pricelist Created from PO</b><br/>
+ <b>PO:</b> <a href="#id={line.order_id.id}&model=purchase.order&view_type=form">{line.order_id.name}</a><br/>
+ <b>System Price:</b> {price_unit:,.2f}<br/>
+ <b>System Tax:</b> {taxes.name if taxes else 'No Tax'}<br/>
+ <b>System Update:</b> {current_time}<br/>
+ """
+ new_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id)
else:
+ # Simpan nilai lama untuk logging
+ old_values = {
+ 'system_price': purchase_pricelist.system_price,
+ 'taxes_system_id': purchase_pricelist.taxes_system_id,
+ }
+
+ # Update dengan context
+ purchase_pricelist = purchase_pricelist.with_context(update_by='system')
purchase_pricelist.write({
'system_last_update': current_time,
'taxes_system_id': taxes.id,
'system_price': price_unit
})
+
+ # Buat lognote jika ada perubahan
+ changes = []
+ if old_values['system_price'] != price_unit:
+ changes.append(f"<li><b>System Price</b>: {old_values['system_price']:,.2f} → {price_unit:,.2f}</li>")
+ if old_values['taxes_system_id'] != taxes:
+ old_tax_name = old_values['taxes_system_id'].name if old_values['taxes_system_id'] else 'No Tax'
+ new_tax_name = taxes.name if taxes else 'No Tax'
+ changes.append(f"<li><b>System Tax</b>: {old_tax_name} → {new_tax_name}</li>")
+
+ if changes:
+ message = f"""
+ <b>System Fields Updated from PO</b><br/>
+ <b>PO:</b> <a href="#id={line.order_id.id}&model=purchase.order&view_type=form">{line.order_id.name}</a><br/>
+ <b>Changes:</b>
+ <ul>
+ {"".join(changes)}
+ <li><b>System Update</b>: {current_time}</li>
+ </ul>
+ """
+ purchase_pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id)
def _compute_date_planned(self):
for order in self:
@@ -1198,8 +1240,83 @@ class PurchaseOrder(models.Model):
if not self.env.context.get('skip_check_payment'):
self.with_context(skip_check_payment=True)._check_payment_term()
self.with_context(notify_tax=True)._check_tax_rule()
+ # Tambahkan pemanggilan method untuk handle pricelist system update
+ self._handle_pricelist_system_update(vals)
return res
+ def _handle_pricelist_system_update(self, vals):
+ if 'order_line' in vals or any(key in vals for key in ['state', 'approval_status']):
+ for order in self:
+ # Hanya proses jika PO sudah approved
+ if order.state in ['purchase', 'done'] and order.approval_status == 'approved':
+ self._process_pricelist_update(order)
+
+ def _process_pricelist_update(self, order):
+ for line in order.order_line:
+ pricelist = self._get_related_pricelist(line.product_id, order.partner_id)
+
+ if pricelist:
+ # Simpan nilai lama
+ old_values = self._get_pricelist_old_values(pricelist)
+
+ # Update dan cek perubahan
+ self._update_and_log_pricelist(pricelist, line, old_values)
+
+ def _get_related_pricelist(self, product_id, vendor_id):
+ return self.env['purchase.pricelist'].search([
+ ('product_id', '=', product_id.id),
+ ('vendor_id', '=', vendor_id.id)
+ ], limit=1)
+
+ def _get_pricelist_old_values(self, pricelist):
+ return {
+ 'system_price': pricelist.system_price,
+ 'taxes_system_id': pricelist.taxes_system_id,
+ 'system_last_update': pricelist.system_last_update
+ }
+
+ def _update_and_log_pricelist(self, pricelist, po_line, old_values):
+ changes = []
+ current_time = fields.Datetime.now()
+
+ # Cek perubahan System Price
+ if pricelist.system_price != po_line.price_unit:
+ if old_values['system_price'] != po_line.price_unit:
+ changes.append(f"<li><b>System Price</b>: {old_values['system_price']:,.2f} → {po_line.price_unit:,.2f}</li>")
+
+ # Cek perubahan System Tax
+ if pricelist.taxes_system_id != po_line.taxes_id:
+ old_tax = old_values['taxes_system_id']
+ old_tax_name = old_tax.name if old_tax else 'No Tax'
+ new_tax_name = po_line.taxes_id.name if po_line.taxes_id else 'No Tax'
+ if old_tax != po_line.taxes_id:
+ changes.append(f"<li><b>System Tax</b>: {old_tax_name} → {new_tax_name}</li>")
+
+ # Update fields jika ada perubahan
+ if changes:
+ pricelist.with_context(update_by='system').write({
+ 'system_price': po_line.price_unit,
+ 'taxes_system_id': po_line.taxes_id.id if po_line.taxes_id else False,
+ 'system_last_update': current_time
+ })
+
+ # Buat lognote
+ self._create_pricelist_lognote(pricelist, po_line, changes, current_time)
+
+ def _create_pricelist_lognote(self, pricelist, po_line, changes, timestamp):
+ message = f"""
+ <b>System Fields Updated from PO</b><br/>
+ <b>PO:</b> <a href="#id={po_line.order_id.id}&model=purchase.order&view_type=form">{po_line.order_id.name}</a><br/>
+ <b>Changes:</b>
+ <ul>
+ {"".join(changes)}
+ <li><b>System Update</b>: {timestamp}</li>
+ </ul>
+ <b>Updated By:</b> {self.env.user.name}
+ """
+
+ pricelist.message_post(body=message, subtype_id=self.env.ref("mail.mt_note").id)
+
class PurchaseOrderUnlockWizard(models.TransientModel):
_name = 'purchase.order.unlock.wizard'
_description = 'Wizard untuk memberikan alasan unlock PO'
diff --git a/indoteknik_custom/models/purchase_pricelist.py b/indoteknik_custom/models/purchase_pricelist.py
index dd680f4d..b3a473b6 100755
--- a/indoteknik_custom/models/purchase_pricelist.py
+++ b/indoteknik_custom/models/purchase_pricelist.py
@@ -6,6 +6,7 @@ from pytz import timezone
class PurchasePricelist(models.Model):
_name = 'purchase.pricelist'
_rec_name = 'product_id'
+ _inherit = ['mail.thread', 'mail.activity.mixin']
name = fields.Char(string='Name', compute="_compute_name")
product_id = fields.Many2one('product.product', string="Product", required=True)
@@ -131,4 +132,81 @@ class PurchasePricelist(models.Model):
rec.sync_pricelist_tier()
rec.product_id.product_tmpl_id._create_solr_queue('_sync_price_to_solr')
- \ No newline at end of file
+
+ def _collect_old_values(self, vals):
+ return {
+ record.id: {
+ field_name: record[field_name]
+ for field_name in vals.keys()
+ if field_name in record._fields
+ }
+ for record in self
+ }
+
+ def _log_field_changes(self, vals, old_values):
+ exclude_fields = ['solr_flag', 'desc_update_solr', 'last_update_solr', 'is_edited']
+
+ for record in self:
+ changes = []
+ for field_name in vals:
+ if field_name not in record._fields or field_name in exclude_fields:
+ continue
+
+ field = record._fields[field_name]
+ field_label = field.string or field_name
+ old_value = old_values.get(record.id, {}).get(field_name)
+ new_value = record[field_name]
+
+ def stringify(val, field, record):
+ if val in [None, False]:
+ return 'None'
+ # Handle Selection
+ if isinstance(field, fields.Selection):
+ selection = field.selection
+ if callable(selection):
+ selection = selection(record)
+ return dict(selection).get(val, str(val))
+ # Handle Boolean
+ if isinstance(field, fields.Boolean):
+ return 'Yes' if val else 'No'
+ # Handle Many2one
+ if isinstance(field, fields.Many2one):
+ if isinstance(val, int):
+ rec = record.env[field.comodel_name].browse(val)
+ return rec.display_name if rec.exists() else str(val)
+ elif isinstance(val, models.BaseModel):
+ return val.display_name
+ return str(val)
+ # Handle Many2many
+ if isinstance(field, fields.Many2many):
+ records = val if isinstance(val, models.BaseModel) else record[field.name]
+ if not records:
+ return 'None'
+ for attr in ['name', 'x_name', 'display_name']:
+ if hasattr(records[0], attr):
+ return ", ".join(records.mapped(attr))
+ return ", ".join(str(r.id) for r in records)
+ # Handle One2many
+ if isinstance(field, fields.One2many):
+ records = val if isinstance(val, models.BaseModel) else record[field.name]
+ if not records:
+ return 'None'
+ return f"{field.comodel_name}({', '.join(str(r.id) for r in records)})"
+ # Default case (Char, Float, Integer, etc)
+ return str(val)
+
+ old_val_str = stringify(old_value, field, record)
+ new_val_str = stringify(new_value, field, record)
+
+ if old_val_str != new_val_str:
+ changes.append(f"<li><b>{field_label}</b>: '{old_val_str}' → '{new_val_str}'</li>")
+
+ if changes:
+ message = "<b>Updated:</b><ul>%s</ul>" % "".join(changes)
+ record.message_post(body=message)
+
+ def write(self, vals):
+ old_values = self._collect_old_values(vals)
+ result = super(PurchasePricelist, self).write(vals)
+ self._log_field_changes(vals, old_values)
+ return result \ No newline at end of file
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 17115908..fcef8906 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -10,6 +10,7 @@ from collections import defaultdict
_logger = logging.getLogger(__name__)
+
class CancelReasonOrder(models.TransientModel):
_name = 'cancel.reason.order'
_description = 'Wizard for Cancel Reason order'
@@ -45,7 +46,7 @@ class CancelReasonOrder(models.TransientModel):
raise UserError('Attachment bukti wajib disertakan')
order.write({'attachment_bukti': self.attachment_bukti})
order.message_post(body='Attachment Bukti Cancel',
- attachment_ids=[self.attachment_bukti.id])
+ attachment_ids=[self.attachment_bukti.id])
if self.reason_cancel == 'ganti_quotation':
if self.nomor_so_pengganti:
order.write({'nomor_so_pengganti': self.nomor_so_pengganti})
@@ -54,7 +55,8 @@ class CancelReasonOrder(models.TransientModel):
order.confirm_cancel_order()
return {'type': 'ir.actions.act_window_close'}
-
+
+
class ShippingOption(models.Model):
_name = "shipping.option"
_description = "Shipping Option"
@@ -65,6 +67,7 @@ class ShippingOption(models.Model):
etd = fields.Char(string="Estimated Delivery Time")
sale_order_id = fields.Many2one('sale.order', string="Sale Order", ondelete="cascade")
+
class SaleOrderLine(models.Model):
_inherit = 'sale.order.line'
@@ -74,7 +77,7 @@ class SaleOrderLine(models.Model):
if line.order_id:
now = fields.Datetime.now()
- initial_reason="Product Rejected"
+ initial_reason = "Product Rejected"
# Buat lognote untuk product yang di delete
log_note = (f"<li>Product '{line.product_id.name}' rejected. </li>"
@@ -114,10 +117,12 @@ class SaleOrderLine(models.Model):
return result
+
class SaleOrder(models.Model):
_inherit = "sale.order"
- ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True, tracking=3)
+ ongkir_ke_xpdc = fields.Float(string='Ongkir ke Ekspedisi', help='Biaya ongkir ekspedisi', copy=False, index=True,
+ tracking=3)
metode_kirim_ke_xpdc = fields.Selection([
('indoteknik_deliv', 'Indoteknik Delivery'),
@@ -128,22 +133,31 @@ class SaleOrder(models.Model):
('other', 'Other'),
], string='Metode Kirim Ke Ekspedisi', copy=False, index=True, tracking=3)
+ notes = fields.Text(string="Notes", tracking=3)
koli_lines = fields.One2many('sales.order.koli', 'sale_order_id', string='Sales Order Koli', auto_join=True)
fulfillment_line_v2 = fields.One2many('sales.order.fulfillment.v2', 'sale_order_id', string='Fullfillment2')
fullfillment_line = fields.One2many('sales.order.fullfillment', 'sales_order_id', string='Fullfillment')
reject_line = fields.One2many('sales.order.reject', 'sale_order_id', string='Reject Lines')
- order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id', string='Purchase Match Lines', states={'cancel': [('readonly', True)], 'done': [('readonly', True)]}, copy=True)
- total_margin = fields.Float('Total Margin', compute='_compute_total_margin', help="Total Margin in Sales Order Header")
- total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin', help="Total Margin in Sales Order Header")
- total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin', help="Total % Margin in Sales Order Header")
- total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header", compute='_compute_total_margin_excl_third_party')
+ order_sales_match_line = fields.One2many('sales.order.purchase.match', 'sales_order_id',
+ string='Purchase Match Lines',
+ states={'cancel': [('readonly', True)], 'done': [('readonly', True)]},
+ copy=True)
+ total_margin = fields.Float('Total Margin', compute='_compute_total_margin',
+ help="Total Margin in Sales Order Header")
+ total_before_margin = fields.Float('Total Before Margin', compute='_compute_total_before_margin',
+ help="Total Margin in Sales Order Header")
+ total_percent_margin = fields.Float('Total Percent Margin', compute='_compute_total_percent_margin',
+ help="Total % Margin in Sales Order Header")
+ total_margin_excl_third_party = fields.Float('Before Margin', help="Before Margin in Sales Order Header",
+ compute='_compute_total_margin_excl_third_party')
approval_status = fields.Selection([
('pengajuan1', 'Approval Manager'),
('pengajuan2', 'Approval Pimpinan'),
('approved', 'Approved'),
], string='Approval Status', readonly=True, copy=False, index=True, tracking=3)
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method', tracking=3)
- have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service', help='To compute is customer get visit service')
+ have_visit_service = fields.Boolean(string='Have Visit Service', compute='_have_visit_service',
+ help='To compute is customer get visit service')
delivery_amt = fields.Float(string='Delivery Amt', copy=False)
shipping_cost_covered = fields.Selection([
('indoteknik', 'Indoteknik'),
@@ -153,7 +167,8 @@ class SaleOrder(models.Model):
('indoteknik', 'Indoteknik'),
('customer', 'Customer')
], string='Shipping Paid by', help='Siapa yang talangin dulu Biaya ekspedisi-nya?', copy=False, tracking=3)
- sales_tax_id = fields.Many2one('account.tax', string='Tax', domain=['|', ('active', '=', False), ('active', '=', True)])
+ sales_tax_id = fields.Many2one('account.tax', string='Tax',
+ domain=['|', ('active', '=', False), ('active', '=', True)])
have_outstanding_invoice = fields.Boolean('Have Outstanding Invoice', compute='_have_outstanding_invoice')
have_outstanding_picking = fields.Boolean('Have Outstanding Picking', compute='_have_outstanding_picking')
have_outstanding_po = fields.Boolean('Have Outstanding PO', compute='_have_outstanding_po')
@@ -174,9 +189,14 @@ class SaleOrder(models.Model):
('sebagian', 'Sebagian Diproses'),
('menunggu', 'Menunggu Diproses'),
], copy=False)
- partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False, help="Nama purchase order customer, diisi oleh customer melalui website.", tracking=3)
- partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False, help="Keterangan purchase order customer, diisi oleh customer melalui website.", tracking=3)
- partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False, help="File purchase order customer, diisi oleh customer melalui website.")
+ partner_purchase_order_name = fields.Char(string='Nama PO Customer', copy=False,
+ help="Nama purchase order customer, diisi oleh customer melalui website.",
+ tracking=3)
+ partner_purchase_order_description = fields.Text(string='Keterangan PO Customer', copy=False,
+ help="Keterangan purchase order customer, diisi oleh customer melalui website.",
+ tracking=3)
+ partner_purchase_order_file = fields.Binary(string='File PO Customer', copy=False,
+ help="File purchase order customer, diisi oleh customer melalui website.")
payment_status = fields.Selection([
('pending', 'Pending'),
('capture', 'Capture'),
@@ -190,17 +210,22 @@ class SaleOrder(models.Model):
('partial_refund', 'Partial Refund'),
('partial_chargeback', 'Partial Chargeback'),
('authorize', 'Authorize'),
- ], tracking=True, string='Payment Status', help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle')
- date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ', help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting")
+ ], tracking=True, string='Payment Status',
+ help='Payment Gateway Status / Midtrans / Web, https://docs.midtrans.com/en/after-payment/status-cycle')
+ date_doc_kirim = fields.Datetime(string='Tanggal Kirim di SJ',
+ help="Tanggal Kirim di cetakan SJ yang terakhir, tidak berpengaruh ke Accounting")
payment_type = fields.Char(string='Payment Type', help='Jenis pembayaran dengan Midtrans')
gross_amount = fields.Float(string='Gross Amount', help='Jumlah pembayaran yang dilakukan dengan Midtrans')
notification = fields.Char(string='Notification', help='Dapat membantu error dari approval')
delivery_service_type = fields.Char(string='Delivery Service Type', help='data dari rajaongkir')
- grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery', compute='_compute_grand_total')
- payment_link_midtrans = fields.Char(string='Payment Link', help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri')
+ grand_total = fields.Monetary(string='Grand Total', help='Amount total + amount delivery',
+ compute='_compute_grand_total')
+ payment_link_midtrans = fields.Char(string='Payment Link',
+ help='Url payment yg digenerate oleh midtrans, harap diserahkan ke customer agar dapat dilakukan pembayaran secara mandiri')
payment_qr_code = fields.Binary("Payment QR Code")
- due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True)
- vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True, copy=False)
+ due_id = fields.Many2one('due.extension', string="Due Extension", readonly=True, tracking=True)
+ vendor_approval_id = fields.Many2many('vendor.approval', string="Vendor Approval", readonly=True, tracking=True,
+ copy=False)
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
@@ -240,8 +265,10 @@ class SaleOrder(models.Model):
use_button = fields.Boolean(string='Using Calculate Selling Price', copy=False)
unreserve_id = fields.Many2one('stock.picking', 'Unreserve Picking')
voucher_shipping_id = fields.Many2one(comodel_name='voucher', string='Voucher Shipping', copy=False)
- margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase')
- percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase', compute='_compute_margin_after_delivery_purchase')
+ margin_after_delivery_purchase = fields.Float(string='Margin After Delivery Purchase',
+ compute='_compute_margin_after_delivery_purchase')
+ percent_margin_after_delivery_purchase = fields.Float(string='% Margin After Delivery Purchase',
+ compute='_compute_margin_after_delivery_purchase')
purchase_delivery_amt = fields.Float(string='Purchase Delivery Amount', compute='_compute_purchase_delivery_amount')
type_promotion = fields.Char(string='Type Promotion', compute='_compute_type_promotion')
partner_invoice_id = fields.Many2one(
@@ -255,11 +282,11 @@ class SaleOrder(models.Model):
'res.partner', string='Delivery Address', readonly=True, required=True,
states={'draft': [('readonly', False)], 'sent': [('readonly', False)], 'sale': [('readonly', False)]},
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
-
+
payment_term_id = fields.Many2one(
'account.payment.term', string='Payment Terms', check_company=True, # Unrequired company
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]", tracking=True)
-
+
total_weight = fields.Float(string='Total Weight', compute='_compute_total_weight')
pareto_status = fields.Selection([
('PR', 'Pareto Repeating'),
@@ -276,7 +303,7 @@ class SaleOrder(models.Model):
copy=False
)
shipping_method_picking = fields.Char(string='Shipping Method Picking', compute='_compute_shipping_method_picking')
-
+
reason_cancel = fields.Selection([
('harga_terlalu_mahal', 'Harga barang terlalu mahal'),
('harga_web_tidak_valid', 'Harga web tidak valid'),
@@ -297,7 +324,8 @@ class SaleOrder(models.Model):
string="Attachment Bukti Cancel", readonly=False,
)
nomor_so_pengganti = fields.Char(string='Nomor SO Pengganti', copy=False, tracking=3)
- shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option", domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
+ shipping_option_id = fields.Many2one("shipping.option", string="Selected Shipping Option",
+ domain="['|', ('sale_order_id', '=', False), ('sale_order_id', '=', id)]")
hold_outgoing = fields.Boolean('Hold Outgoing SO', tracking=3)
state_ask_cancel = fields.Selection([
('hold', 'Hold'),
@@ -350,6 +378,13 @@ class SaleOrder(models.Model):
self.hold_outgoing = False
self.date_unhold = fields.Datetime.now()
else:
+ pick = self.env['stock.picking'].search([
+ ('sale_id', '=', self.id),
+ ('state', 'not in', ['cancel', 'done']),
+ ('name', 'ilike', 'BU/PICK/%')
+ ])
+ for picking in pick:
+ picking.do_unreserve()
self.hold_outgoing = True
self.date_hold = fields.Datetime.now()
@@ -363,7 +398,7 @@ class SaleOrder(models.Model):
tax_sets.add(tax_ids)
if len(tax_sets) > 1:
raise ValidationError("Semua produk dalam Sales Order harus memiliki kombinasi pajak yang sama.")
-
+
# @api.constrains('fee_third_party', 'delivery_amt', 'biaya_lain_lain')
# def _check_total_margin_excl_third_party(self):
# for rec in self:
@@ -411,14 +446,14 @@ class SaleOrder(models.Model):
def action_indoteknik_estimate_shipping(self):
if not self.real_shipping_id.kota_id.is_jabodetabek:
raise UserError('Estimasi ongkir hanya bisa dilakukan di kota Jabodetabek')
-
+
total_weight = 0
missing_weight_products = []
for line in self.order_line:
if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
- line.product_id.weight = line.weight
+ line.product_id.weight = line.weight
else:
missing_weight_products.append(line.product_id.name)
@@ -428,10 +463,10 @@ class SaleOrder(models.Model):
if total_weight == 0:
raise UserError("Tidak dapat mengestimasi ongkir tanpa berat yang valid.")
-
+
if total_weight < 10:
total_weight = 10
-
+
self.delivery_amt = total_weight * 3000
shipping_option = self.env["shipping.option"].create({
@@ -464,7 +499,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
if line.weight > 0:
total_weight += line.weight * line.product_uom_qty
- line.product_id.weight = line.weight
+ line.product_id.weight = line.weight
else:
missing_weight_products.append(line.product_id.name)
@@ -489,11 +524,11 @@ class SaleOrder(models.Model):
etd = cost_detail['cost'][0]['etd']
value = cost_detail['cost'][0]['value']
shipping_options.append((service, description, etd, value, courier['code']))
-
+
self.env["shipping.option"].search([('sale_order_id', '=', self.id)]).unlink()
_logger.info(f"Shipping options: {shipping_options}")
-
+
for service, description, etd, value, provider in shipping_options:
self.env["shipping.option"].create({
"name": service,
@@ -503,22 +538,20 @@ class SaleOrder(models.Model):
"sale_order_id": self.id,
})
-
self.shipping_option_id = self.env["shipping.option"].search([('sale_order_id', '=', self.id)], limit=1).id
_logger.info(f"Shipping option SO ID: {self.shipping_option_id}")
self.message_post(
body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>"
- f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}",
+ f"{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}",
message_type="comment"
)
-
+
# self.message_post(body=f"Estimasi Ongkos Kirim: Rp{self.delivery_amt}<br/>Detail Lain:<br/>{'<br/>'.join([f'Service: {s[0]}, Description: {s[1]}, ETD: {s[2]} hari, Cost: Rp {s[3]}' for s in shipping_options])}", message_type="comment")
else:
raise UserError("Gagal mendapatkan estimasi ongkir.")
-
def _call_rajaongkir_api(self, total_weight, destination_subsdistrict_id):
url = 'https://pro.rajaongkir.com/api/cost'
@@ -540,7 +573,7 @@ class SaleOrder(models.Model):
if response.status_code == 200:
return response.json()
return None
-
+
def _normalize_city_name(self, city_name):
city_name = city_name.lower()
@@ -560,7 +593,7 @@ class SaleOrder(models.Model):
}
normalized_city_name = self._normalize_city_name(city_name)
-
+
response = requests.get(url, headers=headers)
if response.status_code == 200:
city_data = response.json()
@@ -574,7 +607,7 @@ class SaleOrder(models.Model):
headers = {
'key': '9b1310f644056d84d60b0af6bb21611a',
}
-
+
response = requests.get(url, headers=headers)
if response.status_code == 200:
subdistrict_data = response.json()
@@ -586,15 +619,15 @@ class SaleOrder(models.Model):
return subdistrict['subdistrict_id']
return None
-
def _compute_type_promotion(self):
for rec in self:
promotion_types = []
for promotion in rec.order_promotion_ids:
for line_program in promotion.program_line_id:
if line_program.promotion_type:
- promotion_types.append(dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type))
-
+ promotion_types.append(
+ dict(line_program._fields['promotion_type'].selection).get(line_program.promotion_type))
+
rec.type_promotion = ', '.join(sorted(set(promotion_types)))
def _compute_purchase_delivery_amount(self):
@@ -618,11 +651,14 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
- order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (order.amount_untaxed-delivery_amt-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
+ order.percent_margin_after_delivery_purchase = round((order.margin_after_delivery_purchase / (
+ order.amount_untaxed - delivery_amt - order.fee_third_party - order.biaya_lain_lain)) * 100, 2)
def _compute_date_kirim(self):
for rec in self:
- picking = self.env['stock.picking'].search([('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')], order='date_doc_kirim desc', limit=1)
+ picking = self.env['stock.picking'].search(
+ [('sale_id', '=', rec.id), ('state', 'not in', ['cancel']), ('name', 'not ilike', 'BU/PICK/%')],
+ order='date_doc_kirim desc', limit=1)
rec.date_kirim_ril = picking.date_doc_kirim
rec.date_status_done = picking.date_done
rec.date_driver_arrival = picking.driver_arrival_date
@@ -634,54 +670,55 @@ class SaleOrder(models.Model):
'so_ids': [x.id for x in self]
}
return action
-
+
def _compute_fullfillment(self):
for rec in self:
- # rec.fullfillment_line.unlink()
- #
- # for line in rec.order_line:
- # line._compute_reserved_from()
+ # rec.fullfillment_line.unlink()
+ #
+ # for line in rec.order_line:
+ # line._compute_reserved_from()
rec.compute_fullfillment = True
@api.depends('date_order', 'estimated_arrival_days', 'state', 'estimated_arrival_days_start')
def _compute_eta_date(self):
- current_date = datetime.now().date()
- for rec in self:
- if rec.date_order and rec.state not in ['cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start:
+ current_date = datetime.now().date()
+ for rec in self:
+ if rec.date_order and rec.state not in [
+ 'cancel'] and rec.estimated_arrival_days and rec.estimated_arrival_days_start:
rec.eta_date = current_date + timedelta(days=rec.estimated_arrival_days)
rec.eta_date_start = current_date + timedelta(days=rec.estimated_arrival_days_start)
else:
rec.eta_date = False
rec.eta_date_start = False
-
-
- def get_days_until_next_business_day(self,start_date=None, *args, **kwargs):
+
+ def get_days_until_next_business_day(self, start_date=None, *args, **kwargs):
today = start_date or datetime.today().date()
offset = 0 # Counter jumlah hari yang ditambahkan
holiday = self.env['hr.public.holiday']
- while True :
+ while True:
today += timedelta(days=1)
offset += 1
-
+
if today.weekday() >= 5:
continue
is_holiday = holiday.search([("start_date", "=", today)])
if is_holiday:
continue
-
+
break
return offset
-
+
def calculate_sla_by_vendor(self, products):
product_ids = products.mapped('product_id.id') # Kumpulkan semua ID produk
include_instant = True # Default True, tetapi bisa menjadi False
# Cek apakah SEMUA produk memiliki qty_free_bandengan >= qty_needed
- all_fast_products = all(product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products)
+ all_fast_products = all(
+ product.product_id.qty_free_bandengan >= product.product_uom_qty for product in products)
if all_fast_products:
return {'slatime': 1, 'include_instant': include_instant}
@@ -691,7 +728,7 @@ class SaleOrder(models.Model):
('is_winner', '=', True)
])
- max_slatime = 1
+ max_slatime = 1
for vendor in vendors:
vendor_sla = self.env['vendor.sla'].search([('id_vendor', '=', vendor.vendor_id.id)], limit=1)
@@ -700,26 +737,26 @@ class SaleOrder(models.Model):
if vendor_sla.unit == 'hari':
vendor_duration = vendor_sla.duration * 24 * 60
include_instant = False
- else :
+ else:
vendor_duration = vendor_sla.duration * 60
include_instant = True
-
+
estimation_sla = (1 * 24 * 60) + vendor_duration
estimation_sla_days = estimation_sla / (24 * 60)
slatime = math.ceil(estimation_sla_days)
-
+
max_slatime = max(max_slatime, slatime)
return {'slatime': max_slatime, 'include_instant': include_instant}
-
+
def _calculate_etrts_date(self):
for rec in self:
if not rec.date_order:
rec.expected_ready_to_ship = False
return
-
+
current_date = datetime.now().date()
-
+
max_slatime = 1 # Default SLA jika tidak ada
slatime = self.calculate_sla_by_vendor(rec.order_line)
max_slatime = max(max_slatime, slatime['slatime'])
@@ -727,29 +764,28 @@ class SaleOrder(models.Model):
sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
if not rec.estimated_arrival_days:
rec.estimated_arrival_days = sum_days
-
+
eta_date = current_date + timedelta(days=sum_days)
rec.commitment_date = eta_date
rec.expected_ready_to_ship = eta_date
-
- @api.depends("order_line.product_id", "date_order")
- def _compute_etrts_date(self): #Function to calculate Estimated Ready To Ship Date
+
+ @api.depends("order_line.product_id", "date_order")
+ def _compute_etrts_date(self): # Function to calculate Estimated Ready To Ship Date
self._calculate_etrts_date()
-
-
+
def _validate_expected_ready_ship_date(self):
for rec in self:
if rec.expected_ready_to_ship and rec.commitment_date:
current_date = datetime.now().date()
# Hanya membandingkan tanggal saja, tanpa jam
expected_date = rec.expected_ready_to_ship.date()
-
+
max_slatime = 1 # Default SLA jika tidak ada
slatime = self.calculate_sla_by_vendor(rec.order_line)
max_slatime = max(max_slatime, slatime['slatime'])
sum_days = max_slatime + self.get_days_until_next_business_day(current_date) - 1
eta_minimum = current_date + timedelta(days=sum_days)
-
+
if expected_date < eta_minimum:
rec.expected_ready_to_ship = eta_minimum
raise ValidationError(
@@ -758,16 +794,16 @@ class SaleOrder(models.Model):
)
else:
rec.commitment_date = rec.expected_ready_to_ship
-
-
- @api.onchange('expected_ready_to_ship') #Hangle Onchange form Expected Ready to Ship
+
+ @api.onchange('expected_ready_to_ship') # Hangle Onchange form Expected Ready to Ship
def _onchange_expected_ready_ship_date(self):
self._validate_expected_ready_ship_date()
def _set_etrts_date(self):
for order in self:
if order.state in ('done', 'cancel', 'sale'):
- raise UserError(_("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order."))
+ raise UserError(
+ _("You cannot change the Estimated Ready To Ship Date on a done, sale or cancelled order."))
# order.move_lines.write({'estimated_ready_ship_date': order.estimated_ready_ship_date})
def _prepare_invoice(self):
@@ -779,10 +815,12 @@ class SaleOrder(models.Model):
self.ensure_one()
journal = self.env['account.move'].with_context(default_move_type='out_invoice')._get_default_journal()
if not journal:
- raise UserError(_('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name, self.company_id.id))
+ raise UserError(
+ _('Please define an accounting sales journal for the company %s (%s).') % (self.company_id.name,
+ self.company_id.id))
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
invoice_vals = {
'ref': self.client_order_ref or '',
@@ -799,7 +837,8 @@ class SaleOrder(models.Model):
'partner_id': parent_id.id,
'partner_shipping_id': parent_id.id,
'real_invoice_id': self.real_invoice_id.id,
- 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(self.partner_invoice_id.id)).id,
+ 'fiscal_position_id': (self.fiscal_position_id or self.fiscal_position_id.get_fiscal_position(
+ self.partner_invoice_id.id)).id,
'partner_bank_id': self.company_id.partner_id.bank_ids[:1].id,
'journal_id': journal.id, # company comes from the journal
'invoice_origin': self.name,
@@ -815,10 +854,10 @@ class SaleOrder(models.Model):
def _validate_email(self):
rule_regex = self.env['ir.config_parameter'].sudo().get_param('sale.order.validate_email') or ''
pattern = rf'^{rule_regex}$'
-
+
if self.email and not re.match(pattern, self.email):
raise UserError('Email yang anda input kurang valid')
-
+
# @api.constrains('delivery_amt', 'carrier_id', 'shipping_cost_covered')
def _validate_delivery_amt(self):
is_indoteknik = self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik'
@@ -830,13 +869,14 @@ class SaleOrder(models.Model):
raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum diisi.')
else:
raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum diisi.')
-
+
if self.delivery_amt < 100:
if self.carrier_id.id == 1:
- raise UserError('Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.')
+ raise UserError(
+ 'Untuk Kurir Indoteknik Delivery, estimasi ongkos kirim belum memenuhi tarif minimum.')
else:
- raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.')
-
+ raise UserError(
+ 'Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi tarif minimum.')
# if self.delivery_amt < 5000:
# if (self.carrier_id.id == 1 or self.shipping_cost_covered == 'indoteknik') and not self.env.context.get('active_id', []):
@@ -845,7 +885,6 @@ class SaleOrder(models.Model):
# else:
# raise UserError('Untuk Shipping Covered Indoteknik, estimasi ongkos kirim belum memenuhi jumlah minimum.')
-
def override_allow_create_invoice(self):
if not self.env.user.is_accounting:
raise UserError('Hanya Finance Accounting yang dapat klik tombol ini')
@@ -886,14 +925,14 @@ class SaleOrder(models.Model):
'sale_ids': [x.id for x in self]
}
return action
-
+
def open_form_multi_update_state(self):
action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_quotation_so_multi_update')
action['context'] = {
'quotation_ids': [x.id for x in self]
}
return action
-
+
def action_multi_update_invoice_status(self):
for sale in self:
sale.update({
@@ -906,7 +945,7 @@ class SaleOrder(models.Model):
for line in order.order_line:
total += line.vendor_subtotal
order.purchase_total = total
-
+
def check_data_real_delivery_address(self):
real_delivery_address = self.real_shipping_id
@@ -926,8 +965,8 @@ class SaleOrder(models.Model):
def generate_payment_link_midtrans_sales_order(self):
# midtrans_url = 'https://app.sandbox.midtrans.com/snap/v1/transactions' # dev - sandbox
# midtrans_auth = 'Basic U0ItTWlkLXNlcnZlci1uLVY3ZDJjMlpCMFNWRUQyOU95Q1dWWXA6' # dev - sandbox
- midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production
- midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production
+ midtrans_url = 'https://app.midtrans.com/snap/v1/transactions' # production
+ midtrans_auth = 'Basic TWlkLXNlcnZlci1SbGMxZ2gzWGpSVW5scl9JblZzTV9OTnU6' # production
so_number = self.name
so_number = so_number.replace('/', '-')
so_grandtotal = math.floor(self.grand_total)
@@ -942,7 +981,8 @@ class SaleOrder(models.Model):
if check_response.status_code == 200:
status_response = check_response.json()
- if status_response.get('transaction_status') == 'expire' or status_response.get('transaction_status') == 'cancel':
+ if status_response.get('transaction_status') == 'expire' or status_response.get(
+ 'transaction_status') == 'cancel':
so_number = so_number + '-cpl'
json_data = {
@@ -990,8 +1030,7 @@ class SaleOrder(models.Model):
if line.product_id.type == 'product':
line_no += 1
line.line_no = line_no
-
-
+
def write(self, vals):
if 'carrier_id' in vals:
for picking in self.picking_ids:
@@ -1004,7 +1043,7 @@ class SaleOrder(models.Model):
('state', 'in', so_state),
('so_status', '!=', 'terproses'),
])
-
+
for sale in sales:
picking_states = ['draft', 'assigned', 'confirmed', 'waiting']
have_outstanding_pick = any(x.state in picking_states for x in sale.picking_ids)
@@ -1018,16 +1057,16 @@ class SaleOrder(models.Model):
sale.so_status = 'terproses'
else:
sale.so_status = 'menunggu'
-
+
for picking in sale.picking_ids:
sum_qty_pick = sum(move_line.product_uom_qty for move_line in picking.move_ids_without_package)
sum_qty_reserved = sum(move_line.product_uom_qty for move_line in picking.move_line_ids_without_package)
if picking.state == 'done':
continue
- elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved:# baru ke reserved
+ elif sum_qty_pick == sum_qty_reserved and not picking.date_reserved: # baru ke reserved
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
picking.date_reserved = current_time
- elif sum_qty_pick == sum_qty_reserved:# sudah ada data reserved
+ elif sum_qty_pick == sum_qty_reserved: # sudah ada data reserved
picking.date_reserved = picking.date_reserved
else:
picking.date_reserved = ''
@@ -1062,7 +1101,7 @@ class SaleOrder(models.Model):
@api.onchange('partner_id')
def onchange_partner_contact(self):
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
self.npwp = parent_id.npwp
self.sppkp = parent_id.sppkp
@@ -1070,13 +1109,13 @@ class SaleOrder(models.Model):
self.email = parent_id.email
self.pareto_status = parent_id.pareto_status
self.user_id = parent_id.user_id
-
+
@api.onchange('partner_id')
def onchange_partner_id(self):
# INHERIT
result = super(SaleOrder, self).onchange_partner_id()
parent_id = self.partner_id.parent_id
- parent_id = parent_id if parent_id else self.partner_id
+ parent_id = parent_id if parent_id else self.partner_id
self.partner_invoice_id = parent_id
return result
@@ -1109,16 +1148,16 @@ class SaleOrder(models.Model):
minimum_amount = 20000000
for order in self:
order.have_visit_service = self.amount_total > minimum_amount
-
+
def _get_helper_ids(self):
helper_ids_str = self.env['ir.config_parameter'].sudo().get_param('sale.order.user_helper_ids')
return helper_ids_str.split(', ')
-
+
def write(self, values):
helper_ids = self._get_helper_ids()
if str(self.env.user.id) in helper_ids:
values['helper_by_id'] = self.env.user.id
-
+
return super(SaleOrder, self).write(values)
def check_due(self):
@@ -1136,21 +1175,21 @@ class SaleOrder(models.Model):
def _validate_order(self):
if self.payment_term_id.id == 31 and self.total_percent_margin < 25:
raise UserError("Jika ingin menggunakan Tempo 90 Hari maka margin harus di atas 25%")
-
- if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: #GD Bandengan
+
+ if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: # GD Bandengan
raise UserError('Gudang harus Bandengan')
-
+
if self.state not in ['draft', 'sent']:
raise UserError("Status harus draft atau sent")
-
+
self._validate_npwp()
-
+
def _validate_npwp(self):
num_digits = sum(c.isdigit() for c in self.npwp)
if num_digits < 10:
raise UserError("NPWP harus memiliki minimal 10 digit")
-
+
# pattern = r'^\d{10,}$'
# return re.match(pattern, self.npwp) is not None
@@ -1178,7 +1217,8 @@ class SaleOrder(models.Model):
if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp:
raise UserError("SPPKP berbeda pada Master Data Customer")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.user_id.active:
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
@@ -1186,20 +1226,23 @@ class SaleOrder(models.Model):
for order in self:
for line in order.order_line:
if 'bom-it' in line.name.lower() or 'bom' in line.product_id.default_code.lower() if line.product_id.default_code else False:
- search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id)],order='name desc')
+ search_bom = self.env['mrp.production'].search([('product_id', '=', line.product_id.id), ('sale_order', '=', order.id), ('state', '!=', 'cancel')],
+ order='name desc')
if search_bom:
- confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed')
+ confirmed_bom = search_bom.filtered(lambda x: x.state == 'confirmed' or x.state == 'done')
if not confirmed_bom:
- raise UserError("Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.")
+ raise UserError(
+ "Product BOM belum dikonfirmasi di Manufacturing Orders. Silakan hubungi MD.")
else:
raise UserError("Product BOM tidak di temukan di manufacturing orders, silahkan hubungi MD")
-
+
def check_duplicate_product(self):
for order in self:
for line in order.order_line:
- search_product = self.env['sale.order.line'].search([('product_id', '=', line.product_id.id), ('order_id', '=', order.id)])
- if len(search_product) > 1:
- raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name))
+ search_product = self.env['sale.order.line'].search(
+ [('product_id', '=', line.product_id.id), ('order_id', '=', order.id)])
+ if len(search_product) > 1:
+ raise UserError("Terdapat DUPLIKASI data pada Product {}".format(line.product_id.display_name))
def sale_order_approve(self):
self.check_duplicate_product()
@@ -1222,11 +1265,13 @@ class SaleOrder(models.Model):
SYSTEM_UID = 25
FROM_WEBSITE = order.create_uid.id == SYSTEM_UID
- if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement','cust_director']:
+ if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement',
+ 'cust_director']:
raise UserError("This order not yet approved by customer procurement or director")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.commitment_date and order.create_date > datetime(2024, 9, 12):
raise UserError("Expected Delivery Date kosong, wajib diisi")
@@ -1234,8 +1279,10 @@ class SaleOrder(models.Model):
if not order.real_shipping_id:
UserError('Real Delivery Address harus di isi')
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification','Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+ if self.env.context.get('due_approve', []) == False:
+ if order.validate_partner_invoice_due():
+ return self._create_notification_action('Notification',
+ 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
term_days = 0
for term_line in order.payment_term_id.line_ids:
@@ -1253,13 +1300,15 @@ class SaleOrder(models.Model):
if (partner.customer_type == 'pkp' or order.customer_type == 'pkp') and order.sppkp != partner.sppkp:
raise UserError("SPPKP berbeda pada Master Data Customer")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.user_id.active:
raise UserError("Salesperson sudah tidak aktif, mohon diisi yang benar pada data SO dan Contact")
-
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
-
+
+ # if order.validate_partner_invoice_due():
+ # return self._create_notification_action('Notification',
+ # 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
@@ -1269,9 +1318,9 @@ class SaleOrder(models.Model):
self.check_limit_so_to_invoice()
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
-
+
raise UserError("Bisa langsung Confirm")
-
+
def send_notif_to_salesperson(self, cancel=False):
if not cancel:
@@ -1289,7 +1338,9 @@ class SaleOrder(models.Model):
salesperson_data = {}
for rec in grouping_so:
if rec.user_id.id not in salesperson_data:
- salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0, 'sum_total_amount': 0, 'business_partner': '', 'site': ''} # Menetapkan nilai awal untuk 'site'
+ salesperson_data[rec.user_id.id] = {'name': rec.user_id.name, 'orders': [], 'total_amount': 0,
+ 'sum_total_amount': 0, 'business_partner': '',
+ 'site': ''} # Menetapkan nilai awal untuk 'site'
if rec.picking_ids:
if not any(picking.state in ['assigned', 'confirmed', 'waiting'] for picking in rec.picking_ids):
continue
@@ -1308,7 +1359,8 @@ class SaleOrder(models.Model):
})
salesperson_data[rec.user_id.id]['sum_total_amount'] += order_total_amount
salesperson_data[rec.user_id.id]['business_partner'] = grouping_so[0].partner_id.main_parent_id.name
- salesperson_data[rec.user_id.id]['site'] = grouping_so[0].partner_id.site_id.name # Menambahkan nilai hanya jika ada
+ salesperson_data[rec.user_id.id]['site'] = grouping_so[
+ 0].partner_id.site_id.name # Menambahkan nilai hanya jika ada
# Kirim email untuk setiap salesperson
for salesperson_id, data in salesperson_data.items():
@@ -1332,9 +1384,9 @@ class SaleOrder(models.Model):
template = self.env.ref('indoteknik_custom.mail_template_sale_order_notification_to_salesperson')
email_body = template.body_html.replace('${table_content}', table_content)
email_body = email_body.replace('${salesperson_name}', data['name'])
- email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount']))
- email_body = email_body.replace('${site}', str(data['site']))
- email_body = email_body.replace('${business_partner}', str(data['business_partner']))
+ email_body = email_body.replace('${sum_total_amount}', str(data['sum_total_amount']))
+ email_body = email_body.replace('${site}', str(data['site']))
+ email_body = email_body.replace('${business_partner}', str(data['business_partner']))
# Kirim email
self.env['mail.mail'].create({
'subject': 'Notification: Sale Orders',
@@ -1370,8 +1422,9 @@ class SaleOrder(models.Model):
if (outstanding_amount + rec.amount_total) >= block_stage:
if block_stage != 0:
remaining_credit_limit = block_stage - outstanding_amount
- raise UserError(_("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s")
- % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount))
+ raise UserError(
+ _("%s is in Blocking Stage, Remaining credit limit is %s, from %s and outstanding %s")
+ % (rec.partner_id.name, remaining_credit_limit, block_stage, outstanding_amount))
def check_limit_so_to_invoice(self):
for rec in self:
@@ -1393,7 +1446,8 @@ class SaleOrder(models.Model):
# Validasi limit
if remaining_credit_limit <= 0 and block_stage > 0 and not is_cbd:
- raise UserError(_("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.")
+ raise UserError(
+ _("The credit limit for %s will exceed the Blocking Stage if the Sale Order is confirmed. The remaining credit limit is %s, from %s and the outstanding amount is %s.")
% (rec.partner_id.name, block_stage - current_total, block_stage, outstanding_amount))
def validate_different_vendor(self):
@@ -1403,11 +1457,11 @@ class SaleOrder(models.Model):
if self.vendor_approval_id and all(v.state != 'draft' for v in self.vendor_approval_id):
return False
-
+
different_vendor = self.order_line.filtered(
lambda l: l.vendor_id and l.vendor_md_id and l.vendor_id.id != l.vendor_md_id.id
)
-
+
if different_vendor:
vendor_approvals = []
for line in different_vendor:
@@ -1433,15 +1487,14 @@ class SaleOrder(models.Model):
if line.purchase_price_md else False
),
})
-
+
vendor_approvals.append(vendor_approval.id)
-
+
self.vendor_approval_id = [(4, vid) for vid in vendor_approvals]
return True
else:
return False
-
def action_confirm(self):
for order in self:
order._validate_uniform_taxes()
@@ -1451,38 +1504,42 @@ class SaleOrder(models.Model):
order.check_limit_so_to_invoice()
if self.validate_different_vendor() and not self.vendor_approval:
return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
-
+
order.check_data_real_delivery_address()
order.sale_order_check_approve()
order._validate_order()
order.order_line.validate_line()
-
+
main_parent = order.partner_id.get_main_parent()
SYSTEM_UID = 25
FROM_WEBSITE = order.create_uid.id == SYSTEM_UID
-
- if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement', 'cust_director']:
+
+ if FROM_WEBSITE and main_parent.use_so_approval and order.web_approval not in ['cust_procurement',
+ 'cust_director']:
raise UserError("This order not yet approved by customer procurement or director")
if not order.client_order_ref and order.create_date > datetime(2024, 6, 27):
- raise UserError("Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
+ raise UserError(
+ "Customer Reference kosong, di isi dengan NO PO jika PO tidak ada mohon ditulis Tanpa PO")
if not order.commitment_date and order.create_date > datetime(2024, 9, 12):
raise UserError("Expected Delivery Date kosong, wajib diisi")
-
+
if not order.real_shipping_id:
UserError('Real Delivery Address harus di isi')
-
- if order.validate_partner_invoice_due():
- return self._create_notification_action('Notification', 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
-
+
+ if self.env.context.get('due_approve', []) == False:
+ if order.validate_partner_invoice_due():
+ return self._create_notification_action('Notification',
+ 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
elif order._requires_approval_margin_manager():
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
-
+
order.approval_status = 'approved'
order._set_sppkp_npwp_contact()
order.calculate_line_no()
@@ -1496,7 +1553,7 @@ class SaleOrder(models.Model):
for line in order.order_line:
if line.display_type == 'line_note':
note.append(line.name)
-
+
if order.picking_ids:
# Sort picking_ids by creation date to get the most recent one
latest_picking = order.picking_ids.sorted(key=lambda p: p.create_date, reverse=True)[0]
@@ -1510,7 +1567,7 @@ class SaleOrder(models.Model):
main_parent = self.partner_id.get_main_parent()
if self._name != 'sale.order':
return super(SaleOrder, self).action_cancel()
-
+
if self.have_outstanding_invoice:
raise UserError("Invoice harus di Cancel dahulu")
@@ -1521,7 +1578,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
if line.qty_delivered > 0:
raise UserError("DO yang done harus di-Return oleh Logistik")
-
+
if not self.web_approval:
self.web_approval = 'company'
# elif self.have_outstanding_po:
@@ -1551,11 +1608,11 @@ class SaleOrder(models.Model):
def validate_partner_invoice_due(self):
parent_id = self.partner_id.parent_id.id
- parent_id = parent_id if parent_id else self.partner_id.id
+ parent_id = parent_id if parent_id else self.partner_id.id
if self.due_id and self.due_id.is_approve == False:
raise UserError('Document Over Due Yang Anda Buat Belum Di Approve')
-
+
query = [
('partner_id', '=', parent_id),
('state', '=', 'posted'),
@@ -1565,40 +1622,39 @@ class SaleOrder(models.Model):
invoices = self.env['account.move'].search(query, order='invoice_date')
if invoices:
- if not self.env.user.is_leader and not self.env.user.is_sales_manager:
- due_extension = self.env['due.extension'].create([{
- 'partner_id': parent_id,
- 'day_extension': '3',
- 'order_id': self.id,
- }])
- due_extension.generate_due_line()
- self.due_id = due_extension.id
- if len(self.due_id.due_line) > 0:
- return True
- else:
- due_extension.unlink()
- return False
-
+ due_extension = self.env['due.extension'].create([{
+ 'partner_id': parent_id,
+ 'day_extension': '3',
+ 'order_id': self.id,
+ }])
+ due_extension.generate_due_line()
+ self.due_id = due_extension.id
+ if len(self.due_id.due_line) > 0:
+ return True
+ else:
+ due_extension.unlink()
+ return False
+
def _requires_approval_margin_leader(self):
return self.total_percent_margin <= 15 and not self.env.user.is_leader
-
+
def _requires_approval_margin_manager(self):
- return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.is_leader
+ return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.id == 375 and not self.env.user.is_leader
# return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager
-
+
def _create_approval_notification(self, approval_role):
title = 'Warning'
message = f'SO butuh approval {approval_role}'
return self._create_notification_action(title, message)
-
+
def _create_notification_action(self, title, message):
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
- 'params': { 'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'} },
+ 'params': {'title': title, 'message': message, 'next': {'type': 'ir.actions.act_window_close'}},
}
-
- def _set_sppkp_npwp_contact(self):
+
+ def _set_sppkp_npwp_contact(self):
partner = self.partner_id.parent_id or self.partner_id
if not partner.sppkp:
@@ -1617,7 +1673,7 @@ class SaleOrder(models.Model):
# partner.npwp = self.npwp
# partner.sppkp = self.sppkp
# partner.email = self.email
-
+
def _compute_total_margin(self):
for order in self:
total_margin = sum(line.item_margin for line in order.order_line if line.product_id)
@@ -1625,7 +1681,7 @@ class SaleOrder(models.Model):
total_margin -= order.ongkir_ke_xpdc
order.total_margin = total_margin
-
+
def _compute_total_before_margin(self):
for order in self:
total_before_margin = sum(line.item_before_margin for line in order.order_line if line.product_id)
@@ -1640,9 +1696,10 @@ class SaleOrder(models.Model):
delivery_amt = order.delivery_amt
else:
delivery_amt = 0
-
+
# order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-delivery_amt-order.fee_third_party)) * 100, 2)
- order.total_percent_margin = round((order.total_margin / (order.amount_untaxed-order.fee_third_party-order.biaya_lain_lain)) * 100, 2)
+ order.total_percent_margin = round(
+ (order.total_margin / (order.amount_untaxed - order.fee_third_party - order.biaya_lain_lain)) * 100, 2)
# order.total_percent_margin = round((order.total_margin / (order.amount_untaxed)) * 100, 2)
@api.onchange('sales_tax_id')
@@ -1665,20 +1722,20 @@ class SaleOrder(models.Model):
voucher = self.voucher_id
if voucher.limit > 0 and voucher.count_order >= voucher.limit:
raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan')
-
+
partner_voucher_orders = []
for order in voucher.order_ids:
if order.partner_id.id == self.partner_id.id:
partner_voucher_orders.append(order)
-
+
if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher')
-
+
if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]:
raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher')
-
+
self.apply_voucher()
-
+
def action_apply_voucher_shipping(self):
for line in self.order_line:
if line.order_promotion_id:
@@ -1687,18 +1744,18 @@ class SaleOrder(models.Model):
voucher = self.voucher_shipping_id
if voucher.limit > 0 and voucher.count_order >= voucher.limit:
raise UserError('Voucher tidak dapat digunakan karena sudah habis digunakan')
-
+
partner_voucher_orders = []
for order in voucher.order_ids:
if order.partner_id.id == self.partner_id.id:
partner_voucher_orders.append(order)
-
+
if voucher.limit_user > 0 and len(partner_voucher_orders) >= voucher.limit_user:
raise UserError('Voucher tidak dapat digunakan karena Customer ini sudah menghabiskan kuota voucher')
-
+
if self.pricelist_id.id in [x.id for x in voucher.excl_pricelist_ids]:
raise UserError('Voucher tidak dapat digunakan karena pricelist ini tidak berlaku pada voucher')
-
+
self.apply_voucher_shipping()
def apply_voucher(self):
@@ -1715,7 +1772,7 @@ class SaleOrder(models.Model):
for line in self.order_line:
line.initial_discount = line.discount
-
+
voucher_type = voucher['type']
used_total = voucher['total'][voucher_type]
used_discount = voucher['discount'][voucher_type]
@@ -1731,11 +1788,11 @@ class SaleOrder(models.Model):
line_contribution = line.price_subtotal / used_total
line_voucher = used_discount * line_contribution
line_voucher_item = line_voucher / line.product_uom_qty
-
+
line_price_unit = line.price_unit / 1.11 if any(tax.id == 23 for tax in line.tax_id) else line.price_unit
line_discount_item = line_price_unit * line.discount / 100 + line_voucher_item
line_voucher_item = line_discount_item / line_price_unit * 100
-
+
line.amount_voucher_disc = line_voucher
line.discount = line_voucher_item
@@ -1746,27 +1803,27 @@ class SaleOrder(models.Model):
for order in self:
delivery_amt = order.delivery_amt
voucher = order.voucher_shipping_id
-
+
if voucher:
max_discount_amount = voucher.discount_amount
voucher_type = voucher.discount_type
-
+
if voucher_type == 'fixed_price':
discount = max_discount_amount
elif voucher_type == 'percentage':
discount = delivery_amt * (max_discount_amount / 100)
-
+
delivery_amt -= discount
-
+
delivery_amt = max(delivery_amt, 0)
-
+
order.delivery_amt = delivery_amt
-
+
order.amount_voucher_shipping_disc = discount
order.applied_voucher_shipping_id = order.voucher_id.id
def cancel_voucher(self):
- self.applied_voucher_id = False
+ self.applied_voucher_id = False
self.amount_voucher_disc = 0
for line in self.order_line:
line.amount_voucher_disc = 0
@@ -1775,17 +1832,18 @@ class SaleOrder(models.Model):
def cancel_voucher_shipping(self):
self.delivery_amt + self.amount_voucher_shipping_disc
- self.applied_voucher_shipping_id = False
+ self.applied_voucher_shipping_id = False
self.amount_voucher_shipping_disc = 0
def action_web_approve(self):
if self.env.uid != self.partner_id.user_id.id:
- raise UserError('You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name)
-
+ raise UserError(
+ 'You are not authorized to approve this order. Only %s can approve this order.' % self.partner_id.user_id.name)
+
self.web_approval = 'company'
template = self.env.ref('indoteknik_custom.mail_template_sale_order_web_approve_notification')
template.send_mail(self.id, force_send=True)
-
+
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
@@ -1845,13 +1903,14 @@ class SaleOrder(models.Model):
if last_so and rec_purchase_price != last_so.purchase_price:
rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1)
if rec_taxes.price_include:
- selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100))
+ selling_price = (rec_purchase_price / 1.11) / (
+ 1 - (last_so.item_percent_margin_without_deduction / 100))
else:
selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100))
tax_id = last_so.tax_id
for tax in tax_id:
if tax.price_include:
- selling_price = selling_price + (selling_price*11/100)
+ selling_price = selling_price + (selling_price * 11 / 100)
else:
selling_price = selling_price
discount = 0
@@ -1867,13 +1926,14 @@ class SaleOrder(models.Model):
elif last_so and rec_vendor_id == order_line.vendor_id.id and rec_purchase_price != last_so.purchase_price:
rec_taxes = self.env['account.tax'].search([('id', '=', rec_taxes_id)], limit=1)
if rec_taxes.price_include:
- selling_price = (rec_purchase_price / 1.11) / (1 - (last_so.item_percent_margin_without_deduction / 100))
+ selling_price = (rec_purchase_price / 1.11) / (
+ 1 - (last_so.item_percent_margin_without_deduction / 100))
else:
selling_price = rec_purchase_price / (1 - (last_so.item_percent_margin_without_deduction / 100))
tax_id = last_so.tax_id
for tax in tax_id:
if tax.price_include:
- selling_price = selling_price + (selling_price*11/100)
+ selling_price = selling_price + (selling_price * 11 / 100)
else:
selling_price = selling_price
discount = 0
@@ -1904,14 +1964,14 @@ class SaleOrder(models.Model):
return order
# def write(self, vals):
- # Call the super method to handle the write operation
- # res = super(SaleOrder, self).write(vals)
- # self._compute_etrts_date()
- # Check if the update is coming from a save operation
- # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
- # self._update_partner_details()
+ # Call the super method to handle the write operation
+ # res = super(SaleOrder, self).write(vals)
+ # self._compute_etrts_date()
+ # Check if the update is coming from a save operation
+ # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
+ # self._update_partner_details()
- # return res
+ # return res
def _update_partner_details(self):
for order in self:
@@ -1940,11 +2000,11 @@ class SaleOrder(models.Model):
if command[0] == 0: # A new line is being added
raise UserError(
"SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
-
+
res = super(SaleOrder, self).write(vals)
# self._check_total_margin_excl_third_party()
if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']):
self._validate_delivery_amt()
if any(field in vals for field in ["order_line", "client_order_ref"]):
- self._calculate_etrts_date()
- return res \ No newline at end of file
+ self._calculate_etrts_date()
+ return res
diff --git a/indoteknik_custom/models/shipment_group.py b/indoteknik_custom/models/shipment_group.py
index b7d7ac12..48a3fa21 100644
--- a/indoteknik_custom/models/shipment_group.py
+++ b/indoteknik_custom/models/shipment_group.py
@@ -87,20 +87,17 @@ class ShipmentGroupLine(models.Model):
if self.picking_id:
picking = self.env['stock.picking'].browse(self.picking_id.id)
- if self.shipment_id.partner_id and self.shipment_id.partner_id != picking.partner_id:
- raise UserError('Partner must be same as shipment group')
-
if self.shipment_id.carrier_id and self.shipment_id.carrier_id != picking.carrier_id:
raise UserError('carrier must be same as shipment group')
+ if picking.total_mapping_koli == 0:
+ raise UserError(f'Picking {picking.name} tidak memiliki mapping koli')
+
self.partner_id = picking.partner_id
self.shipping_paid_by = picking.sale_id.shipping_paid_by
self.carrier_id = picking.carrier_id.id
self.total_colly = picking.total_mapping_koli
- if not self.shipment_id.partner_id:
- self.shipment_id.partner_id = picking.partner_id
-
if not self.shipment_id.carrier_id:
self.shipment_id.carrier_id = picking.carrier_id
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 6a6fe352..9b8f6775 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -280,7 +280,7 @@ class StockPicking(models.Model):
state_packing = fields.Selection([('not_packing', 'Belum Packing'), ('packing_done', 'Sudah Packing')],
string='Packing Status')
approval_invoice_date_id = fields.Many2one('approval.invoice.date', string='Approval Invoice Date')
- last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim')
+ last_update_date_doc_kirim = fields.Datetime(string='Last Update Tanggal Kirim', copy=False)
update_date_doc_kirim_add = fields.Boolean(string='Update Tanggal Kirim Lewat ADD')
def _get_kgx_awb_number(self):
@@ -1012,6 +1012,8 @@ class StockPicking(models.Model):
self.sale_id.date_doc_kirim = self.date_doc_kirim
def action_assign(self):
+ if self.sale_id.hold_outgoing == True and self.location_id.id == 57 and self.location_dest_id.id == 60:
+ raise UserError("SO on hold")
res = super(StockPicking, self).action_assign()
current_time = datetime.datetime.utcnow()
self.real_shipping_id = self.sale_id.real_shipping_id
diff --git a/indoteknik_custom/views/barcoding_product.xml b/indoteknik_custom/views/barcoding_product.xml
index 55876580..b259f1e8 100644
--- a/indoteknik_custom/views/barcoding_product.xml
+++ b/indoteknik_custom/views/barcoding_product.xml
@@ -20,6 +20,7 @@
<tree>
<field name="product_id"/>
<field name="qr_code_variant" widget="image"/>
+ <field name="sequence_with_total" attrs="{'invisible': [['parent.type', 'not in', ('multiparts')]]}"/>
</tree>
</field>
</record>
@@ -35,8 +36,8 @@
<field name="product_id" required="1"/>
<field name="type" required="1"/>
<field name="quantity" attrs="{'invisible': [['type', 'in', ('barcoding','barcoding_box')]], 'required': [['type', 'not in', ('barcoding')]]}"/>
- <field name="barcode" attrs="{'invisible': [['type', 'in', ('print')]], 'required': [['type', 'not in', ('print')]]}"/>
- <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding')]], 'required': [['type', 'not in', ('print', 'barcoding')]]}"/>
+ <field name="barcode" attrs="{'invisible': [['type', 'in', ('print','multiparts')]], 'required': [['type', 'not in', ('print','multiparts')]]}"/>
+ <field name="qty_pcs_box" attrs="{'invisible': [['type', 'in', ('print','barcoding','multiparts')]], 'required': [['type', 'not in', ('print','barcoding','multiparts')]]}"/>
</group>
</group>
<notebook>
diff --git a/indoteknik_custom/views/purchase_pricelist.xml b/indoteknik_custom/views/purchase_pricelist.xml
index ca5cd416..409e3b6a 100755
--- a/indoteknik_custom/views/purchase_pricelist.xml
+++ b/indoteknik_custom/views/purchase_pricelist.xml
@@ -20,6 +20,8 @@
<field name="count_brand_vendor" optional="hide"/>
<field name="product_categ_ids" string="Product Category" optional="hide"/>
<field name="is_winner" string="Winner" optional="hide"/>
+ <field name="message_follower_ids" widget="mail_followers" optional="hide"/>
+ <field name="activity_ids" widget="mail_activity" optional="hide"/>
</tree>
</field>
</record>
@@ -52,6 +54,11 @@
</group>
</group>
</sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="activity_ids" widget="mail_activity"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 3418deaf..a599a7b8 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -4,91 +4,94 @@
<record id="sale_order_form_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_form" />
+ <field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<button id="action_confirm" position="after">
<button name="calculate_line_no"
- string="Create No"
- type="object"
+ string="Create No"
+ type="object"
/>
<button name="sale_order_approve"
- string="Ask Approval"
- type="object"
- attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
+ string="Ask Approval"
+ type="object"
+ attrs="{'invisible': [('approval_status', '=', ['approved'])]}"
/>
<button name="hold_unhold_qty_outgoing_so"
- string="Hold/Unhold Outgoing"
- type="object"
- attrs="{'invisible': [('state', 'in', ['cancel'])]}"
+ string="Hold/Unhold Outgoing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
/>
<button name="ask_retur_cancel_purchasing"
- string="Ask Cancel Purchasing"
- type="object"
- attrs="{'invisible': [('state', 'in', ['cancel'])]}"
+ string="Ask Cancel Purchasing"
+ type="object"
+ attrs="{'invisible': [('state', 'in', ['cancel'])]}"
/>
<button name="action_web_approve"
- string="Web Approve"
- type="object"
- attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
+ string="Web Approve"
+ type="object"
+ attrs="{'invisible': ['|', '|', ('create_uid', '!=', 25), ('web_approval', '!=', False), ('state', '!=', 'draft')]}"
/>
<button name="indoteknik_custom.action_view_uangmuka_penjualan"
- string="UangMuka"
- type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}" />
+ string="UangMuka"
+ type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}"/>
</button>
<field name="payment_term_id" position="after">
- <field name="create_uid" invisible="1" />
- <field name="create_date" invisible="1" />
+ <field name="create_uid" invisible="1"/>
+ <field name="create_date" invisible="1"/>
<field name="shipping_cost_covered"
- attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
<field name="shipping_paid_by"
- attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}" />
- <field name="delivery_amt" />
- <field name="ongkir_ke_xpdc" />
- <field name="metode_kirim_ke_xpdc" />
- <field name="fee_third_party" />
- <field name="biaya_lain_lain" />
- <field name="total_percent_margin" />
- <field name="total_margin_excl_third_party" readonly="1" />
- <field name="type_promotion" />
- <label for="voucher_id" />
+ attrs="{'required': ['|', ('create_date', '&gt;', '2023-06-15'), ('create_date', '=', False)]}"/>
+ <field name="delivery_amt"/>
+ <field name="ongkir_ke_xpdc"/>
+ <field name="metode_kirim_ke_xpdc"/>
+ <field name="fee_third_party"/>
+ <field name="biaya_lain_lain"/>
+ <field name="total_percent_margin"/>
+ <field name="total_margin_excl_third_party" readonly="1"/>
+ <field name="type_promotion"/>
+ <label for="voucher_id"/>
<div class="o_row">
<field name="voucher_id" id="voucher_id"
- attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}" />
- <field name="applied_voucher_id" invisible="1" />
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"/>
+ <field name="applied_voucher_id" invisible="1"/>
<button name="action_apply_voucher" type="object" string="Apply"
- confirm="Anda yakin untuk menggunakan voucher?"
- help="Apply the selected voucher" class="btn-link mb-1 px-0"
- icon="fa-plus"
- attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
+ attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_id', '!=', False)]}"
/>
<button name="cancel_voucher" type="object" string="Cancel"
- confirm="Anda yakin untuk membatalkan penggunaan voucher?"
- help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
- attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ attrs="{'invisible': ['|', ('applied_voucher_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
- <label for="voucher_shipping_id" />
+ <label for="voucher_shipping_id"/>
<div class="o_row">
<field name="voucher_shipping_id" id="voucher_shipping_id"
- attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}" />
- <field name="applied_voucher_shipping_id" invisible="1" />
+ attrs="{'readonly': ['|', ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"/>
+ <field name="applied_voucher_shipping_id" invisible="1"/>
<button name="action_apply_voucher_shipping" type="object" string="Apply"
- confirm="Anda yakin untuk menggunakan voucher?"
- help="Apply the selected voucher" class="btn-link mb-1 px-0"
- icon="fa-plus"
- attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"
+ confirm="Anda yakin untuk menggunakan voucher?"
+ help="Apply the selected voucher" class="btn-link mb-1 px-0"
+ icon="fa-plus"
+ attrs="{'invisible': ['|', '|', ('voucher_id', '=', False), ('state', 'not in', ['draft', 'sent']), ('applied_voucher_shipping_id', '!=', False)]}"
/>
<button name="cancel_voucher_shipping" type="object" string="Cancel"
- confirm="Anda yakin untuk membatalkan penggunaan voucher?"
- help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
- attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
+ confirm="Anda yakin untuk membatalkan penggunaan voucher?"
+ help="Cancel applied voucher" class="btn-link mb-1 px-0" icon="fa-times"
+ attrs="{'invisible': ['|', ('applied_voucher_shipping_id', '=', False), ('state', 'not in', ['draft','sent'])]}"
/>
</div>
<button name="calculate_selling_price"
- string="Calculate Selling Price"
- type="object"
+ string="Calculate Selling Price"
+ type="object"
/>
</field>
+ <field name="approval_status" position="after">
+ <field name="notes"/>
+ </field>
<field name="source_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
@@ -100,128 +103,128 @@
<field name="compute_fullfillment" invisible="1" />
</field>
<field name="tag_ids" position="after">
- <field name="eta_date_start" />
- <t t-esc="' to '" />
- <field name="eta_date" readonly="1" />
- <field name="expected_ready_to_ship" />
- <field name="flash_sale" />
- <field name="margin_after_delivery_purchase" />
- <field name="percent_margin_after_delivery_purchase" />
- <field name="total_weight" />
- <field name="pareto_status" />
+ <field name="eta_date_start"/>
+ <t t-esc="' to '"/>
+ <field name="eta_date" readonly="1"/>
+ <field name="expected_ready_to_ship"/>
+ <field name="flash_sale"/>
+ <field name="margin_after_delivery_purchase"/>
+ <field name="percent_margin_after_delivery_purchase"/>
+ <field name="total_weight"/>
+ <field name="pareto_status"/>
</field>
<field name="analytic_account_id" position="after">
- <field name="customer_type" readonly="1" />
- <field name="npwp" placeholder='99.999.999.9-999.999' readonly="1" />
- <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" readonly="1" />
- <field name="email" required="1" />
- <field name="unreserve_id" />
- <field name="due_id" readonly="1" />
- <field name="vendor_approval_id" readonly="1" widget="many2many_tags" />
- <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1" />
+ <field name="customer_type" readonly="1"/>
+ <field name="npwp" placeholder='99.999.999.9-999.999' readonly="1"/>
+ <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" readonly="1"/>
+ <field name="email" required="1"/>
+ <field name="unreserve_id"/>
+ <field name="due_id" readonly="1"/>
+ <field name="vendor_approval_id" readonly="1" widget="many2many_tags"/>
+ <field name="source_id" domain="[('id', 'in', [32, 59, 60, 61])]" required="1"/>
<button name="override_allow_create_invoice"
- string="Override Create Invoice"
- type="object"
+ string="Override Create Invoice"
+ type="object"
/>
- <button string="Estimate Shipping" type="object" name="action_estimate_shipping" />
+ <button string="Estimate Shipping" type="object" name="action_estimate_shipping"/>
</field>
<field name="partner_shipping_id" position="after">
- <field name="real_shipping_id" />
- <field name="real_invoice_id" />
- <field name="approval_status" />
+ <field name="real_shipping_id"/>
+ <field name="real_invoice_id"/>
+ <field name="approval_status"/>
<field name="sales_tax_id"
- domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1" />
- <field name="carrier_id" required="1" />
- <field name="delivery_service_type" readonly="1" />
- <field name="shipping_option_id" />
+ domain="[('type_tax_use','=','sale'), ('active', '=', True)]" required="1"/>
+ <field name="carrier_id" required="1"/>
+ <field name="delivery_service_type" readonly="1"/>
+ <field name="shipping_option_id"/>
</field>
<field name="medium_id" position="after">
- <field name="date_doc_kirim" readonly="1" />
- <field name="notification" readonly="1" />
+ <field name="date_doc_kirim" readonly="1"/>
+ <field name="notification" readonly="1"/>
</field>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']"
- position="attributes">
+ position="attributes">
<attribute name="attrs">
- {'readonly': [('state', 'in', ('done','cancel'))]}
+ {'readonly': [('state', 'in', ('done', 'cancel'))]}
</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree"
- position="inside">
- <field name="desc_updatable" invisible="1" />
+ position="inside">
+ <field name="desc_updatable" invisible="1"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='name']"
+ position="attributes">
<attribute name="modifiers">
{'readonly': [('desc_updatable', '=', False)]}
</attribute>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']"
+ position="attributes">
<attribute name="attrs">
{
- 'readonly': [
- '|',
- ('qty_invoiced', '>', 0),
- ('parent.approval_status', '!=', False)
- ]
+ 'readonly': [
+ '|',
+ ('qty_invoiced', '>', 0),
+ ('parent.approval_status', '!=', False)
+ ]
}
</attribute>
</xpath>
<div name="invoice_lines" position="before">
<div name="vendor_id" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="vendor_id" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="vendor_id"/>
<div name="vendor_id">
<field name="vendor_id"
- attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
- domain="[('parent_id', '=', False)]"
- options="{'no_create': True}" class="oe_inline" />
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')]}"
+ domain="[('parent_id', '=', False)]"
+ options="{'no_create': True}" class="oe_inline"/>
</div>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="purchase_price" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_price" />
- <field name="purchase_price" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_price"/>
+ <field name="purchase_price"/>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="purchase_tax_id" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="purchase_tax_id" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="purchase_tax_id"/>
<div name="purchase_tax_id">
- <field name="purchase_tax_id" />
+ <field name="purchase_tax_id"/>
</div>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="item_percent_margin" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="item_percent_margin" />
- <field name="item_percent_margin" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="item_percent_margin"/>
+ <field name="item_percent_margin"/>
</div>
</div>
<div name="invoice_lines" position="before">
<div name="price_subtotal" groups="base.group_no_one"
- attrs="{'invisible': [('display_type', '!=', False)]}">
- <label for="price_subtotal" />
- <field name="price_subtotal" />
+ attrs="{'invisible': [('display_type', '!=', False)]}">
+ <label for="price_subtotal"/>
+ <field name="price_subtotal"/>
</div>
</div>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']"
- position="after">
- <field name="qty_free_bu" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_total']"
+ position="after">
+ <field name="qty_free_bu" optional="hide"/>
<field name="vendor_id"
- attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}"
- domain="[('parent_id', '=', False)]" options="{'no_create':True}" />
- <field name="vendor_md_id" optional="hide" />
+ attrs="{'readonly': [('parent.approval_status', '=', 'approved')], 'invisible': [('display_type', '!=', False)]}"
+ domain="[('parent_id', '=', False)]" options="{'no_create':True}"/>
+ <field name="vendor_md_id" optional="hide"/>
<field name="purchase_price"
- attrs="
+ attrs="
{
'readonly': [
'|',
@@ -229,35 +232,35 @@
('parent.approval_status', '!=', False)
]
}
- " />
- <field name="purchase_price_md" optional="hide" />
+ "/>
+ <field name="purchase_price_md" optional="hide"/>
<field name="purchase_tax_id"
- attrs="{'readonly': [('parent.approval_status', '!=', False)]}"
- domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}" />
- <field name="item_percent_margin" />
- <field name="item_margin" optional="hide" />
- <field name="margin_md" optional="hide" />
- <field name="note" optional="hide" />
- <field name="note_procurement" optional="hide" />
- <field name="vendor_subtotal" optional="hide" />
- <field name="weight" optional="hide" />
- <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide" />
- <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide" />
+ attrs="{'readonly': [('parent.approval_status', '!=', False)]}"
+ domain="[('type_tax_use','=','purchase')]" options="{'no_create':True}"/>
+ <field name="item_percent_margin"/>
+ <field name="item_margin" optional="hide"/>
+ <field name="margin_md" optional="hide"/>
+ <field name="note" optional="hide"/>
+ <field name="note_procurement" optional="hide"/>
+ <field name="vendor_subtotal" optional="hide"/>
+ <field name="weight" optional="hide"/>
+ <field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
+ <field name="order_promotion_id" string="Promotion" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
- position="before">
- <field name="line_no" readonly="1" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="before">
+ <field name="line_no" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']"
- position="before">
- <field name="qty_reserved" invisible="1" />
- <field name="reserved_from" readonly="1" optional="hide" />
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='qty_delivered']"
+ position="before">
+ <field name="qty_reserved" invisible="1"/>
+ <field name="reserved_from" readonly="1" optional="hide"/>
</xpath>
<xpath
- expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
- position="attributes">
+ expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_id']"
+ position="attributes">
<attribute name="options">{'no_create': True}</attribute>
</xpath>
<!-- <xpath
@@ -266,42 +269,42 @@
<attribute name="required">1</attribute>
</xpath> -->
<field name="amount_total" position="after">
- <field name="grand_total" />
- <label for="amount_voucher_disc" string="Voucher" />
+ <field name="grand_total"/>
+ <label for="amount_voucher_disc" string="Voucher"/>
<div>
- <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1" />
+ <field class="mb-0" name="amount_voucher_disc" string="Voucher" readonly="1"/>
<div class="text-right mb-2">
<small>*Hanya informasi</small>
</div>
</div>
- <label for="amount_voucher_shipping_disc" string="Voucher Shipping" />
+ <label for="amount_voucher_shipping_disc" string="Voucher Shipping"/>
<div>
<field class="mb-0" name="amount_voucher_shipping_disc"
- string="Voucher Shipping" readonly="1" />
+ string="Voucher Shipping" readonly="1"/>
<div class="text-right mb-2">
<small>*Hanya informasi</small>
</div>
</div>
- <field name="total_margin" />
- <field name="total_percent_margin" />
- <field name="total_before_margin" />
+ <field name="total_margin"/>
+ <field name="total_percent_margin"/>
+ <field name="total_before_margin"/>
</field>
<field name="effective_date" position="after">
- <field name="carrier_id" />
- <field name="estimated_arrival_days" />
- <field name="picking_iu_id" />
- <field name="note_ekspedisi" />
+ <field name="carrier_id"/>
+ <field name="estimated_arrival_days"/>
+ <field name="picking_iu_id"/>
+ <field name="note_ekspedisi"/>
</field>
<field name="carrier_id" position="attributes">
<attribute name="attrs">
{'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
- ['cancel','draft'])]}
+ ['cancel', 'draft'])]}
</attribute>
</field>
<field name="payment_term_id" position="attributes">
<attribute name="attrs">
{'readonly': [('approval_status', '=', 'approved'), ('state', 'not in',
- ['cancel','draft'])]}
+ ['cancel', 'draft'])]}
</attribute>
</field>
@@ -309,55 +312,55 @@
<page string="Website" name="customer_purchase_order">
<group>
<group>
- <field name="partner_purchase_order_name" readonly="True" />
- <field name="partner_purchase_order_description" readonly="True" />
- <field name="partner_purchase_order_file" readonly="True" />
- <field name="note_website" readonly="True" />
- <field name="web_approval" readonly="True" />
+ <field name="partner_purchase_order_name" readonly="True"/>
+ <field name="partner_purchase_order_description" readonly="True"/>
+ <field name="partner_purchase_order_file" readonly="True"/>
+ <field name="note_website" readonly="True"/>
+ <field name="web_approval" readonly="True"/>
</group>
<group>
<button name="generate_payment_link_midtrans_sales_order"
- string="Create Payment Link"
- type="object"
+ string="Create Payment Link"
+ type="object"
/>
- <field name="payment_link_midtrans" readonly="True" widget="url" />
- <field name="gross_amount" readonly="True" />
- <field name="payment_type" readonly="True" />
- <field name="payment_status" readonly="True" />
- <field name="payment_qr_code" widget="image" readonly="True" />
+ <field name="payment_link_midtrans" readonly="True" widget="url"/>
+ <field name="gross_amount" readonly="True"/>
+ <field name="payment_type" readonly="True"/>
+ <field name="payment_status" readonly="True"/>
+ <field name="payment_qr_code" widget="image" readonly="True"/>
</group>
</group>
</page>
<page string="Promotion" name="page_promotion">
<field name="order_promotion_ids" readonly="1">
<tree options="{'no_open': True}">
- <field name="program_line_id" />
- <field name="quantity" />
- <field name="is_applied" />
+ <field name="program_line_id"/>
+ <field name="quantity"/>
+ <field name="is_applied"/>
</tree>
<form>
<group>
- <field name="program_line_id" />
- <field name="quantity" />
- <field name="is_applied" />
+ <field name="program_line_id"/>
+ <field name="quantity"/>
+ <field name="is_applied"/>
</group>
</form>
</field>
</page>
<page string="Matches PO" name="page_matches_po" invisible="1">
- <field name="order_sales_match_line" readonly="1" />
+ <field name="order_sales_match_line" readonly="1"/>
</page>
<!-- <page string="Fullfillment" name="page_sale_order_fullfillment">
<field name="fullfillment_line" readonly="1"/>
</page> -->
<page string="Fulfillment v2" name="page_sale_order_fullfillment2">
- <field name="fulfillment_line_v2" readonly="1" />
+ <field name="fulfillment_line_v2" readonly="1"/>
</page>
<page string="Reject Line" name="page_sale_order_reject_line">
- <field name="reject_line" readonly="0" />
+ <field name="reject_line" readonly="0"/>
</page>
<page string="Koli" name="page_sales_order_koli_line">
- <field name="koli_lines" readonly="1" />
+ <field name="koli_lines" readonly="1"/>
</page>
</page>
</field>
@@ -369,15 +372,15 @@
<field name="arch" type="xml">
<form string="Cancel Reason">
<group>
- <field name="reason_cancel" widget="selection" />
- <field name="attachment_bukti" widget="many2many_binary" required="1" />
+ <field name="reason_cancel" widget="selection"/>
+ <field name="attachment_bukti" widget="many2many_binary" required="1"/>
<field name="nomor_so_pengganti"
- attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}" />
+ attrs="{'invisible': [('reason_cancel', '!=', 'ganti_quotation')]}"/>
</group>
<footer>
<button string="Confirm" type="object" name="confirm_reject"
- class="btn-primary" />
- <button string="Cancel" class="btn-secondary" special="cancel" />
+ class="btn-primary"/>
+ <button string="Cancel" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
@@ -394,43 +397,44 @@
<record id="sale_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding" />
+ <field name="inherit_id" ref="sale.view_quotation_tree_with_onboarding"/>
<field name="arch" type="xml">
<field name="state" position="after">
- <field name="approval_status" />
- <field name="client_order_ref" />
- <field name="payment_type" optional="hide" />
- <field name="payment_status" optional="hide" />
- <field name="pareto_status" optional="hide" />
- <field name="shipping_method_picking" optional="hide" />
- <field name="hold_outgoing" optional="hide" />
+ <field name="approval_status"/>
+ <field name="client_order_ref"/>
+ <field name="notes"/>
+ <field name="payment_type" optional="hide"/>
+ <field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
+ <field name="shipping_method_picking" optional="hide"/>
+ <field name="hold_outgoing" optional="hide"/>
</field>
</field>
</record>
<record id="sales_order_tree_view_inherit" model="ir.ui.view">
<field name="name">Sale Order</field>
<field name="model">sale.order</field>
- <field name="inherit_id" ref="sale.view_order_tree" />
+ <field name="inherit_id" ref="sale.view_order_tree"/>
<field name="arch" type="xml">
<field name="state" position="after">
- <field name="approval_status" />
- <field name="client_order_ref" />
- <field name="so_status" />
- <field name="date_status_done" />
- <field name="date_kirim_ril" />
- <field name="date_driver_departure" />
- <field name="date_driver_arrival" />
- <field name="payment_type" optional="hide" />
- <field name="payment_status" optional="hide" />
- <field name="pareto_status" optional="hide" />
+ <field name="approval_status"/>
+ <field name="client_order_ref"/>
+ <field name="so_status"/>
+ <field name="date_status_done"/>
+ <field name="date_kirim_ril"/>
+ <field name="date_driver_departure"/>
+ <field name="date_driver_arrival"/>
+ <field name="payment_type" optional="hide"/>
+ <field name="payment_status" optional="hide"/>
+ <field name="pareto_status" optional="hide"/>
</field>
</field>
</record>
<record id="sale_order_multi_update_ir_actions_server" model="ir.actions.server">
<field name="name">Mark As Cancel</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_view_types">form,list</field>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_state()</field>
@@ -438,32 +442,32 @@
<record id="sale_order_update_multi_actions_server" model="ir.actions.server">
<field name="name">Mark As Completed</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_update_status()</field>
</record>
<record id="mail_template_sale_order_web_approve_notification" model="mail.template">
<field name="name">Sale Order: Web Approve Notification</field>
- <field name="model_id" ref="indoteknik_custom.model_sale_order" />
+ <field name="model_id" ref="indoteknik_custom.model_sale_order"/>
<field name="subject">Permintaan Persetujuan Pesanan ${object.name} di Indoteknik.com</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.partner_id.email | safe}</field>
<field name="email_cc">${object.partner_id.get_approve_partner_ids("email_comma_sep")}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
- style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ style="padding: 16px 0; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
- style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<tbody>
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td style="padding-bottom: 24px;">
Dear
@@ -480,8 +484,8 @@
<tr>
<td style="padding-bottom: 16px;">
<a
- href="https://indoteknik.com/my/quotations/${object.id}"
- style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
+ href="https://indoteknik.com/my/quotations/${object.id}"
+ style="color: white; background-color: #C53030; border: none; border-radius: 6px; padding: 4px 8px; width: fit-content; display: block;">
Lihat Pesanan
</a>
</td>
@@ -524,11 +528,11 @@
<field name="model">sales.order.purchase.match</field>
<field name="arch" type="xml">
<tree editable="top" create="false" delete="false">
- <field name="purchase_order_id" readonly="1" />
- <field name="purchase_line_id" readonly="1" />
- <field name="product_id" readonly="1" />
- <field name="qty_so" readonly="1" />
- <field name="qty_po" readonly="1" />
+ <field name="purchase_order_id" readonly="1"/>
+ <field name="purchase_line_id" readonly="1"/>
+ <field name="product_id" readonly="1"/>
+ <field name="qty_so" readonly="1"/>
+ <field name="qty_po" readonly="1"/>
</tree>
</field>
</record>
@@ -540,9 +544,9 @@
<field name="model">sales.order.koli</field>
<field name="arch" type="xml">
<tree editable="top" create="false" delete="false">
- <field name="koli_id" readonly="1" />
- <field name="picking_id" readonly="1" />
- <field name="state" readonly="1" />
+ <field name="koli_id" readonly="1"/>
+ <field name="picking_id" readonly="1"/>
+ <field name="state" readonly="1"/>
</tree>
</field>
</record>
@@ -556,14 +560,14 @@
<field name="model">sales.order.fulfillment.v2</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="so_qty" readonly="1" optional="show" />
- <field name="reserved_stock_qty" readonly="1" optional="show" />
- <field name="delivered_qty" readonly="1" optional="hide" />
- <field name="po_ids" widget="many2many_tags" readonly="1" optional="show" />
- <field name="po_qty" readonly="1" optional="show" />
- <field name="received_qty" readonly="1" optional="show" />
- <field name="purchaser" readonly="1" optional="hide" />
+ <field name="product_id" readonly="1"/>
+ <field name="so_qty" readonly="1" optional="show"/>
+ <field name="reserved_stock_qty" readonly="1" optional="show"/>
+ <field name="delivered_qty" readonly="1" optional="hide"/>
+ <field name="po_ids" widget="many2many_tags" readonly="1" optional="show"/>
+ <field name="po_qty" readonly="1" optional="show"/>
+ <field name="received_qty" readonly="1" optional="show"/>
+ <field name="purchaser" readonly="1" optional="hide"/>
</tree>
</field>
</record>
@@ -573,10 +577,10 @@
<field name="model">sales.order.fullfillment</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="reserved_from" readonly="1" />
- <field name="qty_fullfillment" readonly="1" />
- <field name="user_id" readonly="1" />
+ <field name="product_id" readonly="1"/>
+ <field name="reserved_from" readonly="1"/>
+ <field name="qty_fullfillment" readonly="1"/>
+ <field name="user_id" readonly="1"/>
</tree>
</field>
</record>
@@ -588,9 +592,9 @@
<field name="model">sales.order.reject</field>
<field name="arch" type="xml">
<tree editable="top" create="false">
- <field name="product_id" readonly="1" />
- <field name="qty_reject" readonly="1" />
- <field name="reason_reject" readonly="0" />
+ <field name="product_id" readonly="1"/>
+ <field name="qty_reject" readonly="1"/>
+ <field name="reason_reject" readonly="0"/>
</tree>
</field>
</record>
@@ -599,8 +603,8 @@
<data>
<record id="sale_order_multi_create_uangmuka_ir_actions_server" model="ir.actions.server">
<field name="name">Uang Muka</field>
- <field name="model_id" ref="sale.model_sale_order" />
- <field name="binding_model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
+ <field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="state">code</field>
<field name="code">action = records.open_form_multi_create_uang_muka()</field>
</record>
@@ -609,24 +613,24 @@
<data>
<record id="mail_template_sale_order_notification_to_salesperson" model="mail.template">
<field name="name">Sale Order: Notification to Salesperson</field>
- <field name="model_id" ref="sale.model_sale_order" />
+ <field name="model_id" ref="sale.model_sale_order"/>
<field name="subject">Konsolidasi Pengiriman</field>
<field name="email_from">sales@indoteknik.com</field>
<field name="email_to">${object.user_id.login | safe}</field>
<field name="body_html" type="html">
<table border="0" cellpadding="0" cellspacing="0"
- style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
+ style="padding-top: 16px; background-color: #F1F1F1; font-family:Inter, Helvetica, Verdana, Arial,sans-serif; line-height: 24px; color: #454748; width: 100%; border-collapse:separate;">
<tr>
<td align="center">
<table border="0" cellpadding="0" cellspacing="0" width="590"
- style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
+ style="font-size: 13px; padding: 16px; background-color: white; color: #454748; border-collapse:separate;">
<!-- HEADER -->
<tbody>
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td valign="middle">
<span></span>
@@ -639,8 +643,8 @@
<tr>
<td align="center" style="min-width: 590px;">
<table border="0" cellpadding="0" cellspacing="0"
- width="590"
- style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
+ width="590"
+ style="min-width: 590px; background-color: white; padding: 0px 8px 0px 8px; border-collapse:separate;">
<tr>
<td style="padding-bottom: 24px;">Dear
${salesperson_name},</td>
@@ -657,7 +661,7 @@
<tr>
<td>
<table border="1" cellpadding="5"
- cellspacing="0">
+ cellspacing="0">
<thead>
<tr>
<th>Nama Pesanan</th>
@@ -676,7 +680,7 @@
<tr>
<td style="text-align:center;">
<hr width="100%"
- style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;" />
+ style="background-color:rgb(204,204,204);border:medium none;clear:both;display:block;font-size:0px;min-height:1px;line-height:0; margin: 16px 0px 16px 0px;"/>
</td>
</tr>
</table>
diff --git a/indoteknik_custom/views/shipment_group.xml b/indoteknik_custom/views/shipment_group.xml
index d2c661ba..a4f82e27 100644
--- a/indoteknik_custom/views/shipment_group.xml
+++ b/indoteknik_custom/views/shipment_group.xml
@@ -18,8 +18,9 @@
<field name="model">shipment.group.line</field>
<field name="arch" type="xml">
<tree editable="bottom">
- <field name="sale_id" readonly="1"/>
+ <field name="partner_id" readonly="1"/>
<field name="picking_id" required="1"/>
+ <field name="sale_id" readonly="1"/>
<field name="total_colly" readonly="1"/>
</tree>
</field>