diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-05-16 11:29:04 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-05-16 11:29:04 +0700 |
| commit | 05305c6bf12da58cfadac7f1a901ef825d09cb61 (patch) | |
| tree | 7f6f28397b4ad871131f7f28ea698117bcf1fe0f | |
| parent | c61cb9fcd3d01b4ffafaa6446f9fd68b09a88ff7 (diff) | |
| parent | 4360e1fd9f3af2c18b19463773047d9939716069 (diff) | |
(andri) resolved confict di SO view
| -rw-r--r-- | indoteknik_custom/models/account_move_due_extension.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/approval_invoice_date.py | 2 | ||||
| -rw-r--r-- | indoteknik_custom/models/barcoding_product.py | 34 | ||||
| -rw-r--r-- | indoteknik_custom/models/mrp_production.py | 4 | ||||
| -rw-r--r-- | indoteknik_custom/models/product_pricelist.py | 6 | ||||
| -rwxr-xr-x | indoteknik_custom/models/product_template.py | 631 | ||||
| -rwxr-xr-x | indoteknik_custom/models/purchase_order.py | 125 | ||||
| -rwxr-xr-x | indoteknik_custom/models/purchase_pricelist.py | 80 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 510 | ||||
| -rw-r--r-- | indoteknik_custom/models/shipment_group.py | 9 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 4 | ||||
| -rw-r--r-- | indoteknik_custom/views/barcoding_product.xml | 5 | ||||
| -rwxr-xr-x | indoteknik_custom/views/purchase_pricelist.xml | 7 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 492 | ||||
| -rw-r--r-- | indoteknik_custom/views/shipment_group.xml | 3 |
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', '>', '2023-06-15'), ('create_date', '=', False)]}" /> + attrs="{'required': ['|', ('create_date', '>', '2023-06-15'), ('create_date', '=', False)]}"/> <field name="shipping_paid_by" - attrs="{'required': ['|', ('create_date', '>', '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', '>', '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> |
