From 5f5753dbec8af518dd34821820462cd6340a2a08 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 9 Jun 2025 17:35:22 +0700 Subject: wati leads --- indoteknik_api/controllers/api_v1/wati.py | 171 +++++++++++++++++++++++++++++- indoteknik_custom/models/wati.py | 166 ++++++++++++++++++++++++++++- 2 files changed, 332 insertions(+), 5 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/wati.py b/indoteknik_api/controllers/api_v1/wati.py index 68ff1640..d60cd160 100644 --- a/indoteknik_api/controllers/api_v1/wati.py +++ b/indoteknik_api/controllers/api_v1/wati.py @@ -2,19 +2,182 @@ from .. import controller from odoo import http from odoo.http import request import json +import logging +_logger = logging.getLogger(__name__) -class Wati(controller.Controller): + +# class Wati(controller.Controller): +# prefix = '/api/v1/' +# +# @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', methods=['POST', 'OPTIONS']) +# def notification(self, **kw): +# json_raw = json.loads(request.httprequest.data) +# json_dump = json.dumps(json_raw, indent=4, sort_keys=True) +# +# request.env['wati.notification'].create([{ +# 'json_raw': json_dump, +# 'is_lead': False +# }]) +# +# return + +# REPLACE webhook controller yang sudah ada dengan ini: + +class Wati(http.Controller): prefix = '/api/v1/' - @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', methods=['POST', 'OPTIONS']) + @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', + methods=['POST', 'OPTIONS']) def notification(self, **kw): json_raw = json.loads(request.httprequest.data) json_dump = json.dumps(json_raw, indent=4, sort_keys=True) - request.env['wati.notification'].create([{ + # Create notification record (existing) + notification = request.env['wati.notification'].create([{ 'json_raw': json_dump, 'is_lead': False }]) - return + # NEW: Immediate tags check + phone = json_raw.get('waId') or json_raw.get('from') + + if phone: + lead_id = self._check_tags_immediate(phone, notification[0]) + + if lead_id: + notification.write({'is_lead': True, 'lead_id': lead_id}) + _logger.info('🚀 Lead created immediately via webhook: %s' % lead_id) + + return {'status': 'success'} + + def _check_tags_immediate(self, phone, notification): + """Check tags untuk specific phone - immediate""" + try: + _logger.info('📱 Immediate tags check for: %s' % phone) + + # Get contact dari WATI untuk phone ini + wati_api = request.env['wati.api'] + + params = { + 'pageSize': 1, + 'pageNumber': 1, + 'attribute': json.dumps([ + {'name': "phone", 'operator': "contain", 'value': phone} + ]), + } + + result = wati_api.http_get('/api/v1/getContacts', params) + + if isinstance(result, dict) and result.get('result') == 'success': + contact_list = result.get('contact_list', []) + + if contact_list: + contact = contact_list[0] + + # Check if has tags=leads + if self._contact_has_tags_leads(contact): + # Check existing lead + existing_lead = request.env['crm.lead'].search([('phone', '=', phone)], limit=1) + + if existing_lead: + _logger.info('✅ Lead already exists for %s' % phone) + return existing_lead.id + + # Create new lead + lead_id = self._create_lead_from_webhook(contact) + if lead_id: + _logger.info('🎯 Created new lead %s from webhook tags' % lead_id) + return lead_id + + return None + + except Exception as e: + _logger.error('❌ Error in immediate tags check: %s' % str(e)) + return None + + def _contact_has_tags_leads(self, contact): + """Check if contact has tags=leads""" + custom_params = contact.get('customParams', []) + + for param in custom_params: + if (param.get('name', '').lower() == 'tags' and + param.get('value', '').lower() == 'leads'): + return True + + return False + + def _create_lead_from_webhook(self, contact): + """Create lead dari webhook data""" + try: + phone = contact.get('phone', '') + + # Extract data dari customParams + custom_params = contact.get('customParams', []) + contact_data = {} + + for param in custom_params: + param_name = param.get('name', '').lower() + param_value = param.get('value', '').strip() + + if param_name == 'perusahaan': + contact_data['perusahaan'] = param_value + elif param_name == 'name': + contact_data['name'] = param_value + elif param_name == 'email': + contact_data['email'] = param_value + elif param_name == 'sales': + contact_data['sales'] = param_value + elif param_name == 'notes': + contact_data['notes'] = param_value + + # Generate lead name + company_name = contact_data.get('perusahaan', '') + contact_name = contact_data.get('name', '') or contact.get('name', '') + + if company_name: + lead_name = 'WATI Lead (Webhook) - %s' % company_name + elif contact_name: + lead_name = 'WATI Lead (Webhook) - %s' % contact_name + else: + lead_name = 'WATI Lead (Webhook) - %s' % phone + + # Get salesperson + sales_name = contact_data.get('sales', '') + user_id = 2 # Default + + if sales_name: + user = request.env['res.users'].search([('name', 'ilike', sales_name)], limit=1) + if user: + user_id = user.id + + # Create lead + lead_vals = { + 'name': lead_name, + 'phone': phone, + 'contact_name': contact_name, + 'partner_name': company_name, + 'email_from': contact_data.get('email', ''), + 'description': contact_data.get('notes', 'Lead created from WATI webhook (real-time)'), + 'type': 'lead', + 'user_id': user_id, + } + + new_lead = request.env['crm.lead'].create(lead_vals) + + # Create activity untuk follow up + request.env['mail.activity'].create({ + 'activity_type_id': request.env.ref('mail.mail_activity_data_todo').id, + 'summary': '🚨 URGENT: New WATI Lead (Real-time)', + 'note': 'Lead created instantly from WATI webhook when tags=leads was added. Immediate follow up required!', + 'res_id': new_lead.id, + 'res_model_id': request.env.ref('crm.model_crm_lead').id, + 'user_id': user_id, + 'date_deadline': fields.Date.today(), + }) + + return new_lead.id + + except Exception as e: + _logger.error('❌ Error creating lead from webhook: %s' % str(e)) + return None \ No newline at end of file diff --git a/indoteknik_custom/models/wati.py b/indoteknik_custom/models/wati.py index a0619f83..18517502 100644 --- a/indoteknik_custom/models/wati.py +++ b/indoteknik_custom/models/wati.py @@ -201,6 +201,170 @@ class WatiNotification(models.Model): wati.is_lead = True wati.lead_id = current_lead.id + # FINAL CODE - Sesuai dengan mapping table Anda + + def check_wati_tags_leads(self): + """Check tags 'leads' di WATI dan create leads di Odoo - Final Version""" + _logger.info('=== Starting WATI Tags Check (Final) ===') + + wati_api = self.env['wati.api'] + total_leads_created = 0 + + try: + # Get WATI contacts + wati_contacts = wati_api.http_get('/api/v1/getContacts', {'pageSize': 100, 'pageNumber': 1}) + + if isinstance(wati_contacts, dict) and wati_contacts.get('result') == 'success': + contact_list = wati_contacts.get('contact_list', []) + + for contact in contact_list: + if self._create_lead_if_tagged(contact): + total_leads_created += 1 + + _logger.info('WATI check completed: %s leads created' % total_leads_created) + return {'leads_created': total_leads_created} + + except Exception as e: + _logger.error('Error in WATI tags check: %s' % str(e)) + return {'leads_created': 0, 'error': str(e)} + + def _create_lead_if_tagged(self, contact): + """Create lead jika contact punya tags=leads - Sesuai Mapping Table""" + try: + # Check tags leads + if not self._has_tags_leads(contact): + return False + + phone = contact.get('phone', '') + if not phone: + return False + + # Check existing lead by phone + existing_lead = self.env['crm.lead'].search([('phone', '=', phone)], limit=1) + if existing_lead: + _logger.info('Lead already exists for phone %s' % phone) + return False + + # Extract data dari customParams sesuai mapping table + custom_params = contact.get('customParams', []) + contact_data = self._extract_contact_data(custom_params) + + # Create lead dengan field mapping yang sesuai + lead_vals = { + 'name': self._generate_lead_name(contact_data, contact), + 'phone': phone, # Phone Number → Mobile + 'contact_name': contact_data.get('name', ''), # Name → Contact Name + 'partner_name': contact_data.get('perusahaan', ''), # Perusahaan → Company Name + 'email_from': contact_data.get('email', ''), # Email → Email + 'description': contact_data.get('notes', ''), # Notes → Internal Notes + 'type': 'lead', + 'user_id': self._get_salesperson_id(contact_data.get('sales', '')), # Sales → Salesperson + } + + new_lead = self.env['crm.lead'].create(lead_vals) + _logger.info('Created WATI lead %s for %s (%s)' % (new_lead.id, contact_data.get('name', 'Unknown'), phone)) + return True + + except Exception as e: + _logger.error('Error creating lead: %s' % str(e)) + return False + + def _extract_contact_data(self, custom_params): + """Extract data dari customParams sesuai mapping table""" + contact_data = {} + + for param in custom_params: + param_name = param.get('name', '').lower() + param_value = param.get('value', '').strip() + + # Mapping sesuai table: + if param_name == 'perusahaan': # Perusahaan → Company Name + contact_data['perusahaan'] = param_value + elif param_name == 'name': # Name → Contact Name + contact_data['name'] = param_value + elif param_name == 'email': # Email → Email + contact_data['email'] = param_value + elif param_name == 'sales': # Sales → Salesperson + contact_data['sales'] = param_value + elif param_name == 'notes': # Notes → Internal Notes + contact_data['notes'] = param_value + # Phone Number sudah diambil dari contact.phone + + return contact_data + + def _generate_lead_name(self, contact_data, contact): + """Generate lead name sesuai mapping: Judul Leads berdasarkan company/contact""" + company_name = contact_data.get('perusahaan', '') + contact_name = contact_data.get('name', '') or contact.get('name', '') + + if company_name: + return 'WATI Lead - %s' % company_name + elif contact_name: + return 'WATI Lead - %s' % contact_name + else: + return 'WATI Lead - %s' % contact.get('phone', 'Unknown') + + def _get_salesperson_id(self, sales_name): + """Get salesperson ID dari nama - Sales → Salesperson""" + if not sales_name: + return 2 # Default Sales (ID 2) + + # Try find user by name + user = self.env['res.users'].search([ + ('name', 'ilike', sales_name) + ], limit=1) + + if user: + return user.id + else: + # Fallback ke default Sales + return 2 + + def _has_tags_leads(self, contact): + """Check apakah ada tags untuk tajik ke odoo => Leads""" + custom_params = contact.get('customParams', []) + + for param in custom_params: + param_name = param.get('name', '').lower() + param_value = param.get('value', '').lower() + + # Check: "Judul Tags untuk tajik ke odoo => Leads" + if param_name == 'tags' and param_value == 'leads': + return True + + return False + + def manual_check_tags(self): + """Manual trigger untuk testing""" + result = self.check_wati_tags_leads() + + message = 'WATI Tags Check Completed!\n\n' + message += 'Leads Created: %s\n\n' % result.get('leads_created', 0) + message += 'Field Mapping:\n' + message += '• Perusahaan → Company Name\n' + message += '• Name → Contact Name\n' + message += '• Email → Email\n' + message += '• Sales → Salesperson\n' + message += '• Phone Number → Mobile\n' + message += '• Notes → Internal Notes\n' + message += '• Tags=leads → Trigger Lead Creation' + + if result.get('error'): + message += '\n\nError: %s' % result['error'] + message_type = 'warning' + else: + message_type = 'success' + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'WATI Tags Check', + 'message': message, + 'type': message_type, + 'sticky': True, + } + } class WatiHistory(models.Model): _name = 'wati.history' @@ -319,4 +483,4 @@ class WatiHistoryLine(models.Model): ticket_id = fields.Char(string='Ticket ID') type = fields.Char(string='Type') wa_id = fields.Char(string='WA ID') - date_wati = fields.Datetime(string='Date WATI') + date_wati = fields.Datetime(string='Date WATI') \ No newline at end of file -- cgit v1.2.3 From 8a025fe63ea44b93d3978da7df3aa31533da5300 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 13 Jun 2025 09:13:08 +0700 Subject: add tukar guling model and view --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/tukar_guling.py | 0 indoteknik_custom/views/tukar_guling.xml | 0 3 files changed, 1 insertion(+) create mode 100644 indoteknik_custom/models/tukar_guling.py create mode 100644 indoteknik_custom/views/tukar_guling.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 9fe3dcdb..3a7c9fa0 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -167,6 +167,7 @@ 'views/coretax_faktur.xml', 'views/public_holiday.xml', 'views/stock_inventory.xml', + 'views/tukar_guling.xml' ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py new file mode 100644 index 00000000..e69de29b diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml new file mode 100644 index 00000000..e69de29b -- cgit v1.2.3 From 860abd78b0474279f851378f0b6507fe71fd76be Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 13 Jun 2025 11:39:00 +0700 Subject: PTG document name sequence --- indoteknik_custom/models/__init__.py | 1 + indoteknik_custom/models/tukar_guling.py | 21 +++++++++++ indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/tukar_guling.xml | 50 ++++++++++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py index 08fa9803..72bd7cee 100755 --- a/indoteknik_custom/models/__init__.py +++ b/indoteknik_custom/models/__init__.py @@ -150,3 +150,4 @@ from . import stock_backorder_confirmation from . import account_payment_register from . import stock_inventory from . import approval_invoice_date +from . import tukar_guling diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index e69de29b..ca246c7f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -0,0 +1,21 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError + + +class TukarGuling(models.Model): + _name = 'tukar.guling' + _description = 'Tukar Guling' + _order = 'date desc, id desc' + _rec_name = 'name' + + # Hanya 2 field seperti yang Anda inginkan + name = fields.Char('Reference', required=True, copy=False, readonly=True, default='New') + date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + + # Sequence generator + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + if vals.get('name', 'New') == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or '0' + return super(TukarGuling, self).create(vals_list) \ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 601f04c5..5b60f3ac 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -181,3 +181,4 @@ access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1 access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 +access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,0 diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index e69de29b..34f2fa48 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -0,0 +1,50 @@ + + + + Pengajuan Tukar Guling + ir.actions.act_window + tukar.guling + tree,form + + + + + + Pengajuan Tukar Guling + tukar.guling + PTG/ + 5 + 1 + 1 + + + + + pengajuan.tukar.guling.tree + tukar.guling + + + + + + + + + + pengajuan.tukar.guling.form + tukar.guling + +
+ + + +
+
+
+
\ No newline at end of file -- cgit v1.2.3 From d2c05ac97f195e196605d91088d6f76d9312e528 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 13 Jun 2025 17:19:28 +0700 Subject: match with meet --- indoteknik_custom/models/tukar_guling.py | 141 ++++++++++++++++++++++- indoteknik_custom/security/ir.model.access.csv | 3 +- indoteknik_custom/views/tukar_guling.xml | 149 ++++++++++++++++++------- 3 files changed, 246 insertions(+), 47 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index ca246c7f..e214e268 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -8,14 +8,145 @@ class TukarGuling(models.Model): _order = 'date desc, id desc' _rec_name = 'name' - # Hanya 2 field seperti yang Anda inginkan - name = fields.Char('Reference', required=True, copy=False, readonly=True, default='New') + name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', required=True, + domain=[('picking_type_id.code', '=', 'outgoing')]) + ba_num = fields.Text('Nomor BA') + notes = fields.Text('Notes') + return_type = fields.Selection(String='Return Type', selection=[ + ('tukar_guling', 'Tukar Guling'), + ('revisi_so', 'Revisi SO'), + ('revisi_po', 'Revisi PO'), + ('credit_memo', 'Credit Memo'), + ('debit_memo', 'Debit Memo'), + ('lain_lain', 'Lain-lain')]) + + # ✅ PERBAIKAN: Ganti 'states' dengan 'state' + state = fields.Selection(string='Status', selection=[ + ('draft', 'Draft'), + ('waiting', 'Waiting for Approval'), + ('done', 'Done'), + ('cancel', 'Canceled') + ], default='draft', tracking=True, required=True) + + # ✅ NEW: Line items + line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') + + @api.constrains('line_ids', 'state') + def _check_product_lines(self): + """Constraint: Product lines harus ada jika state bukan draft""" + for record in self: + if record.state in ('waiting', 'done') and not record.line_ids: + raise ValidationError("Product lines harus diisi sebelum submit atau approve!") + + def _validate_product_lines(self): + """Helper method untuk validasi product lines""" + self.ensure_one() + + # Check ada product lines + if not self.line_ids: + raise UserError("Belum ada product lines yang ditambahkan!") + + # Check product sudah diisi + empty_lines = self.line_ids.filtered(lambda line: not line.product_id) + if empty_lines: + raise UserError("Ada product lines yang belum diisi productnya!") + + # Check quantity > 0 + zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0) + if zero_qty_lines: + raise UserError("Quantity product tidak boleh kosong atau 0!") + + return True - # Sequence generator @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', 'New') == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or '0' - return super(TukarGuling, self).create(vals_list) \ No newline at end of file + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG/00001' + return super(TukarGuling, self).create(vals_list) + + def copy(self, default=None): + """Override copy untuk custom behavior saat duplicate""" + if default is None: + default = {} + + # Reset fields penting saat duplicate + default.update({ + 'name': 'New', # Akan auto-generate sequence baru + 'state': 'draft', + 'date': fields.Datetime.now(), + # ba_num dan out_num tidak di-reset, user bisa edit manual + }) + + # Copy record dengan default values + new_record = super(TukarGuling, self).copy(default) + + # Re-sequence line items untuk record baru + if new_record.line_ids: + for i, line in enumerate(new_record.line_ids): + line.sequence = (i + 1) * 10 + + return new_record + + def action_draft(self): + """Reset to draft state""" + for record in self: + if record.state == 'cancel': + record.write({'state': 'draft'}) + else: + raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") + + def action_submit(self): + self.ensure_one() + if self.state != 'draft': + raise UserError("Hanya status Draft saja yang bisa di submit") + self.state = 'waiting' + + def action_approve(self): + self.ensure_one() + if self.state != 'waiting': + raise UserError("Hanya status Waiting saja yang bisa di approve") + self.state = 'done' + + def action_cancel(self): + self.ensure_one() + if self.state == 'done': + raise UserError("Tidak bisa cancel jika sudah done") + self.state = 'cancel' + + +class TukarGulingLine(models.Model): + _name = 'tukar.guling.line' + _description = 'Tukar Guling Line' + _order = 'sequence, id' + + sequence = fields.Integer('Sequence', default=10, copy=False) + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling', required=True, ondelete='cascade') + product_id = fields.Many2one('product.product', string='Product', required=True) + product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0) + product_uom = fields.Many2one('uom.uom', string='Unit of Measure') + name = fields.Text('Description') + + @api.model_create_multi + def create(self, vals_list): + """Override create to auto-assign sequence""" + for vals in vals_list: + if 'sequence' not in vals or vals.get('sequence', 0) <= 0: + # Get max sequence untuk tukar_guling yang sama + tukar_guling_id = vals.get('tukar_guling_id') + if tukar_guling_id: + max_seq = self.search([ + ('tukar_guling_id', '=', tukar_guling_id) + ], order='sequence desc', limit=1) + vals['sequence'] = (max_seq.sequence or 0) + 10 + else: + vals['sequence'] = 10 + return super(TukarGulingLine, self).create(vals_list) + + @api.onchange('product_id') + def _onchange_product_id(self): + if self.product_id: + self.name = self.product_id.display_name + self.product_uom = self.product_id.uom_id \ No newline at end of file diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 5b60f3ac..26198a0f 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -181,4 +181,5 @@ access_shipping_option,shipping.option,model_shipping_option,,1,1,1,1 access_production_purchase_match,access.production.purchase.match,model_production_purchase_match,,1,1,1,1 access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 -access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,0 +access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 +access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_guling_line,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 34f2fa48..8fffcbb0 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -1,50 +1,117 @@ - - Pengajuan Tukar Guling - ir.actions.act_window - tukar.guling - tree,form - + + + + Pengajuan Tukar Guling + ir.actions.act_window + tukar.guling + tree,form + - + + - - Pengajuan Tukar Guling - tukar.guling - PTG/ - 5 - 1 - 1 - - + + + Pengajuan Tukar Guling + tukar.guling + PTG/ + 5 + 1 + 1 + + - - pengajuan.tukar.guling.tree - tukar.guling - - + + + pengajuan.tukar.guling.tree + tukar.guling + + - - - + + + + + + + - - pengajuan.tukar.guling.form - tukar.guling - -
- - - -
-
-
+ + + pengajuan.tukar.guling.form + tukar.guling + +
+
+
+ +
+

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
\ No newline at end of file -- cgit v1.2.3 From 136fc0acd87bceb21b89fc9f040bffc49c93e9f9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 14 Jun 2025 09:10:12 +0700 Subject: restore wati --- indoteknik_custom/models/wati.py | 166 +-------------------------------------- 1 file changed, 1 insertion(+), 165 deletions(-) diff --git a/indoteknik_custom/models/wati.py b/indoteknik_custom/models/wati.py index 18517502..a0619f83 100644 --- a/indoteknik_custom/models/wati.py +++ b/indoteknik_custom/models/wati.py @@ -201,170 +201,6 @@ class WatiNotification(models.Model): wati.is_lead = True wati.lead_id = current_lead.id - # FINAL CODE - Sesuai dengan mapping table Anda - - def check_wati_tags_leads(self): - """Check tags 'leads' di WATI dan create leads di Odoo - Final Version""" - _logger.info('=== Starting WATI Tags Check (Final) ===') - - wati_api = self.env['wati.api'] - total_leads_created = 0 - - try: - # Get WATI contacts - wati_contacts = wati_api.http_get('/api/v1/getContacts', {'pageSize': 100, 'pageNumber': 1}) - - if isinstance(wati_contacts, dict) and wati_contacts.get('result') == 'success': - contact_list = wati_contacts.get('contact_list', []) - - for contact in contact_list: - if self._create_lead_if_tagged(contact): - total_leads_created += 1 - - _logger.info('WATI check completed: %s leads created' % total_leads_created) - return {'leads_created': total_leads_created} - - except Exception as e: - _logger.error('Error in WATI tags check: %s' % str(e)) - return {'leads_created': 0, 'error': str(e)} - - def _create_lead_if_tagged(self, contact): - """Create lead jika contact punya tags=leads - Sesuai Mapping Table""" - try: - # Check tags leads - if not self._has_tags_leads(contact): - return False - - phone = contact.get('phone', '') - if not phone: - return False - - # Check existing lead by phone - existing_lead = self.env['crm.lead'].search([('phone', '=', phone)], limit=1) - if existing_lead: - _logger.info('Lead already exists for phone %s' % phone) - return False - - # Extract data dari customParams sesuai mapping table - custom_params = contact.get('customParams', []) - contact_data = self._extract_contact_data(custom_params) - - # Create lead dengan field mapping yang sesuai - lead_vals = { - 'name': self._generate_lead_name(contact_data, contact), - 'phone': phone, # Phone Number → Mobile - 'contact_name': contact_data.get('name', ''), # Name → Contact Name - 'partner_name': contact_data.get('perusahaan', ''), # Perusahaan → Company Name - 'email_from': contact_data.get('email', ''), # Email → Email - 'description': contact_data.get('notes', ''), # Notes → Internal Notes - 'type': 'lead', - 'user_id': self._get_salesperson_id(contact_data.get('sales', '')), # Sales → Salesperson - } - - new_lead = self.env['crm.lead'].create(lead_vals) - _logger.info('Created WATI lead %s for %s (%s)' % (new_lead.id, contact_data.get('name', 'Unknown'), phone)) - return True - - except Exception as e: - _logger.error('Error creating lead: %s' % str(e)) - return False - - def _extract_contact_data(self, custom_params): - """Extract data dari customParams sesuai mapping table""" - contact_data = {} - - for param in custom_params: - param_name = param.get('name', '').lower() - param_value = param.get('value', '').strip() - - # Mapping sesuai table: - if param_name == 'perusahaan': # Perusahaan → Company Name - contact_data['perusahaan'] = param_value - elif param_name == 'name': # Name → Contact Name - contact_data['name'] = param_value - elif param_name == 'email': # Email → Email - contact_data['email'] = param_value - elif param_name == 'sales': # Sales → Salesperson - contact_data['sales'] = param_value - elif param_name == 'notes': # Notes → Internal Notes - contact_data['notes'] = param_value - # Phone Number sudah diambil dari contact.phone - - return contact_data - - def _generate_lead_name(self, contact_data, contact): - """Generate lead name sesuai mapping: Judul Leads berdasarkan company/contact""" - company_name = contact_data.get('perusahaan', '') - contact_name = contact_data.get('name', '') or contact.get('name', '') - - if company_name: - return 'WATI Lead - %s' % company_name - elif contact_name: - return 'WATI Lead - %s' % contact_name - else: - return 'WATI Lead - %s' % contact.get('phone', 'Unknown') - - def _get_salesperson_id(self, sales_name): - """Get salesperson ID dari nama - Sales → Salesperson""" - if not sales_name: - return 2 # Default Sales (ID 2) - - # Try find user by name - user = self.env['res.users'].search([ - ('name', 'ilike', sales_name) - ], limit=1) - - if user: - return user.id - else: - # Fallback ke default Sales - return 2 - - def _has_tags_leads(self, contact): - """Check apakah ada tags untuk tajik ke odoo => Leads""" - custom_params = contact.get('customParams', []) - - for param in custom_params: - param_name = param.get('name', '').lower() - param_value = param.get('value', '').lower() - - # Check: "Judul Tags untuk tajik ke odoo => Leads" - if param_name == 'tags' and param_value == 'leads': - return True - - return False - - def manual_check_tags(self): - """Manual trigger untuk testing""" - result = self.check_wati_tags_leads() - - message = 'WATI Tags Check Completed!\n\n' - message += 'Leads Created: %s\n\n' % result.get('leads_created', 0) - message += 'Field Mapping:\n' - message += '• Perusahaan → Company Name\n' - message += '• Name → Contact Name\n' - message += '• Email → Email\n' - message += '• Sales → Salesperson\n' - message += '• Phone Number → Mobile\n' - message += '• Notes → Internal Notes\n' - message += '• Tags=leads → Trigger Lead Creation' - - if result.get('error'): - message += '\n\nError: %s' % result['error'] - message_type = 'warning' - else: - message_type = 'success' - - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'WATI Tags Check', - 'message': message, - 'type': message_type, - 'sticky': True, - } - } class WatiHistory(models.Model): _name = 'wati.history' @@ -483,4 +319,4 @@ class WatiHistoryLine(models.Model): ticket_id = fields.Char(string='Ticket ID') type = fields.Char(string='Type') wa_id = fields.Char(string='WA ID') - date_wati = fields.Datetime(string='Date WATI') \ No newline at end of file + date_wati = fields.Datetime(string='Date WATI') -- cgit v1.2.3 From d08d9db9275e9efec1a295224a9d5b8896e957d3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 14 Jun 2025 09:12:44 +0700 Subject: restore wati --- indoteknik_api/controllers/api_v1/wati.py | 171 +----------------------------- indoteknik_custom/__manifest__.py | 1 - 2 files changed, 4 insertions(+), 168 deletions(-) diff --git a/indoteknik_api/controllers/api_v1/wati.py b/indoteknik_api/controllers/api_v1/wati.py index d60cd160..68ff1640 100644 --- a/indoteknik_api/controllers/api_v1/wati.py +++ b/indoteknik_api/controllers/api_v1/wati.py @@ -2,182 +2,19 @@ from .. import controller from odoo import http from odoo.http import request import json -import logging -_logger = logging.getLogger(__name__) - -# class Wati(controller.Controller): -# prefix = '/api/v1/' -# -# @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', methods=['POST', 'OPTIONS']) -# def notification(self, **kw): -# json_raw = json.loads(request.httprequest.data) -# json_dump = json.dumps(json_raw, indent=4, sort_keys=True) -# -# request.env['wati.notification'].create([{ -# 'json_raw': json_dump, -# 'is_lead': False -# }]) -# -# return - -# REPLACE webhook controller yang sudah ada dengan ini: - -class Wati(http.Controller): +class Wati(controller.Controller): prefix = '/api/v1/' - @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', - methods=['POST', 'OPTIONS']) + @http.route(prefix + 'wati/notification', auth='none', type='json', csrf=False, cors='*', methods=['POST', 'OPTIONS']) def notification(self, **kw): json_raw = json.loads(request.httprequest.data) json_dump = json.dumps(json_raw, indent=4, sort_keys=True) - # Create notification record (existing) - notification = request.env['wati.notification'].create([{ + request.env['wati.notification'].create([{ 'json_raw': json_dump, 'is_lead': False }]) - # NEW: Immediate tags check - phone = json_raw.get('waId') or json_raw.get('from') - - if phone: - lead_id = self._check_tags_immediate(phone, notification[0]) - - if lead_id: - notification.write({'is_lead': True, 'lead_id': lead_id}) - _logger.info('🚀 Lead created immediately via webhook: %s' % lead_id) - - return {'status': 'success'} - - def _check_tags_immediate(self, phone, notification): - """Check tags untuk specific phone - immediate""" - try: - _logger.info('📱 Immediate tags check for: %s' % phone) - - # Get contact dari WATI untuk phone ini - wati_api = request.env['wati.api'] - - params = { - 'pageSize': 1, - 'pageNumber': 1, - 'attribute': json.dumps([ - {'name': "phone", 'operator': "contain", 'value': phone} - ]), - } - - result = wati_api.http_get('/api/v1/getContacts', params) - - if isinstance(result, dict) and result.get('result') == 'success': - contact_list = result.get('contact_list', []) - - if contact_list: - contact = contact_list[0] - - # Check if has tags=leads - if self._contact_has_tags_leads(contact): - # Check existing lead - existing_lead = request.env['crm.lead'].search([('phone', '=', phone)], limit=1) - - if existing_lead: - _logger.info('✅ Lead already exists for %s' % phone) - return existing_lead.id - - # Create new lead - lead_id = self._create_lead_from_webhook(contact) - if lead_id: - _logger.info('🎯 Created new lead %s from webhook tags' % lead_id) - return lead_id - - return None - - except Exception as e: - _logger.error('❌ Error in immediate tags check: %s' % str(e)) - return None - - def _contact_has_tags_leads(self, contact): - """Check if contact has tags=leads""" - custom_params = contact.get('customParams', []) - - for param in custom_params: - if (param.get('name', '').lower() == 'tags' and - param.get('value', '').lower() == 'leads'): - return True - - return False - - def _create_lead_from_webhook(self, contact): - """Create lead dari webhook data""" - try: - phone = contact.get('phone', '') - - # Extract data dari customParams - custom_params = contact.get('customParams', []) - contact_data = {} - - for param in custom_params: - param_name = param.get('name', '').lower() - param_value = param.get('value', '').strip() - - if param_name == 'perusahaan': - contact_data['perusahaan'] = param_value - elif param_name == 'name': - contact_data['name'] = param_value - elif param_name == 'email': - contact_data['email'] = param_value - elif param_name == 'sales': - contact_data['sales'] = param_value - elif param_name == 'notes': - contact_data['notes'] = param_value - - # Generate lead name - company_name = contact_data.get('perusahaan', '') - contact_name = contact_data.get('name', '') or contact.get('name', '') - - if company_name: - lead_name = 'WATI Lead (Webhook) - %s' % company_name - elif contact_name: - lead_name = 'WATI Lead (Webhook) - %s' % contact_name - else: - lead_name = 'WATI Lead (Webhook) - %s' % phone - - # Get salesperson - sales_name = contact_data.get('sales', '') - user_id = 2 # Default - - if sales_name: - user = request.env['res.users'].search([('name', 'ilike', sales_name)], limit=1) - if user: - user_id = user.id - - # Create lead - lead_vals = { - 'name': lead_name, - 'phone': phone, - 'contact_name': contact_name, - 'partner_name': company_name, - 'email_from': contact_data.get('email', ''), - 'description': contact_data.get('notes', 'Lead created from WATI webhook (real-time)'), - 'type': 'lead', - 'user_id': user_id, - } - - new_lead = request.env['crm.lead'].create(lead_vals) - - # Create activity untuk follow up - request.env['mail.activity'].create({ - 'activity_type_id': request.env.ref('mail.mail_activity_data_todo').id, - 'summary': '🚨 URGENT: New WATI Lead (Real-time)', - 'note': 'Lead created instantly from WATI webhook when tags=leads was added. Immediate follow up required!', - 'res_id': new_lead.id, - 'res_model_id': request.env.ref('crm.model_crm_lead').id, - 'user_id': user_id, - 'date_deadline': fields.Date.today(), - }) - - return new_lead.id - - except Exception as e: - _logger.error('❌ Error creating lead from webhook: %s' % str(e)) - return None \ No newline at end of file + return diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 3a7c9fa0..9fe3dcdb 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -167,7 +167,6 @@ 'views/coretax_faktur.xml', 'views/public_holiday.xml', 'views/stock_inventory.xml', - 'views/tukar_guling.xml' ], 'demo': [], 'css': [], -- cgit v1.2.3 From dc4089f58420e4395841370f131e8f3a738199d0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 14 Jun 2025 09:20:03 +0700 Subject: add tukar guling view --- indoteknik_custom/__manifest__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 9fe3dcdb..e973f553 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -167,6 +167,7 @@ 'views/coretax_faktur.xml', 'views/public_holiday.xml', 'views/stock_inventory.xml', + 'views/tukar_guling.xml', ], 'demo': [], 'css': [], -- cgit v1.2.3 From f5fc52453c0f7e52b6c87fa57d2de5120c3041e0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 14 Jun 2025 10:05:15 +0700 Subject: check validation --- indoteknik_custom/models/tukar_guling.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index e214e268..36819ad4 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -22,7 +22,6 @@ class TukarGuling(models.Model): ('debit_memo', 'Debit Memo'), ('lain_lain', 'Lain-lain')]) - # ✅ PERBAIKAN: Ganti 'states' dengan 'state' state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('waiting', 'Waiting for Approval'), @@ -30,7 +29,6 @@ class TukarGuling(models.Model): ('cancel', 'Canceled') ], default='draft', tracking=True, required=True) - # ✅ NEW: Line items line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') @api.constrains('line_ids', 'state') @@ -44,6 +42,7 @@ class TukarGuling(models.Model): """Helper method untuk validasi product lines""" self.ensure_one() + # Check ada product lines if not self.line_ids: raise UserError("Belum ada product lines yang ditambahkan!") @@ -74,16 +73,15 @@ class TukarGuling(models.Model): # Reset fields penting saat duplicate default.update({ - 'name': 'New', # Akan auto-generate sequence baru + 'name': 'New', 'state': 'draft', 'date': fields.Datetime.now(), - # ba_num dan out_num tidak di-reset, user bisa edit manual }) # Copy record dengan default values new_record = super(TukarGuling, self).copy(default) - # Re-sequence line items untuk record baru + # Re-sequence line items record baru if new_record.line_ids: for i, line in enumerate(new_record.line_ids): line.sequence = (i + 1) * 10 @@ -100,12 +98,29 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() + # cek bu out sudah diisi atau blm + if not self.out_num: + raise UserError("BU/Out harus diisi!") + + # cek return type + # if not self.return_type: + # raise UserError("Return Type harus diisi!") + if self.state != 'draft': raise UserError("Hanya status Draft saja yang bisa di submit") self.state = 'waiting' def action_approve(self): self.ensure_one() + + # cek bu out sudah diisi atau blm + if not self.out_num: + raise UserError("BU/Out harus diisi!") + + # cek return type + if not self.return_type: + raise UserError("Return Type harus diisi!") + if self.state != 'waiting': raise UserError("Hanya status Waiting saja yang bisa di approve") self.state = 'done' -- cgit v1.2.3 From 50769e870756dd350421a205e6d49ab555023764 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 15 Jun 2025 17:33:46 +0700 Subject: fix state view and add ingoing field --- indoteknik_custom/models/tukar_guling.py | 21 ++++++++++++++------- indoteknik_custom/views/tukar_guling.xml | 14 ++++++++++---- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 36819ad4..b4a901d0 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -10,18 +10,17 @@ class TukarGuling(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', required=True, + out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', domain=[('picking_type_id.code', '=', 'outgoing')]) + in_num = fields.Many2one('stock.picking', 'Nomor BU/In', domain=[('picking_type_id.code', '=', 'incoming')]) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ - ('tukar_guling', 'Tukar Guling'), - ('revisi_so', 'Revisi SO'), + ('tukar_guling', 'Tukar Guling'), # -> barang yang sama + ('revisi_so', 'Revisi SO'), # -> ganti barang ? ('revisi_po', 'Revisi PO'), - ('credit_memo', 'Credit Memo'), - ('debit_memo', 'Debit Memo'), - ('lain_lain', 'Lain-lain')]) - + ('credit_memo', 'Credit Memo'), # -> dijadiin credit memo + ('debit_memo', 'Debit Memo')]) state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('waiting', 'Waiting for Approval'), @@ -31,6 +30,14 @@ class TukarGuling(models.Model): line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') + @api.constrains('return_type', 'out_num', 'in_num') + def _check_return_type_fields(self): + for rec in self: + if rec.return_type in ['tukar_guling', 'credit_memo', 'revisi_so'] and not rec.out_num: + raise ValidationError("Field BU/Out wajib diisi untuk jenis return type tersebut.") + if rec.return_type in ['debit_memo', 'revisi_po'] and not rec.in_num: + raise ValidationError("Field BU/In wajib diisi untuk jenis return type tersebut.") + @api.constrains('line_ids', 'state') def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 8fffcbb0..9b490cd0 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -38,6 +38,7 @@ + - +
@@ -83,13 +83,19 @@ - + + + - + -- cgit v1.2.3 From 32f33ae528f4f9883f38b8afba2ce79222eed4e5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 07:50:57 +0700 Subject: tukar guling fill bu/in or bu/out --- indoteknik_custom/models/tukar_guling.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index b4a901d0..f62206e8 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -30,13 +30,17 @@ class TukarGuling(models.Model): line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') - @api.constrains('return_type', 'out_num', 'in_num') - def _check_return_type_fields(self): - for rec in self: - if rec.return_type in ['tukar_guling', 'credit_memo', 'revisi_so'] and not rec.out_num: - raise ValidationError("Field BU/Out wajib diisi untuk jenis return type tersebut.") - if rec.return_type in ['debit_memo', 'revisi_po'] and not rec.in_num: - raise ValidationError("Field BU/In wajib diisi untuk jenis return type tersebut.") + @api.onchange('return_type') + def _onchange_return_type(self): + domain = [] + if self.return_type in ['debit_memo', 'revisi_po']: + domain = [('picking_type_id.code', '=', 'incoming')] + elif self.return_type in ['revisi_so', 'credit_memo']: + domain = [('picking_type_id.code', '=', 'outgoing')] + elif self.return_type == 'tukar_guling': + domain = [('picking_type_id.code', 'in', ['incoming', 'outgoing'])] + + return {'domain': {'in_num': domain}} @api.constrains('line_ids', 'state') def _check_product_lines(self): -- cgit v1.2.3 From 0354e469c6761964ecfc68208f1ad9a521610d56 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 07:54:40 +0700 Subject: tukar guling fill bu/in or bu/out --- indoteknik_custom/models/tukar_guling.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index f62206e8..4f27afde 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -32,15 +32,28 @@ class TukarGuling(models.Model): @api.onchange('return_type') def _onchange_return_type(self): - domain = [] + in_domain = [] + out_domain = [] + if self.return_type in ['debit_memo', 'revisi_po']: - domain = [('picking_type_id.code', '=', 'incoming')] + # Hanya tampilkan BU In + in_domain = [('picking_type_id.code', '=', 'incoming')] + out_domain = [('id', '=', False)] # Kosongkan BU Out elif self.return_type in ['revisi_so', 'credit_memo']: - domain = [('picking_type_id.code', '=', 'outgoing')] + # Hanya tampilkan BU Out + in_domain = [('id', '=', False)] # Kosongkan BU In + out_domain = [('picking_type_id.code', '=', 'outgoing')] elif self.return_type == 'tukar_guling': - domain = [('picking_type_id.code', 'in', ['incoming', 'outgoing'])] - - return {'domain': {'in_num': domain}} + # Boleh pilih keduanya + in_domain = [('picking_type_id.code', '=', 'incoming')] + out_domain = [('picking_type_id.code', '=', 'outgoing')] + + return { + 'domain': { + 'in_num': in_domain, + 'out_num': out_domain, + } + } @api.constrains('line_ids', 'state') def _check_product_lines(self): -- cgit v1.2.3 From 2f5e84a3f735fd80aa7dad4365db372e872127d7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 07:55:47 +0700 Subject: tukar guling fill bu/in or bu/out --- indoteknik_custom/views/tukar_guling.xml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 9b490cd0..b3fae602 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -83,13 +83,17 @@ - - + attrs="{ + 'invisible': [('return_type', 'not in', ['debit_memo', 'revisi_po', 'tukar_guling'])], + 'required': [('return_type', 'in', ['debit_memo', 'revisi_po'])] + }"/> + + -- cgit v1.2.3 From 402085ff18942c21d1a61eb02f16497c845694b5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 07:56:51 +0700 Subject: validation --- indoteknik_custom/models/tukar_guling.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 4f27afde..27d4d954 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -55,6 +55,13 @@ class TukarGuling(models.Model): } } + @api.constrains('return_type', 'in_num', 'out_num') + def _check_bu_required_for_tukar_guling(self): + for record in self: + if record.return_type == 'tukar_guling': + if not record.in_num and not record.out_num: + raise ValidationError("Untuk Tukar Guling, isi salah satu: BU/In atau BU/Out.") + @api.constrains('line_ids', 'state') def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" -- cgit v1.2.3 From 5ed938e0386e64733b90d8a4b08b0a0a5b4bc00e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 07:58:56 +0700 Subject: validation --- indoteknik_custom/models/tukar_guling.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 27d4d954..f8cbec0a 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -56,11 +56,16 @@ class TukarGuling(models.Model): } @api.constrains('return_type', 'in_num', 'out_num') - def _check_bu_required_for_tukar_guling(self): + def _check_required_bu_fields(self): for record in self: - if record.return_type == 'tukar_guling': - if not record.in_num and not record.out_num: - raise ValidationError("Untuk Tukar Guling, isi salah satu: BU/In atau BU/Out.") + if record.return_type in ['debit_memo', 'revisi_po'] and not record.in_num: + raise ValidationError("BU/In harus diisi untuk return type Debit Memo atau Revisi PO.") + + if record.return_type in ['revisi_so', 'credit_memo'] and not record.out_num: + raise ValidationError("BU/Out harus diisi untuk return type Revisi SO atau Credit Memo.") + + if record.return_type == 'tukar_guling' and not (record.in_num or record.out_num): + raise ValidationError("Untuk Tukar Guling, minimal isi salah satu, BU/In atau BU/Out.") @api.constrains('line_ids', 'state') def _check_product_lines(self): -- cgit v1.2.3 From 0b6109930666039ed8e12569b6a9de7cbb4ea216 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 08:30:48 +0700 Subject: change view --- indoteknik_custom/views/tukar_guling.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index b3fae602..1556adcb 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -83,6 +83,7 @@ + - - + -- cgit v1.2.3 From a0e90200638e26ad06d1caaf2d91d0aeea3ba19d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 16 Jun 2025 14:38:40 +0700 Subject: add tukar guling PO and move from inventory --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/tukar_guling.py | 211 +++++++++++++++++-------- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/tukar_guling.xml | 196 +++++++++++------------ indoteknik_custom/views/tukar_guling_po.xml | 117 ++++++++++++++ 5 files changed, 357 insertions(+), 169 deletions(-) create mode 100644 indoteknik_custom/views/tukar_guling_po.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index e973f553..9f8fad01 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -168,6 +168,7 @@ 'views/public_holiday.xml', 'views/stock_inventory.xml', 'views/tukar_guling.xml', + 'views/tukar_guling_po.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index f8cbec0a..95aa7cd6 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -12,66 +12,34 @@ class TukarGuling(models.Model): date = fields.Datetime('Date', default=fields.Datetime.now, required=True) out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', domain=[('picking_type_id.code', '=', 'outgoing')]) - in_num = fields.Many2one('stock.picking', 'Nomor BU/In', domain=[('picking_type_id.code', '=', 'incoming')]) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ ('tukar_guling', 'Tukar Guling'), # -> barang yang sama ('revisi_so', 'Revisi SO'), # -> ganti barang ? - ('revisi_po', 'Revisi PO'), - ('credit_memo', 'Credit Memo'), # -> dijadiin credit memo - ('debit_memo', 'Debit Memo')]) + ('credit_memo', 'Credit Memo')]) # -> dijadiin credit memo state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), - ('waiting', 'Waiting for Approval'), + ('approval_sales', ' Approval Sales'), + ('approval_logistic', 'Approval Logistic'), + ('approval_finance', 'Approval Finance'), ('done', 'Done'), ('cancel', 'Canceled') ], default='draft', tracking=True, required=True) line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') - @api.onchange('return_type') - def _onchange_return_type(self): - in_domain = [] - out_domain = [] - - if self.return_type in ['debit_memo', 'revisi_po']: - # Hanya tampilkan BU In - in_domain = [('picking_type_id.code', '=', 'incoming')] - out_domain = [('id', '=', False)] # Kosongkan BU Out - elif self.return_type in ['revisi_so', 'credit_memo']: - # Hanya tampilkan BU Out - in_domain = [('id', '=', False)] # Kosongkan BU In - out_domain = [('picking_type_id.code', '=', 'outgoing')] - elif self.return_type == 'tukar_guling': - # Boleh pilih keduanya - in_domain = [('picking_type_id.code', '=', 'incoming')] - out_domain = [('picking_type_id.code', '=', 'outgoing')] - - return { - 'domain': { - 'in_num': in_domain, - 'out_num': out_domain, - } - } - - @api.constrains('return_type', 'in_num', 'out_num') + @api.constrains('return_type', 'out_num') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['debit_memo', 'revisi_po'] and not record.in_num: - raise ValidationError("BU/In harus diisi untuk return type Debit Memo atau Revisi PO.") - - if record.return_type in ['revisi_so', 'credit_memo'] and not record.out_num: - raise ValidationError("BU/Out harus diisi untuk return type Revisi SO atau Credit Memo.") - - if record.return_type == 'tukar_guling' and not (record.in_num or record.out_num): - raise ValidationError("Untuk Tukar Guling, minimal isi salah satu, BU/In atau BU/Out.") + if record.return_type in ['revisi_so', 'credit_memo', 'tukar_guling'] and not record.out_num: + raise ValidationError("BU/Out harus diisi!") @api.constrains('line_ids', 'state') def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" for record in self: - if record.state in ('waiting', 'done') and not record.line_ids: + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") def _validate_product_lines(self): @@ -95,29 +63,29 @@ class TukarGuling(models.Model): return True - @api.model_create_multi - def create(self, vals_list): - for vals in vals_list: - if vals.get('name', 'New') == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG/00001' - return super(TukarGuling, self).create(vals_list) + @api.model + def create(self, vals): + if not vals.get('name') or vals['name'] == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' + return super(TukarGuling, self).create(vals) def copy(self, default=None): - """Override copy untuk custom behavior saat duplicate""" if default is None: default = {} - # Reset fields penting saat duplicate + if 'name' not in default: + default.update({ + 'name': self.env['ir.sequence'].next_by_code(self._name) or 'New', + }) + default.update({ - 'name': 'New', 'state': 'draft', 'date': fields.Datetime.now(), }) - # Copy record dengan default values new_record = super(TukarGuling, self).copy(default) - # Re-sequence line items record baru + # Re-sequence lines if new_record.line_ids: for i, line in enumerate(new_record.line_ids): line.sequence = (i + 1) * 10 @@ -134,40 +102,153 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() - # cek bu out sudah diisi atau blm + + if self.state != 'draft': + raise UserError("Submit hanya bisa dilakukan dari Draft.") + self.state = 'approval_sales' + + def action_approve(self): + self.ensure_one() + if not self.out_num: raise UserError("BU/Out harus diisi!") - # cek return type - # if not self.return_type: - # raise UserError("Return Type harus diisi!") + if not self.return_type: + raise UserError("Return Type harus diisi!") + + # Cek hak akses berdasarkan state + if self.state == 'approval_sales': + if not self.env.user.has_group('indoteknik_custom.group_sales_manager'): + raise UserError("Hanya Sales Manager yang boleh approve tahap ini.") + self.state = 'approval_logistic' + + elif self.state == 'approval_logistic': + if not self.env.user.has_group('indoteknik_custom.group_logistic'): + raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") + self.state = 'approval_finance' + + elif self.state == 'approval_finance': + if not self.env.user.has_group('indoteknik_custom.group_finance'): + raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") + self.state = 'done' + + else: + raise UserError("Status ini tidak bisa di-approve.") + def action_cancel(self): + self.ensure_one() + # if self.state == 'done': + # raise UserError("Tidak bisa cancel jika sudah done") + self.state = 'cancel' + +class TukarGulingPO(models.Model): + _name = 'tukar.guling.po' + _inherit = 'tukar.guling' + _description = 'Tukar Guling PO' + + # tukar_guling_id = fields.Many2one( + # 'tukar.guling', required=True, ondelete='cascade', string='Tukar Guling Ref' + # ) + + return_type = fields.Selection([ + ('tukar_guling', 'Tukar Guling'), + ('revisi_po', 'Revisi PO'), + ('debit_memo', 'Debit Memo'), + ], string='Return Type', required=True) + + @api.constrains('return_type', 'out_num') + def _check_required_bu_fields(self): + for record in self: + if record.return_type in ['tukar_guling', 'revisi_po', 'debit_memo'] and not record.out_num: + raise ValidationError("BU/Out harus diisi!") + + @api.constrains('line_ids', 'state') + def _check_product_lines(self): + """Constraint: Product lines harus ada jika state bukan draft""" + for record in self: + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: + raise ValidationError("Product lines harus diisi sebelum submit atau approve!") + + def _validate_product_lines(self): + """Helper method untuk validasi product lines""" + self.ensure_one() + + # Check ada product lines + if not self.line_ids: + raise UserError("Belum ada product lines yang ditambahkan!") + + # Check product sudah diisi + empty_lines = self.line_ids.filtered(lambda line: not line.product_id) + if empty_lines: + raise UserError("Ada product lines yang belum diisi productnya!") + + # Check quantity > 0 + zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0) + if zero_qty_lines: + raise UserError("Quantity product tidak boleh kosong atau 0!") + return True + + @api.model + def create(self, vals): + if not vals.get('name') or vals['name'] in ('New', False): + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' + return super(TukarGulingPO, self).create(vals) + def copy(self, default=None): + if default is None: + default = {} + + # Generate sequence satu-satunya di sini + default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' + default['state'] = 'draft' + default['date'] = fields.Datetime.now() + + new_record = super(TukarGulingPO, self).copy(default) + + # Re-sequence lines + if new_record.line_ids: + for i, line in enumerate(new_record.line_ids): + line.sequence = (i + 1) * 10 + + return new_record + + def action_draft(self): + """Reset to draft state""" + for record in self: + if record.state == 'cancel': + record.write({'state': 'draft'}) + else: + raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") + + def action_submit(self): + self.ensure_one() if self.state != 'draft': - raise UserError("Hanya status Draft saja yang bisa di submit") - self.state = 'waiting' + raise UserError("Submit hanya bisa dilakukan dari Draft.") + self.state = 'approval_sales' def action_approve(self): self.ensure_one() - # cek bu out sudah diisi atau blm if not self.out_num: raise UserError("BU/Out harus diisi!") - # cek return type if not self.return_type: raise UserError("Return Type harus diisi!") - if self.state != 'waiting': - raise UserError("Hanya status Waiting saja yang bisa di approve") - self.state = 'done' + if self.state == 'approval_sales': + self.state = 'approval_logistic' + elif self.state == 'approval_logistic': + self.state = 'approval_finance' + elif self.state == 'approval_finance': + self.state = 'done' + else: + raise UserError("Status ini tidak bisa di-approve.") def action_cancel(self): self.ensure_one() - if self.state == 'done': - raise UserError("Tidak bisa cancel jika sudah done") + # if self.state == 'done': + # raise UserError("Tidak bisa cancel jika sudah done") self.state = 'cancel' - class TukarGulingLine(models.Model): _name = 'tukar.guling.line' _description = 'Tukar Guling Line' diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 26198a0f..f8fb6ac6 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -182,4 +182,5 @@ access_production_purchase_match,access.production.purchase.match,model_producti access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 +access_tukar_guling_po_all_users,tukar.guling.po.all.users,model_tukar_guling_po,base.group_user,1,1,1,1 access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_guling_line,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 1556adcb..e008d2a1 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -1,127 +1,115 @@ - - - - Pengajuan Tukar Guling - ir.actions.act_window - tukar.guling - tree,form - - - - + + + Pengajuan Tukar Guling SO + ir.actions.act_window + tukar.guling + tree,form + + + - - - - Pengajuan Tukar Guling - tukar.guling - PTG/ - 5 - 1 - 1 - - - - - - pengajuan.tukar.guling.tree - tukar.guling - - - - - - - - - + + Pengajuan Tukar Guling SO + tukar.guling + PTG/ + 5 + 1 + 1 + + + + + pengajuan.tukar.guling.tree + tukar.guling + + + + + + + + - - - - - - - pengajuan.tukar.guling.form - tukar.guling - -
-
-
- -
-

- -

-
-
- - - - - - - + + +
+

+ +

+
+
+ + + + + - - - - - - - - - - - - - - - + + + + +
+ + + + + + + - - - + + - - - - -
-
-
-
-
+ + + + + + + + +
\ No newline at end of file diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml new file mode 100644 index 00000000..840b625b --- /dev/null +++ b/indoteknik_custom/views/tukar_guling_po.xml @@ -0,0 +1,117 @@ + + + + + + Pengajuan Tukar Guling PO + ir.actions.act_window + tukar.guling.po + tree,form + + + + + + + Pengajuan Tukar Guling PO + tukar.guling.po + PTGPO/ + 5 + 1 + 1 + + + + + + pengajuan.tukar.guling.po.tree + tukar.guling.po + + + + + + + + + + + + + + + pengajuan.tukar.guling.po.form + tukar.guling.po + +
+
+
+ +
+

+ +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
-- cgit v1.2.3 From abd7da741c6eec02dbefa195b91dbedd70b3323e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 17 Jun 2025 08:09:57 +0700 Subject: add tukar guling PO and move from inventory --- indoteknik_custom/models/tukar_guling.py | 56 ++++++++++++++++++++++---- indoteknik_custom/security/ir.model.access.csv | 1 + indoteknik_custom/views/tukar_guling_po.xml | 6 +-- 3 files changed, 52 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 95aa7cd6..aeb2c9e7 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -142,12 +142,25 @@ class TukarGuling(models.Model): class TukarGulingPO(models.Model): _name = 'tukar.guling.po' - _inherit = 'tukar.guling' _description = 'Tukar Guling PO' - # tukar_guling_id = fields.Many2one( - # 'tukar.guling', required=True, ondelete='cascade', string='Tukar Guling Ref' - # ) + name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') + date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', + domain=[('picking_type_id.code', '=', 'outgoing')]) + ba_num = fields.Text('Nomor BA') + notes = fields.Text('Notes') + state = fields.Selection(string='Status', selection=[ + ('draft', 'Draft'), + ('approval_purchase', ' Approval Purchase'), + ('approval_logistic', 'Approval Logistic'), + ('approval_finance', 'Approval Finance'), + ('done', 'Done'), + ('cancel', 'Canceled') + ], default='draft', tracking=True, required=True) + + line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines') + tukar_guling_po_id = fields.Many2one('tukar.guling.po', 'Tukar Guling PO') return_type = fields.Selection([ ('tukar_guling', 'Tukar Guling'), @@ -165,7 +178,7 @@ class TukarGulingPO(models.Model): def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" for record in self: - if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: + if record.state in ('approval_purchase', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") def _validate_product_lines(self): @@ -223,7 +236,7 @@ class TukarGulingPO(models.Model): if self.state != 'draft': raise UserError("Submit hanya bisa dilakukan dari Draft.") - self.state = 'approval_sales' + self.state = 'approval_purchase' def action_approve(self): self.ensure_one() @@ -234,12 +247,21 @@ class TukarGulingPO(models.Model): if not self.return_type: raise UserError("Return Type harus diisi!") - if self.state == 'approval_sales': + if self.state == 'approval_purchase': + if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'): + raise UserError("Hanya Purchasing yang boleh approve tahap ini.") self.state = 'approval_logistic' + elif self.state == 'approval_logistic': + if not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") self.state = 'approval_finance' + elif self.state == 'approval_finance': + if not self.env.user.has_group('indoteknik_custom.group_role_fat'): + raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") self.state = 'done' + else: raise UserError("Status ini tidak bisa di-approve.") @@ -281,4 +303,22 @@ class TukarGulingLine(models.Model): def _onchange_product_id(self): if self.product_id: self.name = self.product_id.display_name - self.product_uom = self.product_id.uom_id \ No newline at end of file + self.product_uom = self.product_id.uom_id + +class TukarGulingLinePO(models.Model): + _name = 'tukar.guling.line.po' + _description = 'Tukar Guling Line (PO)' + _order = 'sequence, id' + + tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', required=True, ondelete='cascade') + sequence = fields.Integer('Sequence', default=10, copy=False) + product_id = fields.Many2one('product.product', string='Product', required=True) + product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0) + product_uom = fields.Many2one('uom.uom', string='Unit of Measure') + name = fields.Text('Description') + + @api.onchange('product_id') + def _onchange_product_id(self): + if self.product_id: + self.name = self.product_id.display_name + self.product_uom = self.product_id.uom_id diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index f8fb6ac6..45ce23dd 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -184,3 +184,4 @@ access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_match access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 access_tukar_guling_po_all_users,tukar.guling.po.all.users,model_tukar_guling_po,base.group_user,1,1,1,1 access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_guling_line,base.group_user,1,1,1,1 +access_tukar_guling_line_po_all_users,tukar.guling.line.po.all.users,model_tukar_guling_line_po,base.group_user,1,1,1,1 diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml index 840b625b..3ad269ee 100644 --- a/indoteknik_custom/views/tukar_guling_po.xml +++ b/indoteknik_custom/views/tukar_guling_po.xml @@ -40,7 +40,7 @@ @@ -60,7 +60,7 @@ attrs="{'invisible': [('state', '!=', 'draft')]}"/> +

@@ -80,6 +89,9 @@ + + + - - - + + + - + Date: Wed, 18 Jun 2025 09:33:37 +0700 Subject: tukar guling so done --- indoteknik_custom/models/tukar_guling.py | 23 ++++++++++++++++++++--- indoteknik_custom/views/tukar_guling.xml | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index c1672b73..7ed6e10f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -1,6 +1,8 @@ from odoo import models, fields, api from odoo.exceptions import UserError, ValidationError +import logging +_logger = logging.getLogger(__name__) class TukarGuling(models.Model): _name = 'tukar.guling' @@ -178,7 +180,19 @@ class TukarGuling(models.Model): if not self.out_num: raise UserError("BU/Out harus diisi terlebih dahulu.") - group_id = self.out_num.group_id.id if self.out_num.group_id else False + origin_so = self.out_num.origin + if not origin_so: + raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") + + # Cari DO dari SO + get_group_id = self.env['stock.picking'].search([ + ('origin', '=', origin_so), + ], limit=1) + + if not get_group_id: + raise UserError(f"Delivery Order dari SO {origin_so} tidak ditemukan.") + + group_id = get_group_id.group_id.id if get_group_id.group_id else False Picking = self.env['stock.picking'] srt_type = self.env['stock.picking.type'].search([ @@ -191,6 +205,7 @@ class TukarGuling(models.Model): # Lokasi location_dest_id = srt_type.default_location_dest_id.id + location_dest_id_ort = ort_type.default_location_dest_id.id location_customer = self.out_num.location_dest_id # 1. BU/SRT: retur dari out_num @@ -210,6 +225,7 @@ class TukarGuling(models.Model): 'product_uom': line.product_uom.id, 'location_id': location_customer.id, 'location_dest_id': location_dest_id, + 'group_id': group_id, }) for line in self.line_ids ] }) @@ -233,7 +249,7 @@ class TukarGuling(models.Model): 'partner_id': self.out_num.partner_id.id, 'picking_type_id': ort_type.id, 'location_id': location_dest_id, - 'location_dest_id': location_customer.id, + 'location_dest_id': location_dest_id_ort, 'origin': f"Retur {pick.name}", 'tukar_guling_id': self.id, 'group_id': group_id, @@ -244,7 +260,8 @@ class TukarGuling(models.Model): 'product_uom_qty': line.product_uom_qty, 'product_uom': line.product_uom.id, 'location_id': location_dest_id, - 'location_dest_id': location_customer.id, + 'location_dest_id': location_dest_id_ort, + 'group_id': group_id, }) for line in self.line_ids ] }) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 013fdc24..9f8a6ff6 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -89,7 +89,6 @@ - + -- cgit v1.2.3 From d1ff4bd35deac6c17a17e97f0904f67e113c5add Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 18 Jun 2025 13:32:05 +0700 Subject: revisi, fetch item from bu out in tukar guling line --- indoteknik_custom/models/tukar_guling.py | 141 ++++++++++++++++++++++------ indoteknik_custom/views/tukar_guling.xml | 14 +-- indoteknik_custom/views/tukar_guling_po.xml | 4 +- 3 files changed, 122 insertions(+), 37 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 7ed6e10f..a5724104 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -4,6 +4,7 @@ import logging _logger = logging.getLogger(__name__) + class TukarGuling(models.Model): _name = 'tukar.guling' _description = 'Tukar Guling' @@ -21,14 +22,13 @@ class TukarGuling(models.Model): ) name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', - domain=[('picking_type_id.code', '=', 'outgoing')]) + operations = fields.Many2one('stock.picking', 'Operations', + domain=[('picking_type_id.code', '=', 'outgoing')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ - ('tukar_guling', 'Tukar Guling'), # -> barang yang sama - ('revisi_so', 'Revisi SO'), # -> ganti barang ? - ('credit_memo', 'Credit Memo')]) # -> dijadiin credit memo + ('tukar_guling', 'Tukar Guling'), # -> barang yang sama + ('revisi_so', 'Revisi SO')]) state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('approval_sales', ' Approval Sales'), @@ -40,24 +40,108 @@ class TukarGuling(models.Model): line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') - @api.constrains('return_type', 'out_num') + @api.onchange('operations') + def _onchange_operations(self): + """Auto-populate lines ketika operations dipilih""" + if self.operations: + # Clear existing lines + self.line_ids = [(5, 0, 0)] + + # Set origin dari operations + if self.operations.origin: + self.origin = self.operations.origin + + # Auto-populate lines dari move_ids operations + lines_data = [] + sequence = 10 + + # Untuk Odoo 14, gunakan move_ids_without_package atau move_lines + moves_to_check = [] + + # 1. move_ids_without_package (standard di Odoo 14) + if hasattr(self.operations, 'move_ids_without_package') and self.operations.move_ids_without_package: + moves_to_check = self.operations.move_ids_without_package + # 2. move_lines (backup untuk versi lama) + elif hasattr(self.operations, 'move_lines') and self.operations.move_lines: + moves_to_check = self.operations.move_lines + + # Debug logging + _logger = logging.getLogger(__name__) + _logger.info(f"BU/OUT: {self.operations.name}, State: {self.operations.state}") + _logger.info(f"Total moves found: {len(moves_to_check)}") + + for move in moves_to_check: + _logger.info( + f"Move: {move.name}, Product: {move.product_id.name if move.product_id else 'No Product'}, Qty: {move.product_uom_qty}, State: {move.state}") + + # Ambil semua move yang ada quantity + if move.product_id and move.product_uom_qty > 0: + lines_data.append((0, 0, { + 'sequence': sequence, + 'product_id': move.product_id.id, + 'product_uom_qty': move.product_uom_qty, + 'product_uom': move.product_uom.id, + 'name': move.name or move.product_id.display_name, + })) + sequence += 10 + + if lines_data: + self.line_ids = lines_data + _logger.info(f"Created {len(lines_data)} lines") + else: + _logger.info("No lines created - no valid moves found") + else: + # Clear lines jika operations dikosongkan + self.line_ids = [(5, 0, 0)] + self.origin = False + + def action_populate_lines(self): + """Manual button untuk populate lines - sebagai alternatif""" + self.ensure_one() + if not self.operations: + raise UserError("Pilih BU/OUT terlebih dahulu!") + + # Clear existing lines + self.line_ids = [(5, 0, 0)] + + lines_data = [] + sequence = 10 + + # Ambil semua stock moves dari operations + for move in self.operations.move_ids: + if move.product_uom_qty > 0: + lines_data.append((0, 0, { + 'sequence': sequence, + 'product_id': move.product_id.id, + 'product_uom_qty': move.product_uom_qty, + 'product_uom': move.product_uom.id, + 'name': move.name or move.product_id.display_name, + })) + sequence += 10 + + if lines_data: + self.line_ids = lines_data + else: + raise UserError("Tidak ditemukan barang di BU/OUT yang dipilih!") + + @api.constrains('return_type', 'operations') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['revisi_so', 'credit_memo', 'tukar_guling'] and not record.out_num: + if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations: raise ValidationError("BU/Out harus diisi!") @api.constrains('line_ids', 'state') def _check_product_lines(self): """Constraint: Product lines harus ada jika state bukan draft""" for record in self: - if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', + 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") def _validate_product_lines(self): """Helper method untuk validasi product lines""" self.ensure_one() - # Check ada product lines if not self.line_ids: raise UserError("Belum ada product lines yang ditambahkan!") @@ -78,9 +162,9 @@ class TukarGuling(models.Model): def create(self, vals): if not vals.get('name') or vals['name'] == 'New': vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' - # Auto-fill origin from out_num - if not vals.get('origin') and vals.get('out_num'): - picking = self.env['stock.picking'].browse(vals['out_num']) + # Auto-fill origin from operations + if not vals.get('origin') and vals.get('operations'): + picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: vals['origin'] = picking.origin return super(TukarGuling, self).create(vals) @@ -109,8 +193,8 @@ class TukarGuling(models.Model): return new_record def write(self, vals): - if 'out_num' in vals and not vals.get('origin'): - picking = self.env['stock.picking'].browse(vals['out_num']) + if 'operations' in vals and not vals.get('origin'): + picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: vals['origin'] = picking.origin @@ -145,7 +229,7 @@ class TukarGuling(models.Model): def action_approve(self): self.ensure_one() - if not self.out_num: + if not self.operations: raise UserError("BU/Out harus diisi!") if not self.return_type: @@ -170,6 +254,7 @@ class TukarGuling(models.Model): rec._create_pickings() else: raise UserError("Status ini tidak bisa di-approve.") + def action_cancel(self): self.ensure_one() # if self.state == 'done': @@ -177,10 +262,10 @@ class TukarGuling(models.Model): self.state = 'cancel' def _create_pickings(self): - if not self.out_num: + if not self.operations: raise UserError("BU/Out harus diisi terlebih dahulu.") - origin_so = self.out_num.origin + origin_so = self.operations.origin if not origin_so: raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") @@ -206,15 +291,15 @@ class TukarGuling(models.Model): # Lokasi location_dest_id = srt_type.default_location_dest_id.id location_dest_id_ort = ort_type.default_location_dest_id.id - location_customer = self.out_num.location_dest_id + location_customer = self.operations.location_dest_id - # 1. BU/SRT: retur dari out_num + # 1. BU/SRT: retur dari operations srt_picking = Picking.create({ - 'partner_id': self.out_num.partner_id.id, + 'partner_id': self.operations.partner_id.id, 'picking_type_id': srt_type.id, 'location_id': location_customer.id, 'location_dest_id': location_dest_id, - 'origin': f"Retur {self.out_num.name}", + 'origin': f"Retur {self.operations.name}", 'tukar_guling_id': self.id, 'group_id': group_id, 'move_ids_without_package': [ @@ -232,7 +317,7 @@ class TukarGuling(models.Model): srt_picking.action_confirm() # 2. Cari BU/PICK dari SO yang sama - origin_so = self.out_num.origin + origin_so = self.operations.origin if not origin_so: raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") @@ -246,7 +331,7 @@ class TukarGuling(models.Model): # 3. BU/ORT: retur dari BU/PICK ort_picking = Picking.create({ - 'partner_id': self.out_num.partner_id.id, + 'partner_id': self.operations.partner_id.id, 'picking_type_id': ort_type.id, 'location_id': location_dest_id, 'location_dest_id': location_dest_id_ort, @@ -266,7 +351,7 @@ class TukarGuling(models.Model): ] }) ort_picking.action_confirm() - + ort_picking.action_assign() class TukarGulingPO(models.Model): _name = 'tukar.guling.po' @@ -274,7 +359,7 @@ class TukarGulingPO(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - out_num = fields.Many2one('stock.picking', 'Nomor BU/Out', + operations = fields.Many2one('stock.picking', 'Nomor BU/Out', domain=[('picking_type_id.code', '=', 'outgoing')]) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') @@ -296,10 +381,10 @@ class TukarGulingPO(models.Model): ('debit_memo', 'Debit Memo'), ], string='Return Type', required=True) - @api.constrains('return_type', 'out_num') + @api.constrains('return_type', 'operations') def _check_required_bu_fields(self): for record in self: - if record.return_type in ['tukar_guling', 'revisi_po', 'debit_memo'] and not record.out_num: + if record.return_type in ['tukar_guling', 'revisi_po', 'debit_memo'] and not record.operations: raise ValidationError("BU/Out harus diisi!") @api.constrains('line_ids', 'state') @@ -369,7 +454,7 @@ class TukarGulingPO(models.Model): def action_approve(self): self.ensure_one() - if not self.out_num: + if not self.operations: raise UserError("BU/Out harus diisi!") if not self.return_type: diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 9f8a6ff6..c775c301 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -3,7 +3,7 @@ - Pengajuan Tukar Guling SO + Pengajuan Return SO ir.actions.act_window tukar.guling tree,form @@ -11,14 +11,14 @@ - Pengajuan Tukar Guling SO + Pengajuan Return SO tukar.guling PTG/ 5 @@ -34,7 +34,7 @@ - + - diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml index 3ad269ee..76d85904 100644 --- a/indoteknik_custom/views/tukar_guling_po.xml +++ b/indoteknik_custom/views/tukar_guling_po.xml @@ -36,7 +36,7 @@ - + - - + -- cgit v1.2.3 From 177a0e86577ed705e07f19e880f8a7cfaced7c2a Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 08:05:11 +0700 Subject: cannot edit line product only delete --- indoteknik_custom/views/tukar_guling.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 4cc3495a..ce4a6737 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -105,9 +105,9 @@ - - - + + + -- cgit v1.2.3 From ff9b0cb54bb2f9c99773cfc7c8c5b612bb5d7444 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 08:45:34 +0700 Subject: rev cannot edit line product only delete --- indoteknik_custom/views/tukar_guling.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index ce4a6737..b257000f 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -105,8 +105,8 @@ - - + + Date: Thu, 19 Jun 2025 10:33:02 +0700 Subject: push --- indoteknik_custom/__manifest__.py | 2 +- indoteknik_custom/models/stock_picking_return.py | 432 +++++++++++++++++++++-- indoteknik_custom/models/tukar_guling.py | 148 -------- indoteknik_custom/security/ir.model.access.csv | 2 - 4 files changed, 399 insertions(+), 185 deletions(-) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index b9365ba9..f58acbfb 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -169,7 +169,7 @@ 'views/stock_inventory.xml', 'views/sale_order_delay.xml', 'views/tukar_guling.xml', - 'views/tukar_guling_po.xml', + # 'views/tukar_guling_po.xml', ], 'demo': [], 'css': [], diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index a683d80e..341383e8 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -1,38 +1,402 @@ -from odoo import _, api, fields, models -from odoo.exceptions import UserError -from odoo.tools.float_utils import float_round +from odoo import models, fields, api +from odoo.exceptions import UserError, ValidationError +import logging +_logger = logging.getLogger(__name__) -class ReturnPicking(models.TransientModel): - _inherit = 'stock.return.picking' + +class TukarGuling(models.Model): + _name = 'tukar.guling' + _description = 'Tukar Guling' + _order = 'date desc, id desc' + _rec_name = 'name' + + origin = fields.Char(string='Origin SO') + real_shipping_id = fields.Many2one('res.partner', string='Shipping Address') + picking_ids = fields.One2many('stock.picking', 'tukar_guling_id', string='Transfers') + + name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') + date = fields.Datetime('Date', default=fields.Datetime.now, required=True) + operations = fields.Many2one('stock.picking', 'Operations', + domain=[('picking_type_id.code', '=', 'outgoing')], + help='Nomor BU/Out atau BU/Pick') + ba_num = fields.Text('Nomor BA') + notes = fields.Text('Notes') + return_type = fields.Selection(String='Return Type', selection=[ + ('tukar_guling', 'Tukar Guling'), + ('revisi_so', 'Revisi SO')]) + + state = fields.Selection(string='Status', selection=[ + ('draft', 'Draft'), + ('approval_sales', 'Approval Sales'), + ('approval_logistic', 'Approval Logistic'), + ('approval_finance', 'Approval Finance'), + ('done', 'Done'), + ('cancel', 'Canceled') + ], default='draft', tracking=True, required=True) + + line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') + + @api.onchange('operations') + def _onchange_operations(self): + """Auto-populate lines ketika operations dipilih""" + if self.operations: + # Clear existing lines + self.line_ids = [(5, 0, 0)] + + # Set origin dari operations + if self.operations.origin: + self.origin = self.operations.origin + + # Set shipping address + if self.operations.real_shipping_id: + self.real_shipping_id = self.operations.real_shipping_id.id + + # Auto-populate lines dari move_ids operations + lines_data = [] + sequence = 10 + + # Ambil moves yang sudah done/delivered + moves_to_check = self.operations.move_ids_without_package.filtered( + lambda m: m.state == 'done' and m.quantity_done > 0 + ) + + _logger.info(f"BU/OUT: {self.operations.name}, State: {self.operations.state}") + _logger.info(f"Total moves found: {len(moves_to_check)}") + + for move in moves_to_check: + _logger.info( + f"Move: {move.name}, Product: {move.product_id.name if move.product_id else 'No Product'}, " + f"Qty Done: {move.quantity_done}, State: {move.state}" + ) + + # Hanya ambil yang sudah done dengan quantity_done > 0 + if move.product_id and move.quantity_done > 0: + lines_data.append((0, 0, { + 'sequence': sequence, + 'product_id': move.product_id.id, + 'product_uom_qty': move.quantity_done, # Gunakan quantity_done + 'product_uom': move.product_uom.id, + 'name': move.name or move.product_id.display_name, + })) + sequence += 10 + + if lines_data: + self.line_ids = lines_data + _logger.info(f"Created {len(lines_data)} lines") + else: + _logger.info("No lines created - no valid moves found") + else: + # Clear lines jika operations dikosongkan + self.line_ids = [(5, 0, 0)] + self.origin = False + self.real_shipping_id = False + + def _create_pickings(self): + """Improved picking creation with proper move handling""" + if not self.operations: + raise UserError("BU/Out harus diisi terlebih dahulu.") + + origin_so = self.operations.origin + if not origin_so: + raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") + + # Cari BU/PICK dari SO yang sama + pick_picking = self.env['stock.picking'].search([ + ('origin', '=', origin_so), + ('picking_type_id.code', '=', 'internal') + ], limit=1) + + if not pick_picking: + raise UserError(f"BU/PICK dengan origin {origin_so} tidak ditemukan.") + + # Ambil group_id dari operations + group_id = self.operations.group_id.id if self.operations.group_id else False + + Picking = self.env['stock.picking'] + StockMove = self.env['stock.move'] + + # Cari picking types + srt_type = self.env['stock.picking.type'].search([ + ('sequence_code', '=', 'SRT') + ], limit=1) + + ort_type = self.env['stock.picking.type'].search([ + ('sequence_code', '=', 'ORT') + ], limit=1) + + if not srt_type or not ort_type: + raise UserError("Picking type SRT atau ORT tidak ditemukan!") + + # Lokasi + location_dest_id = srt_type.default_location_dest_id.id + location_dest_id_ort = ort_type.default_location_dest_id.id + location_customer = self.operations.location_dest_id + + # 1. Create BU/SRT: return dari customer ke gudang + srt_picking = Picking.create({ + 'partner_id': self.operations.partner_id.id, + 'real_shipping_id': self.operations.real_shipping_id.id, + 'picking_type_id': srt_type.id, + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + 'origin': f"Retur {self.operations.name}", + 'tukar_guling_id': self.id, + 'group_id': group_id, + }) + + # Create moves untuk SRT + srt_moves = [] + for line in self.line_ids: + move_vals = { + 'name': line.name or line.product_id.name, + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + 'picking_id': srt_picking.id, + 'group_id': group_id, + 'state': 'draft', + } + move = StockMove.create(move_vals) + srt_moves.append(move) + + # Confirm SRT picking + srt_picking.action_confirm() + + # 2. Create BU/ORT: return dari gudang ke supplier/vendor + ort_picking = Picking.create({ + 'partner_id': self.operations.partner_id.id, + 'real_shipping_id': self.operations.real_shipping_id.id, + 'picking_type_id': ort_type.id, + 'location_id': location_dest_id, + 'location_dest_id': location_dest_id_ort, + 'origin': f"Retur {pick_picking.name}", + 'tukar_guling_id': self.id, + 'group_id': group_id, + }) + + # Create moves untuk ORT + ort_moves = [] + for line in self.line_ids: + move_vals = { + 'name': line.name or line.product_id.name, + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'location_id': location_dest_id, + 'location_dest_id': location_dest_id_ort, + 'picking_id': ort_picking.id, + 'group_id': group_id, + 'state': 'draft', + } + move = StockMove.create(move_vals) + ort_moves.append(move) + + # Confirm ORT picking + ort_picking.action_confirm() + ort_picking.action_assign() + + # Log creation + _logger.info(f"Created SRT picking: {srt_picking.name} with {len(srt_moves)} moves") + _logger.info(f"Created ORT picking: {ort_picking.name} with {len(ort_moves)} moves") + + return { + 'srt_picking': srt_picking, + 'ort_picking': ort_picking + } + + def action_approve(self): + self.ensure_one() + + if not self.operations: + raise UserError("BU/Out harus diisi!") + + if not self.return_type: + raise UserError("Return Type harus diisi!") + + # Validasi product lines + self._validate_product_lines() + + # Cek hak akses berdasarkan state + if self.state == 'approval_sales': + if not self.env.user.has_group('indoteknik_custom.group_role_sales'): + raise UserError("Hanya Sales Manager yang boleh approve tahap ini.") + self.state = 'approval_logistic' + + elif self.state == 'approval_logistic': + if not self.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") + self.state = 'approval_finance' + + elif self.state == 'approval_finance': + if not self.env.user.has_group('indoteknik_custom.group_role_fat'): + raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") + self.state = 'done' + # Create pickings saat final approval + result = self._create_pickings() + if result: + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': 'Success', + 'message': f"Berhasil membuat BU/SRT: {result['srt_picking'].name} dan BU/ORT: {result['ort_picking'].name}", + 'type': 'success', + 'sticky': False, + } + } + else: + raise UserError("Status ini tidak bisa di-approve.") + + # ... (rest of the methods remain the same) + @api.constrains('return_type', 'operations') + def _check_required_bu_fields(self): + for record in self: + if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations: + raise ValidationError("BU/Out harus diisi!") + + @api.constrains('line_ids', 'state') + def _check_product_lines(self): + """Constraint: Product lines harus ada jika state bukan draft""" + for record in self: + if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', + 'done') and not record.line_ids: + raise ValidationError("Product lines harus diisi sebelum submit atau approve!") + + def _validate_product_lines(self): + """Helper method untuk validasi product lines""" + self.ensure_one() + + # Check ada product lines + if not self.line_ids: + raise UserError("Belum ada product lines yang ditambahkan!") + + # Check product sudah diisi + empty_lines = self.line_ids.filtered(lambda line: not line.product_id) + if empty_lines: + raise UserError("Ada product lines yang belum diisi productnya!") + + # Check quantity > 0 + zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0) + if zero_qty_lines: + raise UserError("Quantity product tidak boleh kosong atau 0!") + + return True @api.model - def default_get(self, fields): - res = super(ReturnPicking, self).default_get(fields) - - stock_picking = self.env['stock.picking'].search([ - ('id', '=', res['picking_id']), - ]) - - # sale_id = stock_picking.group_id.sale_id - if not stock_picking.approval_return_status == 'approved': - raise UserError('Harus Approval Accounting AR untuk melakukan Retur') - - # purchase = self.env['purchase.order'].search([ - # ('name', '=', stock_picking.group_id.name), - # ]) - # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: - # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - - return res - -class ReturnPickingLine(models.TransientModel): - _inherit = 'stock.return.picking.line' - - @api.onchange('quantity') - def _onchange_quantity(self): - for rec in self: - qty_done = rec.move_id.quantity_done - - if rec.quantity > qty_done: - raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file + def create(self, vals): + if not vals.get('name') or vals['name'] == 'New': + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' + # Auto-fill origin from operations + if not vals.get('origin') and vals.get('operations'): + picking = self.env['stock.picking'].browse(vals['operations']) + if picking.origin: + vals['origin'] = picking.origin + return super(TukarGuling, self).create(vals) + + def action_submit(self): + self.ensure_one() + if self.state != 'draft': + raise UserError("Submit hanya bisa dilakukan dari Draft.") + + # Validasi sebelum submit + self._validate_product_lines() + + self.state = 'approval_sales' + + def action_cancel(self): + self.ensure_one() + # Cek apakah ada picking yang sudah dibuat + if self.picking_ids: + done_pickings = self.picking_ids.filtered(lambda p: p.state == 'done') + if done_pickings: + raise UserError("Tidak bisa cancel karena ada transfer yang sudah selesai!") + + self.state = 'cancel' + + def action_view_picking(self): + self.ensure_one() + action = self.env.ref('stock.action_picking_tree_all').read()[0] + pickings = self.picking_ids + if len(pickings) > 1: + action['domain'] = [('id', 'in', pickings.ids)] + elif pickings: + action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] + action['res_id'] = pickings.id + else: + raise UserError("Belum ada transfer yang dibuat!") + return action + + +class TukarGulingLine(models.Model): + _name = 'tukar.guling.line' + _description = 'Tukar Guling Line' + _order = 'sequence, id' + + sequence = fields.Integer('Sequence', default=10, copy=False) + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling', required=True, ondelete='cascade') + product_id = fields.Many2one('product.product', string='Product', required=True) + product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0) + product_uom = fields.Many2one('uom.uom', string='Unit of Measure') + name = fields.Text('Description') + + @api.model_create_multi + def create(self, vals_list): + """Override create to auto-assign sequence""" + for vals in vals_list: + if 'sequence' not in vals or vals.get('sequence', 0) <= 0: + # Get max sequence untuk tukar_guling yang sama + tukar_guling_id = vals.get('tukar_guling_id') + if tukar_guling_id: + max_seq = self.search([ + ('tukar_guling_id', '=', tukar_guling_id) + ], order='sequence desc', limit=1) + vals['sequence'] = (max_seq.sequence or 0) + 10 + else: + vals['sequence'] = 10 + return super(TukarGulingLine, self).create(vals_list) + + @api.onchange('product_id') + def _onchange_product_id(self): + if self.product_id: + self.name = self.product_id.display_name + self.product_uom = self.product_id.uom_id + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Reference') + + def action_create_tukar_guling(self): + """Action untuk membuat Tukar Guling dari picking""" + self.ensure_one() + + # Cek apakah picking sudah done + if self.state != 'done': + raise UserError("Hanya bisa membuat Tukar Guling dari delivery yang sudah selesai!") + + # Cek apakah sudah ada tukar guling untuk picking ini + existing_tukar_guling = self.env['tukar.guling'].search([ + ('operations', '=', self.id) + ]) + + if existing_tukar_guling: + raise UserError(f"Sudah ada Tukar Guling untuk delivery ini: {existing_tukar_guling.name}") + + # Create tukar guling baru + tukar_guling = self.env['tukar.guling'].create({ + 'operations': self.id, + 'return_type': 'tukar_guling', # default value + }) + + return { + 'type': 'ir.actions.act_window', + 'name': 'Tukar Guling', + 'res_model': 'tukar.guling', + 'res_id': tukar_guling.id, + 'view_mode': 'form', + 'target': 'current', + } \ No newline at end of file diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 186cff97..08b862a7 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -355,137 +355,6 @@ class TukarGuling(models.Model): ort_picking.action_confirm() ort_picking.action_assign() -class TukarGulingPO(models.Model): - _name = 'tukar.guling.po' - _description = 'Tukar Guling PO' - - name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') - date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - operations = fields.Many2one('stock.picking', 'Nomor BU/Out', - domain=[('picking_type_id.code', '=', 'outgoing')]) - ba_num = fields.Text('Nomor BA') - notes = fields.Text('Notes') - state = fields.Selection(string='Status', selection=[ - ('draft', 'Draft'), - ('approval_purchase', ' Approval Purchase'), - ('approval_logistic', 'Approval Logistic'), - ('approval_finance', 'Approval Finance'), - ('done', 'Done'), - ('cancel', 'Canceled') - ], default='draft', tracking=True, required=True) - - line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines') - tukar_guling_po_id = fields.Many2one('tukar.guling.po', 'Tukar Guling PO') - - return_type = fields.Selection([ - ('tukar_guling', 'Tukar Guling'), - ('revisi_po', 'Revisi PO'), - ('debit_memo', 'Debit Memo'), - ], string='Return Type', required=True) - - @api.constrains('return_type', 'operations') - def _check_required_bu_fields(self): - for record in self: - if record.return_type in ['tukar_guling', 'revisi_po', 'debit_memo'] and not record.operations: - raise ValidationError("BU/Out harus diisi!") - - @api.constrains('line_ids', 'state') - def _check_product_lines(self): - """Constraint: Product lines harus ada jika state bukan draft""" - for record in self: - if record.state in ('approval_purchase', 'approval_logistic', 'approval_finance', 'done') and not record.line_ids: - raise ValidationError("Product lines harus diisi sebelum submit atau approve!") - - def _validate_product_lines(self): - """Helper method untuk validasi product lines""" - self.ensure_one() - - # Check ada product lines - if not self.line_ids: - raise UserError("Belum ada product lines yang ditambahkan!") - - # Check product sudah diisi - empty_lines = self.line_ids.filtered(lambda line: not line.product_id) - if empty_lines: - raise UserError("Ada product lines yang belum diisi productnya!") - - # Check quantity > 0 - zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0) - if zero_qty_lines: - raise UserError("Quantity product tidak boleh kosong atau 0!") - return True - - @api.model - def create(self, vals): - if not vals.get('name') or vals['name'] in ('New', False): - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' - return super(TukarGulingPO, self).create(vals) - def copy(self, default=None): - if default is None: - default = {} - - # Generate sequence satu-satunya di sini - default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'New' - default['state'] = 'draft' - default['date'] = fields.Datetime.now() - - new_record = super(TukarGulingPO, self).copy(default) - - # Re-sequence lines - if new_record.line_ids: - for i, line in enumerate(new_record.line_ids): - line.sequence = (i + 1) * 10 - - return new_record - - def action_draft(self): - """Reset to draft state""" - for record in self: - if record.state == 'cancel': - record.write({'state': 'draft'}) - else: - raise UserError("Hanya record yang di-cancel yang bisa dikembalikan ke draft") - - def action_submit(self): - self.ensure_one() - - if self.state != 'draft': - raise UserError("Submit hanya bisa dilakukan dari Draft.") - self.state = 'approval_purchase' - - def action_approve(self): - self.ensure_one() - - if not self.operations: - raise UserError("BU/Out harus diisi!") - - if not self.return_type: - raise UserError("Return Type harus diisi!") - - if self.state == 'approval_purchase': - if not self.env.user.has_group('indoteknik_custom.group_role_purchasing'): - raise UserError("Hanya Purchasing yang boleh approve tahap ini.") - self.state = 'approval_logistic' - - elif self.state == 'approval_logistic': - if not self.env.user.has_group('indoteknik_custom.group_role_logistic'): - raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") - self.state = 'approval_finance' - - elif self.state == 'approval_finance': - if not self.env.user.has_group('indoteknik_custom.group_role_fat'): - raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") - self.state = 'done' - - else: - raise UserError("Status ini tidak bisa di-approve.") - - def action_cancel(self): - self.ensure_one() - # if self.state == 'done': - # raise UserError("Tidak bisa cancel jika sudah done") - self.state = 'cancel' - class TukarGulingLine(models.Model): _name = 'tukar.guling.line' _description = 'Tukar Guling Line' @@ -520,23 +389,6 @@ class TukarGulingLine(models.Model): self.name = self.product_id.display_name self.product_uom = self.product_id.uom_id -class TukarGulingLinePO(models.Model): - _name = 'tukar.guling.line.po' - _description = 'Tukar Guling Line (PO)' - _order = 'sequence, id' - - tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', required=True, ondelete='cascade') - sequence = fields.Integer('Sequence', default=10, copy=False) - product_id = fields.Many2one('product.product', string='Product', required=True) - product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0) - product_uom = fields.Many2one('uom.uom', string='Unit of Measure') - name = fields.Text('Description') - - @api.onchange('product_id') - def _onchange_product_id(self): - if self.product_id: - self.name = self.product_id.display_name - self.product_uom = self.product_id.uom_id class StockPicking(models.Model): _inherit = 'stock.picking' diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 63e7b53a..a6d0acaa 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -183,6 +183,4 @@ access_production_purchase_match,access.production.purchase.match,model_producti access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 -access_tukar_guling_po_all_users,tukar.guling.po.all.users,model_tukar_guling_po,base.group_user,1,1,1,1 access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_guling_line,base.group_user,1,1,1,1 -access_tukar_guling_line_po_all_users,tukar.guling.line.po.all.users,model_tukar_guling_line_po,base.group_user,1,1,1,1 -- cgit v1.2.3 From 32724232b991afaff527cf5ff9e58a2cad7ea824 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 13:05:31 +0700 Subject: Fix sequence --- indoteknik_custom/models/stock_picking_return.py | 436 +++-------------------- indoteknik_custom/models/tukar_guling.py | 37 +- 2 files changed, 75 insertions(+), 398 deletions(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 341383e8..e50446d3 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -1,402 +1,54 @@ -from odoo import models, fields, api -from odoo.exceptions import UserError, ValidationError -import logging - -_logger = logging.getLogger(__name__) - - -class TukarGuling(models.Model): - _name = 'tukar.guling' - _description = 'Tukar Guling' - _order = 'date desc, id desc' - _rec_name = 'name' - - origin = fields.Char(string='Origin SO') - real_shipping_id = fields.Many2one('res.partner', string='Shipping Address') - picking_ids = fields.One2many('stock.picking', 'tukar_guling_id', string='Transfers') - - name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') - date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.code', '=', 'outgoing')], - help='Nomor BU/Out atau BU/Pick') - ba_num = fields.Text('Nomor BA') - notes = fields.Text('Notes') - return_type = fields.Selection(String='Return Type', selection=[ - ('tukar_guling', 'Tukar Guling'), - ('revisi_so', 'Revisi SO')]) - - state = fields.Selection(string='Status', selection=[ - ('draft', 'Draft'), - ('approval_sales', 'Approval Sales'), - ('approval_logistic', 'Approval Logistic'), - ('approval_finance', 'Approval Finance'), - ('done', 'Done'), - ('cancel', 'Canceled') - ], default='draft', tracking=True, required=True) - - line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') - - @api.onchange('operations') - def _onchange_operations(self): - """Auto-populate lines ketika operations dipilih""" - if self.operations: - # Clear existing lines - self.line_ids = [(5, 0, 0)] - - # Set origin dari operations - if self.operations.origin: - self.origin = self.operations.origin - - # Set shipping address - if self.operations.real_shipping_id: - self.real_shipping_id = self.operations.real_shipping_id.id - - # Auto-populate lines dari move_ids operations - lines_data = [] - sequence = 10 - - # Ambil moves yang sudah done/delivered - moves_to_check = self.operations.move_ids_without_package.filtered( - lambda m: m.state == 'done' and m.quantity_done > 0 - ) - - _logger.info(f"BU/OUT: {self.operations.name}, State: {self.operations.state}") - _logger.info(f"Total moves found: {len(moves_to_check)}") - - for move in moves_to_check: - _logger.info( - f"Move: {move.name}, Product: {move.product_id.name if move.product_id else 'No Product'}, " - f"Qty Done: {move.quantity_done}, State: {move.state}" - ) - - # Hanya ambil yang sudah done dengan quantity_done > 0 - if move.product_id and move.quantity_done > 0: - lines_data.append((0, 0, { - 'sequence': sequence, - 'product_id': move.product_id.id, - 'product_uom_qty': move.quantity_done, # Gunakan quantity_done - 'product_uom': move.product_uom.id, - 'name': move.name or move.product_id.display_name, - })) - sequence += 10 - - if lines_data: - self.line_ids = lines_data - _logger.info(f"Created {len(lines_data)} lines") - else: - _logger.info("No lines created - no valid moves found") - else: - # Clear lines jika operations dikosongkan - self.line_ids = [(5, 0, 0)] - self.origin = False - self.real_shipping_id = False - - def _create_pickings(self): - """Improved picking creation with proper move handling""" - if not self.operations: - raise UserError("BU/Out harus diisi terlebih dahulu.") - - origin_so = self.operations.origin - if not origin_so: - raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - - # Cari BU/PICK dari SO yang sama - pick_picking = self.env['stock.picking'].search([ - ('origin', '=', origin_so), - ('picking_type_id.code', '=', 'internal') - ], limit=1) - - if not pick_picking: - raise UserError(f"BU/PICK dengan origin {origin_so} tidak ditemukan.") - - # Ambil group_id dari operations - group_id = self.operations.group_id.id if self.operations.group_id else False - - Picking = self.env['stock.picking'] - StockMove = self.env['stock.move'] - - # Cari picking types - srt_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'SRT') - ], limit=1) - - ort_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'ORT') - ], limit=1) - - if not srt_type or not ort_type: - raise UserError("Picking type SRT atau ORT tidak ditemukan!") - - # Lokasi - location_dest_id = srt_type.default_location_dest_id.id - location_dest_id_ort = ort_type.default_location_dest_id.id - location_customer = self.operations.location_dest_id - - # 1. Create BU/SRT: return dari customer ke gudang - srt_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': srt_type.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'origin': f"Retur {self.operations.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - }) - - # Create moves untuk SRT - srt_moves = [] - for line in self.line_ids: - move_vals = { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'picking_id': srt_picking.id, - 'group_id': group_id, - 'state': 'draft', - } - move = StockMove.create(move_vals) - srt_moves.append(move) - - # Confirm SRT picking - srt_picking.action_confirm() - - # 2. Create BU/ORT: return dari gudang ke supplier/vendor - ort_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': ort_type.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'origin': f"Retur {pick_picking.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - }) - - # Create moves untuk ORT - ort_moves = [] - for line in self.line_ids: - move_vals = { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'picking_id': ort_picking.id, - 'group_id': group_id, - 'state': 'draft', - } - move = StockMove.create(move_vals) - ort_moves.append(move) - - # Confirm ORT picking - ort_picking.action_confirm() - ort_picking.action_assign() - - # Log creation - _logger.info(f"Created SRT picking: {srt_picking.name} with {len(srt_moves)} moves") - _logger.info(f"Created ORT picking: {ort_picking.name} with {len(ort_moves)} moves") - - return { - 'srt_picking': srt_picking, - 'ort_picking': ort_picking - } - - def action_approve(self): - self.ensure_one() - - if not self.operations: - raise UserError("BU/Out harus diisi!") - - if not self.return_type: - raise UserError("Return Type harus diisi!") - - # Validasi product lines - self._validate_product_lines() - - # Cek hak akses berdasarkan state - if self.state == 'approval_sales': - if not self.env.user.has_group('indoteknik_custom.group_role_sales'): - raise UserError("Hanya Sales Manager yang boleh approve tahap ini.") - self.state = 'approval_logistic' - - elif self.state == 'approval_logistic': - if not self.env.user.has_group('indoteknik_custom.group_role_logistic'): - raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") - self.state = 'approval_finance' - - elif self.state == 'approval_finance': - if not self.env.user.has_group('indoteknik_custom.group_role_fat'): - raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") - self.state = 'done' - # Create pickings saat final approval - result = self._create_pickings() - if result: - return { - 'type': 'ir.actions.client', - 'tag': 'display_notification', - 'params': { - 'title': 'Success', - 'message': f"Berhasil membuat BU/SRT: {result['srt_picking'].name} dan BU/ORT: {result['ort_picking'].name}", - 'type': 'success', - 'sticky': False, - } - } - else: - raise UserError("Status ini tidak bisa di-approve.") - - # ... (rest of the methods remain the same) - @api.constrains('return_type', 'operations') - def _check_required_bu_fields(self): - for record in self: - if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations: - raise ValidationError("BU/Out harus diisi!") - - @api.constrains('line_ids', 'state') - def _check_product_lines(self): - """Constraint: Product lines harus ada jika state bukan draft""" - for record in self: - if record.state in ('approval_sales', 'approval_logistic', 'approval_finance', - 'done') and not record.line_ids: - raise ValidationError("Product lines harus diisi sebelum submit atau approve!") - - def _validate_product_lines(self): - """Helper method untuk validasi product lines""" - self.ensure_one() - - # Check ada product lines - if not self.line_ids: - raise UserError("Belum ada product lines yang ditambahkan!") - - # Check product sudah diisi - empty_lines = self.line_ids.filtered(lambda line: not line.product_id) - if empty_lines: - raise UserError("Ada product lines yang belum diisi productnya!") - - # Check quantity > 0 - zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0) - if zero_qty_lines: - raise UserError("Quantity product tidak boleh kosong atau 0!") - - return True +from odoo import _, api, fields, models +from odoo.exceptions import UserError +from odoo.tools.float_utils import float_round + + +class ReturnPicking(models.TransientModel): + _inherit = 'stock.return.picking' + + # @api.model + # def default_get(self, fields): + # res = super(ReturnPicking, self).default_get(fields) + # + # stock_picking = self.env['stock.picking'].search([ + # ('id', '=', res['picking_id']), + # ]) + # + # # sale_id = stock_picking.group_id.sale_id + # if not stock_picking.approval_return_status == 'approved': + # raise UserError('Harus Approval Accounting AR untuk melakukan Retur') + # + # # purchase = self.env['purchase.order'].search([ + # # ('name', '=', stock_picking.group_id.name), + # # ]) + # # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: + # # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') + # + # return res @api.model - def create(self, vals): - if not vals.get('name') or vals['name'] == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' - # Auto-fill origin from operations - if not vals.get('origin') and vals.get('operations'): - picking = self.env['stock.picking'].browse(vals['operations']) - if picking.origin: - vals['origin'] = picking.origin - return super(TukarGuling, self).create(vals) - - def action_submit(self): - self.ensure_one() - if self.state != 'draft': - raise UserError("Submit hanya bisa dilakukan dari Draft.") - - # Validasi sebelum submit - self._validate_product_lines() - - self.state = 'approval_sales' - - def action_cancel(self): - self.ensure_one() - # Cek apakah ada picking yang sudah dibuat - if self.picking_ids: - done_pickings = self.picking_ids.filtered(lambda p: p.state == 'done') - if done_pickings: - raise UserError("Tidak bisa cancel karena ada transfer yang sudah selesai!") - - self.state = 'cancel' - - def action_view_picking(self): - self.ensure_one() - action = self.env.ref('stock.action_picking_tree_all').read()[0] - pickings = self.picking_ids - if len(pickings) > 1: - action['domain'] = [('id', 'in', pickings.ids)] - elif pickings: - action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] - action['res_id'] = pickings.id - else: - raise UserError("Belum ada transfer yang dibuat!") - return action - - -class TukarGulingLine(models.Model): - _name = 'tukar.guling.line' - _description = 'Tukar Guling Line' - _order = 'sequence, id' - - sequence = fields.Integer('Sequence', default=10, copy=False) - tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling', required=True, ondelete='cascade') - product_id = fields.Many2one('product.product', string='Product', required=True) - product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0) - product_uom = fields.Many2one('uom.uom', string='Unit of Measure') - name = fields.Text('Description') - - @api.model_create_multi - def create(self, vals_list): - """Override create to auto-assign sequence""" - for vals in vals_list: - if 'sequence' not in vals or vals.get('sequence', 0) <= 0: - # Get max sequence untuk tukar_guling yang sama - tukar_guling_id = vals.get('tukar_guling_id') - if tukar_guling_id: - max_seq = self.search([ - ('tukar_guling_id', '=', tukar_guling_id) - ], order='sequence desc', limit=1) - vals['sequence'] = (max_seq.sequence or 0) + 10 - else: - vals['sequence'] = 10 - return super(TukarGulingLine, self).create(vals_list) - - @api.onchange('product_id') - def _onchange_product_id(self): - if self.product_id: - self.name = self.product_id.display_name - self.product_uom = self.product_id.uom_id - + def default_get(self, fields): + res = super(ReturnPicking, self).default_get(fields) -class StockPicking(models.Model): - _inherit = 'stock.picking' + picking_id = res.get('picking_id') + if not picking_id: + return res - tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Reference') + stock_picking = self.env['stock.picking'].browse(picking_id) - def action_create_tukar_guling(self): - """Action untuk membuat Tukar Guling dari picking""" - self.ensure_one() + if not stock_picking.approval_return_status == 'approved': + raise UserError('Harus Approval Accounting AR untuk melakukan Retur') - # Cek apakah picking sudah done - if self.state != 'done': - raise UserError("Hanya bisa membuat Tukar Guling dari delivery yang sudah selesai!") + return res - # Cek apakah sudah ada tukar guling untuk picking ini - existing_tukar_guling = self.env['tukar.guling'].search([ - ('operations', '=', self.id) - ]) - if existing_tukar_guling: - raise UserError(f"Sudah ada Tukar Guling untuk delivery ini: {existing_tukar_guling.name}") +class ReturnPickingLine(models.TransientModel): + _inherit = 'stock.return.picking.line' - # Create tukar guling baru - tukar_guling = self.env['tukar.guling'].create({ - 'operations': self.id, - 'return_type': 'tukar_guling', # default value - }) + @api.onchange('quantity') + def _onchange_quantity(self): + for rec in self: + qty_done = rec.move_id.quantity_done - return { - 'type': 'ir.actions.act_window', - 'name': 'Tukar Guling', - 'res_model': 'tukar.guling', - 'res_id': tukar_guling.id, - 'view_mode': 'form', - 'target': 'current', - } \ No newline at end of file + if rec.quantity > qty_done: + raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 08b862a7..7bcf5e80 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -160,23 +160,34 @@ class TukarGuling(models.Model): @api.model def create(self, vals): + # Generate sequence number if not vals.get('name') or vals['name'] == 'New': - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'New' + # Pastikan sequence code 'tukar.guling' ada + sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling')], limit=1) + if sequence: + vals['name'] = sequence.next_by_id() + else: + # Fallback jika sequence belum dibuat + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG-00001' + # Auto-fill origin from operations if not vals.get('origin') and vals.get('operations'): picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: vals['origin'] = picking.origin + return super(TukarGuling, self).create(vals) def copy(self, default=None): if default is None: default = {} - if 'name' not in default: - default.update({ - 'name': self.env['ir.sequence'].next_by_code(self._name) or 'New', - }) + # Generate new sequence untuk duplicate + sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling')], limit=1) + if sequence: + default['name'] = sequence.next_by_id() + else: + default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG-COPY' default.update({ 'state': 'draft', @@ -191,7 +202,6 @@ class TukarGuling(models.Model): line.sequence = (i + 1) * 10 return new_record - def write(self, vals): if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) @@ -352,6 +362,21 @@ class TukarGuling(models.Model): }) for line in self.line_ids ] }) + for line in self.line_ids: + move = ort_picking.move_ids_without_package.filtered( + lambda m: m.product_id == line.product_id + )[:1] + + if move: + self.env['stock.move.line'].create({ + 'move_id': move.id, + 'picking_id': ort_picking.id, + 'product_id': line.product_id.id, + 'product_uom_id': line.product_uom.id, + 'qty_done': line.product_uom_qty, # Ambil dari return.picking.line.quantity + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + }) ort_picking.action_confirm() ort_picking.action_assign() -- cgit v1.2.3 From 865900d364f3a97586c19290ae458e655ea7f592 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 13:11:04 +0700 Subject: Fix sequence --- indoteknik_custom/views/ir_sequence.xml | 9 +++++++++ indoteknik_custom/views/tukar_guling.xml | 10 ---------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 97bf40bb..bfee0fd4 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -160,5 +160,14 @@ 1 1 + + Pengajuan Return SO + tukar.guling + PTG/ + 5 + 1 + 1 + + \ No newline at end of file diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index b257000f..765c913d 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -16,16 +16,6 @@ sequence="3" action="action_pengajuan_tukar_guling" /> - - - Pengajuan Return SO - tukar.guling - PTG/ - 5 - 1 - 1 - - pengajuan.tukar.guling.tree -- cgit v1.2.3 From bb8d3f325228936acaad5f8cbdac555fc289b854 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 14:49:03 +0700 Subject: push --- indoteknik_custom/models/tukar_guling.py | 5 +++-- indoteknik_custom/views/ir_sequence.xml | 11 +++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 7bcf5e80..fe0d6ab0 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -202,6 +202,7 @@ class TukarGuling(models.Model): line.sequence = (i + 1) * 10 return new_record + def write(self, vals): if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) @@ -279,7 +280,7 @@ class TukarGuling(models.Model): if not origin_so: raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - # Cari DO dari SO + # Cari DO (deliv order (bu/out)) dari SO get_group_id = self.env['stock.picking'].search([ ('origin', '=', origin_so), ], limit=1) @@ -373,7 +374,7 @@ class TukarGuling(models.Model): 'picking_id': ort_picking.id, 'product_id': line.product_id.id, 'product_uom_id': line.product_uom.id, - 'qty_done': line.product_uom_qty, # Ambil dari return.picking.line.quantity + 'qty_done': line.product_uom_qty, 'location_id': location_customer.id, 'location_dest_id': location_dest_id, }) diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index bfee0fd4..934e9100 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -161,13 +161,20 @@ 1 - Pengajuan Return SO + Pengajuan Retur Tukar Guling SO tukar.guling PTG/ 5 1 1 - + + Pengajuan Retur Revisi SO (PRS) + tukar.guling.prs + PRS- + 5 + 1 + 1 + \ No newline at end of file -- cgit v1.2.3 From 1c637ef3fa5f1a0cb39ba0b32353320485622901 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 19 Jun 2025 15:00:29 +0700 Subject: Don, create 4 document --- indoteknik_custom/models/tukar_guling.py | 175 ++++++++++++------------------- 1 file changed, 69 insertions(+), 106 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index fe0d6ab0..6c5d74ec 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -273,113 +273,76 @@ class TukarGuling(models.Model): self.state = 'cancel' def _create_pickings(self): - if not self.operations: - raise UserError("BU/Out harus diisi terlebih dahulu.") - - origin_so = self.operations.origin - if not origin_so: - raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - - # Cari DO (deliv order (bu/out)) dari SO - get_group_id = self.env['stock.picking'].search([ - ('origin', '=', origin_so), - ], limit=1) - - if not get_group_id: - raise UserError(f"Delivery Order dari SO {origin_so} tidak ditemukan.") - - group_id = get_group_id.group_id.id if get_group_id.group_id else False - Picking = self.env['stock.picking'] - srt_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'SRT') - ], limit=1) - - ort_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'ORT') - ], limit=1) - - # Lokasi - location_dest_id = srt_type.default_location_dest_id.id - location_dest_id_ort = ort_type.default_location_dest_id.id - location_customer = self.operations.location_dest_id - - # 1. BU/SRT: retur dari operations - srt_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': srt_type.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'origin': f"Retur {self.operations.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - 'move_ids_without_package': [ - (0, 0, { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'group_id': group_id, - }) for line in self.line_ids - ] - }) - srt_picking.action_confirm() - - # 2. Cari BU/PICK dari SO yang sama - origin_so = self.operations.origin - if not origin_so: - raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - - pick = Picking.search([ - ('origin', '=', origin_so), - ('picking_type_id.code', '=', 'internal') - ], limit=1) - - if not pick: - raise UserError(f"BU/PICK dengan origin {origin_so} tidak ditemukan.") - - # 3. BU/ORT: retur dari BU/PICK - ort_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': ort_type.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'origin': f"Retur {pick.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - 'move_ids_without_package': [ - (0, 0, { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'group_id': group_id, - }) for line in self.line_ids - ] - }) - for line in self.line_ids: - move = ort_picking.move_ids_without_package.filtered( - lambda m: m.product_id == line.product_id - )[:1] - - if move: - self.env['stock.move.line'].create({ - 'move_id': move.id, - 'picking_id': ort_picking.id, - 'product_id': line.product_id.id, - 'product_uom_id': line.product_uom.id, - 'qty_done': line.product_uom_qty, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - }) - ort_picking.action_confirm() - ort_picking.action_assign() + group_id = self.env['procurement.group'].create({'name': self.name}).id + + def create_moves(picking_type, origin_suffix, location_id, location_dest_id): + return Picking.create({ + 'partner_id': self.operations.partner_id.id, + 'real_shipping_id': self.operations.real_shipping_id.id, + 'picking_type_id': picking_type.id, + 'location_id': location_id, + 'location_dest_id': location_dest_id, + 'origin': f"Tukar Guling {self.name} - {origin_suffix}", + 'tukar_guling_id': self.id, + 'group_id': group_id, + 'move_ids_without_package': [ + (0, 0, { + 'name': line.name or line.product_id.name, + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'location_id': location_id, + 'location_dest_id': location_dest_id, + 'group_id': group_id, + }) for line in self.line_ids + ] + }) + + # BU/SRT + srt_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'SRT')], limit=1) + if not srt_type: + raise UserError("Picking Type dengan sequence_code 'SRT' tidak ditemukan.") + bu_srt = create_moves( + srt_type, 'SRT', + self.operations.location_dest_id.id, + srt_type.default_location_dest_id.id + ) + bu_srt.action_confirm() + + # BU/ORT + ort_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'ORT')], limit=1) + if not ort_type: + raise UserError("Picking Type dengan sequence_code 'ORT' tidak ditemukan.") + bu_ort = create_moves( + ort_type, 'ORT', + ort_type.default_location_src_id.id, + ort_type.default_location_dest_id.id + ) + bu_ort.action_confirm() + + # Jika return_type tukar_guling → lanjut buat PICK dan OUT + if self.return_type == 'tukar_guling': + pick_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'PICK')], limit=1) + if not pick_type: + raise UserError("Picking Type dengan sequence_code 'PICK' tidak ditemukan.") + bu_pick = create_moves( + pick_type, 'PICK', + pick_type.default_location_src_id.id, + pick_type.default_location_dest_id.id + ) + bu_pick.action_confirm() + + out_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'OUT')], limit=1) + if not out_type: + raise UserError("Picking Type dengan sequence_code 'OUT' tidak ditemukan.") + bu_out = create_moves( + out_type, 'OUT', + out_type.default_location_src_id.id, + self.operations.location_dest_id.id + ) + bu_out.action_confirm() + class TukarGulingLine(models.Model): _name = 'tukar.guling.line' -- cgit v1.2.3 From f1bd55eed208cc351e7486c556cbdf7b6c94a341 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 20 Jun 2025 08:28:02 +0700 Subject: redirect to pengajuan retur --- indoteknik_custom/__manifest__.py | 1 + indoteknik_custom/models/stock_picking_return.py | 113 ++++++++++++++------- .../views/tukar_guling_return_views.xml | 20 ++++ 3 files changed, 98 insertions(+), 36 deletions(-) create mode 100644 indoteknik_custom/views/tukar_guling_return_views.xml diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index f58acbfb..7a1cf2a3 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -169,6 +169,7 @@ 'views/stock_inventory.xml', 'views/sale_order_delay.xml', 'views/tukar_guling.xml', + 'views/tukar_guling_return_views.xml' # 'views/tukar_guling_po.xml', ], 'demo': [], diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index e50446d3..d6225f1a 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -1,46 +1,87 @@ -from odoo import _, api, fields, models from odoo.exceptions import UserError from odoo.tools.float_utils import float_round +from odoo import models, fields, api, _ +from odoo.exceptions import UserError + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError -class ReturnPicking(models.TransientModel): +class StockReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' - # @api.model - # def default_get(self, fields): - # res = super(ReturnPicking, self).default_get(fields) - # - # stock_picking = self.env['stock.picking'].search([ - # ('id', '=', res['picking_id']), - # ]) - # - # # sale_id = stock_picking.group_id.sale_id - # if not stock_picking.approval_return_status == 'approved': - # raise UserError('Harus Approval Accounting AR untuk melakukan Retur') - # - # # purchase = self.env['purchase.order'].search([ - # # ('name', '=', stock_picking.group_id.name), - # # ]) - # # if not stock_picking.approval_return_status == 'approved' and purchase.invoice_ids: - # # raise UserError('Harus Approval Accounting AP untuk melakukan Retur') - # - # return res - - @api.model - def default_get(self, fields): - res = super(ReturnPicking, self).default_get(fields) - - picking_id = res.get('picking_id') - if not picking_id: - return res - - stock_picking = self.env['stock.picking'].browse(picking_id) - - if not stock_picking.approval_return_status == 'approved': - raise UserError('Harus Approval Accounting AR untuk melakukan Retur') - - return res + return_type = fields.Selection([ + ('revisi_so', 'Revisi SO'), + ('tukar_guling', 'Tukar Guling') + ], string='Jenis Retur', default='revisi_so') + + def create_returns(self): + """Override method to handle Tukar Guling redirection""" + if self.return_type == 'tukar_guling': + return self._redirect_to_tukar_guling() + return super(StockReturnPicking, self).create_returns() + + def _redirect_to_tukar_guling(self): + """Redirect to Tukar Guling form with pre-filled data""" + self.ensure_one() + picking = self.picking_id + + # Gunakan pendekatan yang lebih kompatibel untuk Odoo 14 + # Cari hanya baris yang masih ada dan memiliki quantity > 0 + valid_lines = [] + for line in self.product_return_moves: + # Periksa apakah baris masih ada di database atau baru dibuat + if line.id: + # Untuk baris yang sudah ada di database, pastikan masih ada + if not self.env['stock.return.picking.line'].browse(line.id).exists(): + continue + # Baris baru yang belum disimpan tidak memiliki id + + if line.quantity > 0: + valid_lines.append(line) + + if not valid_lines: + raise UserError(_("Please specify at least one product to return with positive quantity.")) + + # Prepare context for Tukar Guling form + context = { + 'default_operations': picking.id, + 'default_partner_id': picking.partner_id.id, + 'default_origin': picking.origin or picking.name, + 'default_return_type': 'tukar_guling', + 'default_date': fields.Datetime.now(), + 'default_state': 'draft', + 'default_ba_num': _('Retur dari %s') % picking.name, + } + + # Prepare product lines + line_vals = [] + for line in valid_lines: + line_vals.append((0, 0, { + 'product_id': line.product_id.id, + 'product_uom_qty': line.quantity, + 'product_uom': line.product_id.uom_id.id, + 'name': line.product_id.display_name, + })) + + context['default_line_ids'] = line_vals + + # Set SO if available + if picking.sale_id: + context['default_so_id'] = picking.sale_id.id + + # Set shipping address + if picking.partner_id: + context['default_real_shipping_id'] = picking.partner_id.id + return { + 'name': _('Tukar Guling'), + 'type': 'ir.actions.act_window', + 'res_model': 'tukar.guling', + 'view_mode': 'form', + 'target': 'current', + 'context': context, + } class ReturnPickingLine(models.TransientModel): _inherit = 'stock.return.picking.line' diff --git a/indoteknik_custom/views/tukar_guling_return_views.xml b/indoteknik_custom/views/tukar_guling_return_views.xml new file mode 100644 index 00000000..9312005a --- /dev/null +++ b/indoteknik_custom/views/tukar_guling_return_views.xml @@ -0,0 +1,20 @@ + + + + + stock.return.picking.form.inherit.tukar.guling + stock.return.picking + + + + + +
+
+ +
+
+
+
+
+
\ No newline at end of file -- cgit v1.2.3 From bce4d940dc90bf50e045a8fde3fd1c7bb53e8562 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 20 Jun 2025 09:21:19 +0700 Subject: revert --- indoteknik_custom/models/tukar_guling.py | 176 +++++++++++++++++++------------ indoteknik_custom/views/ir_sequence.xml | 11 +- 2 files changed, 108 insertions(+), 79 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 6c5d74ec..7bcf5e80 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -202,7 +202,6 @@ class TukarGuling(models.Model): line.sequence = (i + 1) * 10 return new_record - def write(self, vals): if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) @@ -273,76 +272,113 @@ class TukarGuling(models.Model): self.state = 'cancel' def _create_pickings(self): - Picking = self.env['stock.picking'] - group_id = self.env['procurement.group'].create({'name': self.name}).id - - def create_moves(picking_type, origin_suffix, location_id, location_dest_id): - return Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': picking_type.id, - 'location_id': location_id, - 'location_dest_id': location_dest_id, - 'origin': f"Tukar Guling {self.name} - {origin_suffix}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - 'move_ids_without_package': [ - (0, 0, { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_id, - 'location_dest_id': location_dest_id, - 'group_id': group_id, - }) for line in self.line_ids - ] - }) - - # BU/SRT - srt_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'SRT')], limit=1) - if not srt_type: - raise UserError("Picking Type dengan sequence_code 'SRT' tidak ditemukan.") - bu_srt = create_moves( - srt_type, 'SRT', - self.operations.location_dest_id.id, - srt_type.default_location_dest_id.id - ) - bu_srt.action_confirm() - - # BU/ORT - ort_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'ORT')], limit=1) - if not ort_type: - raise UserError("Picking Type dengan sequence_code 'ORT' tidak ditemukan.") - bu_ort = create_moves( - ort_type, 'ORT', - ort_type.default_location_src_id.id, - ort_type.default_location_dest_id.id - ) - bu_ort.action_confirm() - - # Jika return_type tukar_guling → lanjut buat PICK dan OUT - if self.return_type == 'tukar_guling': - pick_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'PICK')], limit=1) - if not pick_type: - raise UserError("Picking Type dengan sequence_code 'PICK' tidak ditemukan.") - bu_pick = create_moves( - pick_type, 'PICK', - pick_type.default_location_src_id.id, - pick_type.default_location_dest_id.id - ) - bu_pick.action_confirm() - - out_type = self.env['stock.picking.type'].search([('sequence_code', '=', 'OUT')], limit=1) - if not out_type: - raise UserError("Picking Type dengan sequence_code 'OUT' tidak ditemukan.") - bu_out = create_moves( - out_type, 'OUT', - out_type.default_location_src_id.id, - self.operations.location_dest_id.id - ) - bu_out.action_confirm() + if not self.operations: + raise UserError("BU/Out harus diisi terlebih dahulu.") + + origin_so = self.operations.origin + if not origin_so: + raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") + + # Cari DO dari SO + get_group_id = self.env['stock.picking'].search([ + ('origin', '=', origin_so), + ], limit=1) + if not get_group_id: + raise UserError(f"Delivery Order dari SO {origin_so} tidak ditemukan.") + + group_id = get_group_id.group_id.id if get_group_id.group_id else False + + Picking = self.env['stock.picking'] + srt_type = self.env['stock.picking.type'].search([ + ('sequence_code', '=', 'SRT') + ], limit=1) + + ort_type = self.env['stock.picking.type'].search([ + ('sequence_code', '=', 'ORT') + ], limit=1) + + # Lokasi + location_dest_id = srt_type.default_location_dest_id.id + location_dest_id_ort = ort_type.default_location_dest_id.id + location_customer = self.operations.location_dest_id + + # 1. BU/SRT: retur dari operations + srt_picking = Picking.create({ + 'partner_id': self.operations.partner_id.id, + 'real_shipping_id': self.operations.real_shipping_id.id, + 'picking_type_id': srt_type.id, + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + 'origin': f"Retur {self.operations.name}", + 'tukar_guling_id': self.id, + 'group_id': group_id, + 'move_ids_without_package': [ + (0, 0, { + 'name': line.name or line.product_id.name, + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + 'group_id': group_id, + }) for line in self.line_ids + ] + }) + srt_picking.action_confirm() + + # 2. Cari BU/PICK dari SO yang sama + origin_so = self.operations.origin + if not origin_so: + raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") + + pick = Picking.search([ + ('origin', '=', origin_so), + ('picking_type_id.code', '=', 'internal') + ], limit=1) + + if not pick: + raise UserError(f"BU/PICK dengan origin {origin_so} tidak ditemukan.") + + # 3. BU/ORT: retur dari BU/PICK + ort_picking = Picking.create({ + 'partner_id': self.operations.partner_id.id, + 'real_shipping_id': self.operations.real_shipping_id.id, + 'picking_type_id': ort_type.id, + 'location_id': location_dest_id, + 'location_dest_id': location_dest_id_ort, + 'origin': f"Retur {pick.name}", + 'tukar_guling_id': self.id, + 'group_id': group_id, + 'move_ids_without_package': [ + (0, 0, { + 'name': line.name or line.product_id.name, + 'product_id': line.product_id.id, + 'product_uom_qty': line.product_uom_qty, + 'product_uom': line.product_uom.id, + 'location_id': location_dest_id, + 'location_dest_id': location_dest_id_ort, + 'group_id': group_id, + }) for line in self.line_ids + ] + }) + for line in self.line_ids: + move = ort_picking.move_ids_without_package.filtered( + lambda m: m.product_id == line.product_id + )[:1] + + if move: + self.env['stock.move.line'].create({ + 'move_id': move.id, + 'picking_id': ort_picking.id, + 'product_id': line.product_id.id, + 'product_uom_id': line.product_uom.id, + 'qty_done': line.product_uom_qty, # Ambil dari return.picking.line.quantity + 'location_id': location_customer.id, + 'location_dest_id': location_dest_id, + }) + ort_picking.action_confirm() + ort_picking.action_assign() class TukarGulingLine(models.Model): _name = 'tukar.guling.line' diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 934e9100..bfee0fd4 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -161,20 +161,13 @@ 1 - Pengajuan Retur Tukar Guling SO + Pengajuan Return SO tukar.guling PTG/ 5 1 1 + - - Pengajuan Retur Revisi SO (PRS) - tukar.guling.prs - PRS- - 5 - 1 - 1 - \ No newline at end of file -- cgit v1.2.3 From 41b26b7fca60533fe30240d19b972cbe7022f333 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 09:07:05 +0700 Subject: return oke --- indoteknik_custom/models/stock_picking_return.py | 127 ++++++++---- indoteknik_custom/models/tukar_guling.py | 242 ++++++++++++----------- indoteknik_custom/views/tukar_guling.xml | 1 - 3 files changed, 221 insertions(+), 149 deletions(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index d6225f1a..74bf6407 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -1,10 +1,6 @@ from odoo.exceptions import UserError from odoo.tools.float_utils import float_round from odoo import models, fields, api, _ -from odoo.exceptions import UserError - -from odoo import models, fields, api, _ -from odoo.exceptions import UserError class StockReturnPicking(models.TransientModel): @@ -16,8 +12,7 @@ class StockReturnPicking(models.TransientModel): ], string='Jenis Retur', default='revisi_so') def create_returns(self): - """Override method to handle Tukar Guling redirection""" - if self.return_type == 'tukar_guling': + if self._context.get('from_ui', True) and self.return_type == 'tukar_guling': return self._redirect_to_tukar_guling() return super(StockReturnPicking, self).create_returns() @@ -26,53 +21,100 @@ class StockReturnPicking(models.TransientModel): self.ensure_one() picking = self.picking_id - # Gunakan pendekatan yang lebih kompatibel untuk Odoo 14 - # Cari hanya baris yang masih ada dan memiliki quantity > 0 + # Get valid return lines with better error handling valid_lines = [] - for line in self.product_return_moves: - # Periksa apakah baris masih ada di database atau baru dibuat - if line.id: - # Untuk baris yang sudah ada di database, pastikan masih ada - if not self.env['stock.return.picking.line'].browse(line.id).exists(): - continue - # Baris baru yang belum disimpan tidak memiliki id - if line.quantity > 0: - valid_lines.append(line) + try: + # Refresh the recordset to ensure we have the latest data + self.env.cr.execute("SELECT id FROM stock_return_picking_line WHERE wizard_id = %s", (self.id,)) + line_ids = [row[0] for row in self.env.cr.fetchall()] + + if line_ids: + # Use sudo to avoid access rights issues and browse existing lines + existing_lines = self.env['stock.return.picking.line'].sudo().browse(line_ids) + for line in existing_lines: + if line.exists() and line.quantity > 0: + valid_lines.append(line) + + # If no lines found via direct query, try the original approach + if not valid_lines: + for line in self.product_return_moves: + if hasattr(line, 'quantity') and line.quantity > 0: + # Additional check to ensure the line is valid + if line.product_id and line.move_id: + valid_lines.append(line) + + except Exception as e: + # Fallback: create lines based on picking moves + valid_lines = [] + for move in picking.move_ids_without_package: + if move.product_uom_qty > 0 and move.state == 'done': + # Create a temporary line object for data extraction + temp_line = type('TempLine', (), { + 'product_id': move.product_id, + 'quantity': move.quantity_done or move.product_uom_qty, + 'move_id': move + })() + valid_lines.append(temp_line) if not valid_lines: - raise UserError(_("Please specify at least one product to return with positive quantity.")) + raise UserError(_("Tidak ada produk yang bisa diretur. Pastikan ada produk dengan quantity > 0.")) # Prepare context for Tukar Guling form context = { 'default_operations': picking.id, - 'default_partner_id': picking.partner_id.id, - 'default_origin': picking.origin or picking.name, 'default_return_type': 'tukar_guling', 'default_date': fields.Datetime.now(), 'default_state': 'draft', 'default_ba_num': _('Retur dari %s') % picking.name, + 'from_return_picking': True, # Flag to prevent onchange from overriding lines } + # Set origin + if picking.origin: + context['default_origin'] = picking.origin + + # Set partner + if picking.partner_id: + context['default_partner_id'] = picking.partner_id.id + + # Set shipping address + if hasattr(picking, 'real_shipping_id') and picking.real_shipping_id: + context['default_real_shipping_id'] = picking.real_shipping_id.id + elif picking.partner_id: + context['default_real_shipping_id'] = picking.partner_id.id + # Prepare product lines line_vals = [] + sequence = 10 + for line in valid_lines: - line_vals.append((0, 0, { - 'product_id': line.product_id.id, - 'product_uom_qty': line.quantity, - 'product_uom': line.product_id.uom_id.id, - 'name': line.product_id.display_name, - })) + try: + # Get quantity - handle both real lines and temp objects + quantity = getattr(line, 'quantity', 0) + if quantity <= 0: + continue - context['default_line_ids'] = line_vals + # Get product + product = getattr(line, 'product_id', None) + if not product: + continue - # Set SO if available - if picking.sale_id: - context['default_so_id'] = picking.sale_id.id + line_vals.append((0, 0, { + 'sequence': sequence, + 'product_id': product.id, + 'product_uom_qty': quantity, + 'product_uom': product.uom_id.id, + 'name': product.display_name, + })) + sequence += 10 - # Set shipping address - if picking.partner_id: - context['default_real_shipping_id'] = picking.partner_id.id + except Exception as e: + # Skip problematic lines + continue + + if line_vals: + context['default_line_ids'] = line_vals return { 'name': _('Tukar Guling'), @@ -83,13 +125,24 @@ class StockReturnPicking(models.TransientModel): 'context': context, } + class ReturnPickingLine(models.TransientModel): _inherit = 'stock.return.picking.line' @api.onchange('quantity') def _onchange_quantity(self): + """Validate quantity against done quantity""" for rec in self: - qty_done = rec.move_id.quantity_done - - if rec.quantity > qty_done: - raise UserError(f"Quantity yang Anda masukkan tidak boleh melebihi quantity done yaitu: {qty_done}") \ No newline at end of file + if rec.move_id and rec.quantity > 0: + # Get quantity done from the move + qty_done = rec.move_id.quantity_done + + # If quantity_done is 0, use product_uom_qty as fallback + if qty_done == 0: + qty_done = rec.move_id.product_uom_qty + + if rec.quantity > qty_done: + raise UserError( + _("Quantity yang Anda masukkan (%.2f) tidak boleh melebihi quantity done yaitu: %.2f untuk produk %s") + % (rec.quantity, qty_done, rec.product_id.name) + ) \ No newline at end of file diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 7bcf5e80..bdd2a2f5 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -11,6 +11,11 @@ class TukarGuling(models.Model): _order = 'date desc, id desc' _rec_name = 'name' + picking_ids = fields.One2many( + 'stock.picking', + 'tukar_guling_id', + string='Transfers') + origin = fields.Char(string='Origin SO') real_shipping_id = fields.Many2one('res.partner', string='Shipping Address') @@ -44,7 +49,16 @@ class TukarGuling(models.Model): def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" if self.operations: - # Clear existing lines + from_return_picking = self.env.context.get('from_return_picking', False) or \ + self.env.context.get('default_line_ids', False) + + if self.line_ids and from_return_picking: + # Hanya update origin, jangan ubah lines + if self.operations.origin: + self.origin = self.operations.origin + return + + # Clear existing lines hanya jika tidak dari return picking self.line_ids = [(5, 0, 0)] # Set origin dari operations @@ -91,10 +105,14 @@ class TukarGuling(models.Model): else: _logger.info("No lines created - no valid moves found") else: - # Clear lines jika operations dikosongkan - self.line_ids = [(5, 0, 0)] - self.origin = False + # Clear lines jika operations dikosongkan, kecuali dari return picking + from_return_picking = self.env.context.get('from_return_picking', False) or \ + self.env.context.get('default_line_ids', False) + if not from_return_picking: + self.line_ids = [(5, 0, 0)] + + self.origin = False def action_populate_lines(self): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() @@ -272,113 +290,115 @@ class TukarGuling(models.Model): self.state = 'cancel' def _create_pickings(self): - if not self.operations: - raise UserError("BU/Out harus diisi terlebih dahulu.") - - origin_so = self.operations.origin - if not origin_so: - raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - - # Cari DO dari SO - get_group_id = self.env['stock.picking'].search([ - ('origin', '=', origin_so), - ], limit=1) - - if not get_group_id: - raise UserError(f"Delivery Order dari SO {origin_so} tidak ditemukan.") - - group_id = get_group_id.group_id.id if get_group_id.group_id else False - - Picking = self.env['stock.picking'] - srt_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'SRT') - ], limit=1) - - ort_type = self.env['stock.picking.type'].search([ - ('sequence_code', '=', 'ORT') - ], limit=1) - - # Lokasi - location_dest_id = srt_type.default_location_dest_id.id - location_dest_id_ort = ort_type.default_location_dest_id.id - location_customer = self.operations.location_dest_id - - # 1. BU/SRT: retur dari operations - srt_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': srt_type.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'origin': f"Retur {self.operations.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - 'move_ids_without_package': [ - (0, 0, { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, - 'group_id': group_id, - }) for line in self.line_ids - ] - }) - srt_picking.action_confirm() - - # 2. Cari BU/PICK dari SO yang sama - origin_so = self.operations.origin - if not origin_so: - raise UserError("BU/OUT tidak memiliki origin (SO), tidak bisa cari BU/PICK.") - - pick = Picking.search([ - ('origin', '=', origin_so), - ('picking_type_id.code', '=', 'internal') - ], limit=1) - - if not pick: - raise UserError(f"BU/PICK dengan origin {origin_so} tidak ditemukan.") - - # 3. BU/ORT: retur dari BU/PICK - ort_picking = Picking.create({ - 'partner_id': self.operations.partner_id.id, - 'real_shipping_id': self.operations.real_shipping_id.id, - 'picking_type_id': ort_type.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'origin': f"Retur {pick.name}", - 'tukar_guling_id': self.id, - 'group_id': group_id, - 'move_ids_without_package': [ - (0, 0, { - 'name': line.name or line.product_id.name, - 'product_id': line.product_id.id, - 'product_uom_qty': line.product_uom_qty, - 'product_uom': line.product_uom.id, - 'location_id': location_dest_id, - 'location_dest_id': location_dest_id_ort, - 'group_id': group_id, - }) for line in self.line_ids - ] - }) - for line in self.line_ids: - move = ort_picking.move_ids_without_package.filtered( - lambda m: m.product_id == line.product_id - )[:1] - - if move: - self.env['stock.move.line'].create({ - 'move_id': move.id, - 'picking_id': ort_picking.id, - 'product_id': line.product_id.id, - 'product_uom_id': line.product_uom.id, - 'qty_done': line.product_uom_qty, # Ambil dari return.picking.line.quantity - 'location_id': location_customer.id, - 'location_dest_id': location_dest_id, + for record in self: + if not record.operations: + raise UserError("BU/OUT dari field operations tidak ditemukan.") + + operation_picking = record.operations + + # 1. Cari semua picking DONE berdasarkan origin SO + related_pickings = self.env['stock.picking'].search([ + ('origin', '=', record.origin), + ('state', '=', 'done'), + ]) + if not related_pickings: + raise UserError("Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") + + # 2. Filter berdasarkan tipe picking + bu_pick_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 30) # BU/PICK + bu_out_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 29) # BU/OUT + + if not bu_pick_to_return and not bu_out_to_return: + raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") + + created_returns = [] + + # Lokasi default untuk retur + bu_out_type = self.env['stock.picking.type'].browse(73) + bu_stock_type = self.env['stock.picking.type'].browse(74) + + bu_out = bu_out_type.default_location_src_id.id + bu_stock = bu_out_type.default_location_dest_id.id + + if not bu_out or not bu_stock: + raise UserError("salahwoi") + + partner_location = self.env['stock.location'].browse(2) + if not partner_location: + raise UserError("Lokasi partner (real_shipping_id) tidak ditemukan pada BU/OUT utama.") + + # Fungsi membuat retur dari picking tertentu + def _create_return_from_picking(picking): + grup = self.env['stock.picking'].search([('origin', '=', self.operations.origin)]) + # Tentukan lokasi berdasarkan jenis picking + if picking.picking_type_id.id == 29: # BU/OUT → BU/SRT + default_location_id = partner_location.id + default_location_dest_id = bu_out + elif picking.picking_type_id.id == 30: # BU/PICK → BU/ORT + default_location_id = bu_out + default_location_dest_id = bu_stock + else: + return None + + return_context = dict(self.env.context) + return_context.update({ + 'active_id': picking.id, + 'default_location_id': default_location_id, + 'default_location_dest_id': default_location_dest_id, + 'from_ui': False, }) - ort_picking.action_confirm() - ort_picking.action_assign() + + return_wizard = self.env['stock.return.picking'].with_context(return_context).create({ + 'picking_id': picking.id, + 'location_id': default_location_id, + }) + + # Buat return lines + return_lines = [] + for move in picking.move_lines: + if move.quantity_done > 0: + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.quantity_done, + 'move_id': move.id, + })) + if not return_lines: + return None + + return_wizard.product_return_moves = return_lines + + _logger.info("Creating return for picking %s", picking.name) + _logger.info("Default location src: %s", default_location_id) + _logger.info("Default location dest: %s", default_location_dest_id) + _logger.info("Move lines: %s", picking.move_lines) + return_vals = return_wizard.create_returns() + return_id = return_vals.get('res_id') + + if not return_id: + raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) + + picking_obj = self.env['stock.picking'].browse(return_id) + for p in picking_obj: + p.group_id = self.operations.group_id.id + p.origin_tukar_guling_id = record.id + + return picking_obj.name + + # Buat return dari BU/PICK + for picking in bu_pick_to_return: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) + + # Buat return dari BU/OUT + for picking in bu_out_to_return: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) + + if not created_returns: + raise UserError("wkwkwk") + class TukarGulingLine(models.Model): _name = 'tukar.guling.line' @@ -418,4 +438,4 @@ class TukarGulingLine(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' - origin_tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') \ No newline at end of file + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') \ No newline at end of file diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 765c913d..942f085d 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -73,7 +73,6 @@

-

-- cgit v1.2.3 From 2dcea6aa1c0aa57de8bac126f041ca547bc73cad Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 09:18:25 +0700 Subject: fix cannot validate srt --- indoteknik_custom/models/tukar_guling.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index bdd2a2f5..81ef6b1a 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -11,11 +11,6 @@ class TukarGuling(models.Model): _order = 'date desc, id desc' _rec_name = 'name' - picking_ids = fields.One2many( - 'stock.picking', - 'tukar_guling_id', - string='Transfers') - origin = fields.Char(string='Origin SO') real_shipping_id = fields.Many2one('res.partner', string='Shipping Address') @@ -323,7 +318,7 @@ class TukarGuling(models.Model): if not bu_out or not bu_stock: raise UserError("salahwoi") - partner_location = self.env['stock.location'].browse(2) + partner_location = self.env['stock.location'].browse(5) if not partner_location: raise UserError("Lokasi partner (real_shipping_id) tidak ditemukan pada BU/OUT utama.") @@ -380,7 +375,7 @@ class TukarGuling(models.Model): picking_obj = self.env['stock.picking'].browse(return_id) for p in picking_obj: p.group_id = self.operations.group_id.id - p.origin_tukar_guling_id = record.id + p.tukar_guling_id = record.id return picking_obj.name -- cgit v1.2.3 From 87bd344baa9b40cde21256bd1e3680d0d2396e2e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 09:32:38 +0700 Subject: fix cannot validate srt --- indoteknik_custom/models/tukar_guling.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 81ef6b1a..d77ea8d4 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -108,6 +108,7 @@ class TukarGuling(models.Model): self.line_ids = [(5, 0, 0)] self.origin = False + def action_populate_lines(self): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() @@ -215,6 +216,7 @@ class TukarGuling(models.Model): line.sequence = (i + 1) * 10 return new_record + def write(self, vals): if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) @@ -297,7 +299,8 @@ class TukarGuling(models.Model): ('state', '=', 'done'), ]) if not related_pickings: - raise UserError("Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") + raise UserError( + "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") # 2. Filter berdasarkan tipe picking bu_pick_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 30) # BU/PICK @@ -318,9 +321,11 @@ class TukarGuling(models.Model): if not bu_out or not bu_stock: raise UserError("salahwoi") - partner_location = self.env['stock.location'].browse(5) + partner_location = self.env['stock.location'].search( + [('complete_name', 'ilike', 'Partner Locations/Customers'), + ('id', '=', '5')]) if not partner_location: - raise UserError("Lokasi partner (real_shipping_id) tidak ditemukan pada BU/OUT utama.") + raise UserError("Lokasi partner salah atau tidak ditemukan pada BU/OUT.") # Fungsi membuat retur dari picking tertentu def _create_return_from_picking(picking): @@ -433,4 +438,4 @@ class TukarGulingLine(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' - tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') \ No newline at end of file + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') -- cgit v1.2.3 From c1fa178a6afb9ef4a914dc617d2fb69da50af673 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 11:21:52 +0700 Subject: fix from and to return --- indoteknik_custom/models/tukar_guling.py | 56 +++++++++++++++++++------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d77ea8d4..1f5e786f 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -300,11 +300,11 @@ class TukarGuling(models.Model): ]) if not related_pickings: raise UserError( - "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") + "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) # 2. Filter berdasarkan tipe picking - bu_pick_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 30) # BU/PICK - bu_out_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 29) # BU/OUT + bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK + bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") @@ -312,13 +312,18 @@ class TukarGuling(models.Model): created_returns = [] # Lokasi default untuk retur - bu_out_type = self.env['stock.picking.type'].browse(73) - bu_stock_type = self.env['stock.picking.type'].browse(74) + srt_type = self.env['stock.picking.type'].browse(73) + ort_type = self.env['stock.picking.type'].browse(74) - bu_out = bu_out_type.default_location_src_id.id - bu_stock = bu_out_type.default_location_dest_id.id + stock_location = self.env['stock.location'] - if not bu_out or not bu_stock: + srt_src = stock_location.browse(5) + srt_dest = stock_location.browse(60) + + ort_src = stock_location.browse(60) + ort_dest = stock_location.browse(57) + + if not ort_src or not ort_dest or not srt_src or not srt_dest: raise UserError("salahwoi") partner_location = self.env['stock.location'].search( @@ -329,14 +334,19 @@ class TukarGuling(models.Model): # Fungsi membuat retur dari picking tertentu def _create_return_from_picking(picking): - grup = self.env['stock.picking'].search([('origin', '=', self.operations.origin)]) - # Tentukan lokasi berdasarkan jenis picking + grup = self.operations.group_id + + PARTNER_LOCATION_ID = 5 # Partner Locations/Customers + BU_OUTPUT_LOCATION_ID = 60 # BU/Output (from your logs) + BU_STOCK_LOCATION_ID = 57 # BU/Stock (adjust to your actual ID) + + # Determine locations based on picking type if picking.picking_type_id.id == 29: # BU/OUT → BU/SRT - default_location_id = partner_location.id - default_location_dest_id = bu_out + default_location_id = PARTNER_LOCATION_ID # From: Partner Locations + default_location_dest_id = BU_OUTPUT_LOCATION_ID # To: BU/Output elif picking.picking_type_id.id == 30: # BU/PICK → BU/ORT - default_location_id = bu_out - default_location_dest_id = bu_stock + default_location_id = BU_OUTPUT_LOCATION_ID # From: BU/Output + default_location_dest_id = BU_STOCK_LOCATION_ID # To: BU/Stock (FIXED) else: return None @@ -353,7 +363,7 @@ class TukarGuling(models.Model): 'location_id': default_location_id, }) - # Buat return lines + # Create return lines return_lines = [] for move in picking.move_lines: if move.quantity_done > 0: @@ -370,19 +380,21 @@ class TukarGuling(models.Model): _logger.info("Creating return for picking %s", picking.name) _logger.info("Default location src: %s", default_location_id) _logger.info("Default location dest: %s", default_location_dest_id) - _logger.info("Move lines: %s", picking.move_lines) return_vals = return_wizard.create_returns() return_id = return_vals.get('res_id') + return_picking = self.env['stock.picking'].browse(return_id) - if not return_id: + if not return_picking: raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - picking_obj = self.env['stock.picking'].browse(return_id) - for p in picking_obj: - p.group_id = self.operations.group_id.id - p.tukar_guling_id = record.id + # Force the destination location (extra safeguard) + return_picking.write({ + 'location_dest_id': default_location_dest_id, + 'group_id': grup.id, + 'tukar_guling_id': record.id, + }) - return picking_obj.name + return return_picking.name # Buat return dari BU/PICK for picking in bu_pick_to_return: -- cgit v1.2.3 From 19c7a29333bd2c196a4aec2b173293da4d25e3ab Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 11:38:17 +0700 Subject: fix product not showing in detailed operations --- indoteknik_custom/models/tukar_guling.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 1f5e786f..740bb7d7 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -366,10 +366,11 @@ class TukarGuling(models.Model): # Create return lines return_lines = [] for move in picking.move_lines: - if move.quantity_done > 0: + qty = move.quantity_done or move.product_uom_qty + if qty > 0: return_lines.append((0, 0, { 'product_id': move.product_id.id, - 'quantity': move.quantity_done, + 'quantity': qty, 'move_id': move.id, })) if not return_lines: -- cgit v1.2.3 From cbf88d93e082f30305123deb467c4c15916d9519 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 15:05:43 +0700 Subject: Revert " fix from and to return" wkwkwk This reverts commit c1fa178a6afb9ef4a914dc617d2fb69da50af673. --- indoteknik_custom/models/tukar_guling.py | 56 +++++++++++++------------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 740bb7d7..ff52bab5 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -300,11 +300,11 @@ class TukarGuling(models.Model): ]) if not related_pickings: raise UserError( - "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) + "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") # 2. Filter berdasarkan tipe picking - bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK - bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT + bu_pick_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 30) # BU/PICK + bu_out_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 29) # BU/OUT if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") @@ -312,18 +312,13 @@ class TukarGuling(models.Model): created_returns = [] # Lokasi default untuk retur - srt_type = self.env['stock.picking.type'].browse(73) - ort_type = self.env['stock.picking.type'].browse(74) + bu_out_type = self.env['stock.picking.type'].browse(73) + bu_stock_type = self.env['stock.picking.type'].browse(74) - stock_location = self.env['stock.location'] + bu_out = bu_out_type.default_location_src_id.id + bu_stock = bu_out_type.default_location_dest_id.id - srt_src = stock_location.browse(5) - srt_dest = stock_location.browse(60) - - ort_src = stock_location.browse(60) - ort_dest = stock_location.browse(57) - - if not ort_src or not ort_dest or not srt_src or not srt_dest: + if not bu_out or not bu_stock: raise UserError("salahwoi") partner_location = self.env['stock.location'].search( @@ -334,19 +329,14 @@ class TukarGuling(models.Model): # Fungsi membuat retur dari picking tertentu def _create_return_from_picking(picking): - grup = self.operations.group_id - - PARTNER_LOCATION_ID = 5 # Partner Locations/Customers - BU_OUTPUT_LOCATION_ID = 60 # BU/Output (from your logs) - BU_STOCK_LOCATION_ID = 57 # BU/Stock (adjust to your actual ID) - - # Determine locations based on picking type + grup = self.env['stock.picking'].search([('origin', '=', self.operations.origin)]) + # Tentukan lokasi berdasarkan jenis picking if picking.picking_type_id.id == 29: # BU/OUT → BU/SRT - default_location_id = PARTNER_LOCATION_ID # From: Partner Locations - default_location_dest_id = BU_OUTPUT_LOCATION_ID # To: BU/Output + default_location_id = partner_location.id + default_location_dest_id = bu_out elif picking.picking_type_id.id == 30: # BU/PICK → BU/ORT - default_location_id = BU_OUTPUT_LOCATION_ID # From: BU/Output - default_location_dest_id = BU_STOCK_LOCATION_ID # To: BU/Stock (FIXED) + default_location_id = bu_out + default_location_dest_id = bu_stock else: return None @@ -363,7 +353,7 @@ class TukarGuling(models.Model): 'location_id': default_location_id, }) - # Create return lines + # Buat return lines return_lines = [] for move in picking.move_lines: qty = move.quantity_done or move.product_uom_qty @@ -381,21 +371,19 @@ class TukarGuling(models.Model): _logger.info("Creating return for picking %s", picking.name) _logger.info("Default location src: %s", default_location_id) _logger.info("Default location dest: %s", default_location_dest_id) + _logger.info("Move lines: %s", picking.move_lines) return_vals = return_wizard.create_returns() return_id = return_vals.get('res_id') - return_picking = self.env['stock.picking'].browse(return_id) - if not return_picking: + if not return_id: raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - # Force the destination location (extra safeguard) - return_picking.write({ - 'location_dest_id': default_location_dest_id, - 'group_id': grup.id, - 'tukar_guling_id': record.id, - }) + picking_obj = self.env['stock.picking'].browse(return_id) + for p in picking_obj: + p.group_id = self.operations.group_id.id + p.tukar_guling_id = record.id - return return_picking.name + return picking_obj.name # Buat return dari BU/PICK for picking in bu_pick_to_return: -- cgit v1.2.3 From ba4b74c9dc301dec73217bb6c35bb78ab7f41fb3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 15:29:50 +0700 Subject: re fix location --- indoteknik_custom/models/tukar_guling.py | 61 +++++++++++++++++++------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index ff52bab5..da1cfcf4 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError import logging @@ -20,6 +20,7 @@ class TukarGuling(models.Model): 'tukar_guling_id', string='Transfers' ) + # origin_so = fields.Many2one('sale.order', string='Origin SO') name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', @@ -300,11 +301,11 @@ class TukarGuling(models.Model): ]) if not related_pickings: raise UserError( - "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin + "Atau masih belum Done") + "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) # 2. Filter berdasarkan tipe picking - bu_pick_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 30) # BU/PICK - bu_out_to_return = related_pickings.filtered(lambda p: p.picking_type_id.id == 29) # BU/OUT + bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK + bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") @@ -312,13 +313,18 @@ class TukarGuling(models.Model): created_returns = [] # Lokasi default untuk retur - bu_out_type = self.env['stock.picking.type'].browse(73) - bu_stock_type = self.env['stock.picking.type'].browse(74) + srt_type = self.env['stock.picking.type'].browse(73) + ort_type = self.env['stock.picking.type'].browse(74) - bu_out = bu_out_type.default_location_src_id.id - bu_stock = bu_out_type.default_location_dest_id.id + stock_location = self.env['stock.location'] - if not bu_out or not bu_stock: + srt_src = stock_location.browse(5) + srt_dest = stock_location.browse(60) + + ort_src = stock_location.browse(60) + ort_dest = stock_location.browse(57) + + if not ort_src or not ort_dest or not srt_src or not srt_dest: raise UserError("salahwoi") partner_location = self.env['stock.location'].search( @@ -329,14 +335,19 @@ class TukarGuling(models.Model): # Fungsi membuat retur dari picking tertentu def _create_return_from_picking(picking): - grup = self.env['stock.picking'].search([('origin', '=', self.operations.origin)]) - # Tentukan lokasi berdasarkan jenis picking + grup = self.operations.group_id + + PARTNER_LOCATION_ID = 5 # Partner Locations/Customers + BU_OUTPUT_LOCATION_ID = 60 # BU/Output (from your logs) + BU_STOCK_LOCATION_ID = 57 # BU/Stock (adjust to your actual ID) + + # Determine locations based on picking type if picking.picking_type_id.id == 29: # BU/OUT → BU/SRT - default_location_id = partner_location.id - default_location_dest_id = bu_out + default_location_id = PARTNER_LOCATION_ID # From: Partner Locations + default_location_dest_id = BU_OUTPUT_LOCATION_ID # To: BU/Output elif picking.picking_type_id.id == 30: # BU/PICK → BU/ORT - default_location_id = bu_out - default_location_dest_id = bu_stock + default_location_id = BU_OUTPUT_LOCATION_ID # From: BU/Output + default_location_dest_id = BU_STOCK_LOCATION_ID # To: BU/Stock (FIXED) else: return None @@ -353,7 +364,7 @@ class TukarGuling(models.Model): 'location_id': default_location_id, }) - # Buat return lines + # Create return lines return_lines = [] for move in picking.move_lines: qty = move.quantity_done or move.product_uom_qty @@ -371,19 +382,21 @@ class TukarGuling(models.Model): _logger.info("Creating return for picking %s", picking.name) _logger.info("Default location src: %s", default_location_id) _logger.info("Default location dest: %s", default_location_dest_id) - _logger.info("Move lines: %s", picking.move_lines) return_vals = return_wizard.create_returns() return_id = return_vals.get('res_id') + return_picking = self.env['stock.picking'].browse(return_id) - if not return_id: + if not return_picking: raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - picking_obj = self.env['stock.picking'].browse(return_id) - for p in picking_obj: - p.group_id = self.operations.group_id.id - p.tukar_guling_id = record.id + # Force the destination location (extra safeguard) + return_picking.write({ + 'location_dest_id': default_location_dest_id, + 'group_id': grup.id, + 'tukar_guling_id': record.id, + }) - return picking_obj.name + return return_picking.name # Buat return dari BU/PICK for picking in bu_pick_to_return: @@ -439,4 +452,4 @@ class TukarGulingLine(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' - tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') \ No newline at end of file -- cgit v1.2.3 From 1be3cacacce54b6fe71eb3786d152c1d18707724 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 16:07:48 +0700 Subject: re fix location --- indoteknik_custom/models/tukar_guling.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index da1cfcf4..5c99bc18 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -337,17 +337,17 @@ class TukarGuling(models.Model): def _create_return_from_picking(picking): grup = self.operations.group_id - PARTNER_LOCATION_ID = 5 # Partner Locations/Customers - BU_OUTPUT_LOCATION_ID = 60 # BU/Output (from your logs) - BU_STOCK_LOCATION_ID = 57 # BU/Stock (adjust to your actual ID) + PARTNER_LOCATION_ID = 5 + BU_OUTPUT_LOCATION_ID = 60 + BU_STOCK_LOCATION_ID = 57 # Determine locations based on picking type - if picking.picking_type_id.id == 29: # BU/OUT → BU/SRT - default_location_id = PARTNER_LOCATION_ID # From: Partner Locations - default_location_dest_id = BU_OUTPUT_LOCATION_ID # To: BU/Output - elif picking.picking_type_id.id == 30: # BU/PICK → BU/ORT - default_location_id = BU_OUTPUT_LOCATION_ID # From: BU/Output - default_location_dest_id = BU_STOCK_LOCATION_ID # To: BU/Stock (FIXED) + if picking.picking_type_id.id == 29: + default_location_id = PARTNER_LOCATION_ID + default_location_dest_id = BU_OUTPUT_LOCATION_ID + elif picking.picking_type_id.id == 30: + default_location_id = BU_OUTPUT_LOCATION_ID + default_location_dest_id = BU_STOCK_LOCATION_ID else: return None @@ -389,7 +389,7 @@ class TukarGuling(models.Model): if not return_picking: raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - # Force the destination location (extra safeguard) + # Force the destination location return_picking.write({ 'location_dest_id': default_location_dest_id, 'group_id': grup.id, -- cgit v1.2.3 From 680748ae128a90b9999acff60c770e2472c7fcbe Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 21 Jun 2025 20:52:32 +0700 Subject: it should be done --- indoteknik_custom/models/tukar_guling.py | 37 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 5c99bc18..a7b6e07e 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -315,6 +315,8 @@ class TukarGuling(models.Model): # Lokasi default untuk retur srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) + bu_pick_type = self.env['stock.picking.type'].browse(30) + bu_out_type = self.env['stock.picking.type'].browse(29) stock_location = self.env['stock.location'] @@ -342,12 +344,22 @@ class TukarGuling(models.Model): BU_STOCK_LOCATION_ID = 57 # Determine locations based on picking type - if picking.picking_type_id.id == 29: + if picking.picking_type_id.id == 30: # -> ngeretur bu pick + return_type = srt_type + default_location_id = BU_OUTPUT_LOCATION_ID + default_location_dest_id = BU_STOCK_LOCATION_ID + elif picking.picking_type_id.id == 29: # -> ngeretur bu out + return_type = ort_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID - elif picking.picking_type_id.id == 30: + elif picking.picking_type_id.id == 74: # -> ngeretur srt + return_type = bu_pick_type + default_location_id = BU_STOCK_LOCATION_ID + default_location_dest_id = BU_OUTPUT_LOCATION_ID + elif picking.picking_type_id.id == 73: # -> ngeretur ort + return_type = bu_out_type default_location_id = BU_OUTPUT_LOCATION_ID - default_location_dest_id = BU_STOCK_LOCATION_ID + default_location_dest_id = PARTNER_LOCATION_ID else: return None @@ -396,20 +408,29 @@ class TukarGuling(models.Model): 'tukar_guling_id': record.id, }) - return return_picking.name + return return_picking - # Buat return dari BU/PICK - for picking in bu_pick_to_return: + # Buat return dari BU/OUT + for picking in bu_out_to_return: name = _create_return_from_picking(picking) if name: created_returns.append(name) - # Buat return dari BU/OUT - for picking in bu_out_to_return: + # Buat return dari BU/PICK + for picking in bu_pick_to_return: name = _create_return_from_picking(picking) if name: created_returns.append(name) + # Buat return dari SRT + if record.return_type == 'tukar_guling': + target = [woi for woi in created_returns if woi.picking_type_id.id in (73, 74)] + for picking in target: + retur = _create_return_from_picking(picking) + if retur: + created_returns.append(retur) + + if not created_returns: raise UserError("wkwkwk") -- cgit v1.2.3 From 39e570854fa72d673bd37fc6582861bf1fc49aa7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sun, 22 Jun 2025 21:09:36 +0700 Subject: should be done --- indoteknik_custom/models/tukar_guling.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index a7b6e07e..456a2111 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -303,7 +303,7 @@ class TukarGuling(models.Model): raise UserError( "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) - # 2. Filter berdasarkan tipe picking + # filter based on stockin.picking picking type bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT @@ -345,11 +345,11 @@ class TukarGuling(models.Model): # Determine locations based on picking type if picking.picking_type_id.id == 30: # -> ngeretur bu pick - return_type = srt_type + return_type = ort_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID elif picking.picking_type_id.id == 29: # -> ngeretur bu out - return_type = ort_type + return_type = srt_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID elif picking.picking_type_id.id == 74: # -> ngeretur srt @@ -373,7 +373,8 @@ class TukarGuling(models.Model): return_wizard = self.env['stock.return.picking'].with_context(return_context).create({ 'picking_id': picking.id, - 'location_id': default_location_id, + 'location_id': default_location_dest_id, + 'original_location_id': default_location_id }) # Create return lines @@ -404,6 +405,7 @@ class TukarGuling(models.Model): # Force the destination location return_picking.write({ 'location_dest_id': default_location_dest_id, + 'location_id': default_location_id, 'group_id': grup.id, 'tukar_guling_id': record.id, }) @@ -422,9 +424,9 @@ class TukarGuling(models.Model): if name: created_returns.append(name) - # Buat return dari SRT + # Buat return dari SRT dan ort if record.return_type == 'tukar_guling': - target = [woi for woi in created_returns if woi.picking_type_id.id in (73, 74)] + target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)] for picking in target: retur = _create_return_from_picking(picking) if retur: -- cgit v1.2.3 From f0f3e9d142dd435c9740f6d6c439e1b400a36c45 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 23 Jun 2025 15:28:01 +0700 Subject: fix qty --- indoteknik_custom/models/tukar_guling.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 456a2111..dfa62dc8 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -202,7 +202,7 @@ class TukarGuling(models.Model): if sequence: default['name'] = sequence.next_by_id() else: - default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'PTG-COPY' + default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling') or 'copy' default.update({ 'state': 'draft', @@ -312,6 +312,7 @@ class TukarGuling(models.Model): created_returns = [] + # Lokasi default untuk retur srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) @@ -329,12 +330,6 @@ class TukarGuling(models.Model): if not ort_src or not ort_dest or not srt_src or not srt_dest: raise UserError("salahwoi") - partner_location = self.env['stock.location'].search( - [('complete_name', 'ilike', 'Partner Locations/Customers'), - ('id', '=', '5')]) - if not partner_location: - raise UserError("Lokasi partner salah atau tidak ditemukan pada BU/OUT.") - # Fungsi membuat retur dari picking tertentu def _create_return_from_picking(picking): grup = self.operations.group_id @@ -348,19 +343,28 @@ class TukarGuling(models.Model): return_type = ort_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID + if not default_location_id or not default_location_dest_id: + raise UserError("Lokasi Origin atau Destination salah.") elif picking.picking_type_id.id == 29: # -> ngeretur bu out return_type = srt_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID + if not default_location_id or not default_location_dest_id: + raise UserError("Lokasi Origin atau Destination salah.") elif picking.picking_type_id.id == 74: # -> ngeretur srt return_type = bu_pick_type default_location_id = BU_STOCK_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID + if not default_location_id or not default_location_dest_id: + raise UserError("Lokasi Origin atau Destination salah.") elif picking.picking_type_id.id == 73: # -> ngeretur ort return_type = bu_out_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID + if not default_location_id or not default_location_dest_id: + raise UserError("Lokasi Origin atau Destination salah.") else: + raise UserError("Hayo") return None return_context = dict(self.env.context) @@ -379,12 +383,12 @@ class TukarGuling(models.Model): # Create return lines return_lines = [] - for move in picking.move_lines: - qty = move.quantity_done or move.product_uom_qty - if qty > 0: + for line in record.line_ids: + move = picking.move_lines.filtered(lambda wkwk: wkwk.product_id == line.product_id) + if move: return_lines.append((0, 0, { - 'product_id': move.product_id.id, - 'quantity': qty, + 'product_id': line.product_id.id, + 'quantity': line.product_uom_qty, 'move_id': move.id, })) if not return_lines: -- cgit v1.2.3 From 0d95dabc04a3f5334168e989705e9a7568130df4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 24 Jun 2025 08:41:47 +0700 Subject: spesific tf --- indoteknik_custom/models/tukar_guling.py | 8 +++++++- indoteknik_custom/views/tukar_guling.xml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index dfa62dc8..d75dad41 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -25,6 +25,7 @@ class TukarGuling(models.Model): date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', domain=[('picking_type_id.code', '=', 'outgoing')], help='Nomor BU/Out atau BU/Pick') + spesific_operations = fields.Many2one('stock.picking', 'Spesific Operations', domain = [('origin', '=', origin), ('state', '=', 'done')]) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ @@ -44,6 +45,10 @@ class TukarGuling(models.Model): @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" + if self.origin: + return {'domain': { + 'specific_transfers': [('origin', '=', self.origin), ('state', '=', 'done')] + }} if self.operations: from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) @@ -294,7 +299,6 @@ class TukarGuling(models.Model): operation_picking = record.operations - # 1. Cari semua picking DONE berdasarkan origin SO related_pickings = self.env['stock.picking'].search([ ('origin', '=', record.origin), ('state', '=', 'done'), @@ -391,6 +395,8 @@ class TukarGuling(models.Model): 'quantity': line.product_uom_qty, 'move_id': move.id, })) + if not move: + raise UserError("eror woi") if not return_lines: return None diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 942f085d..7bc783eb 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -12,7 +12,7 @@ -- cgit v1.2.3 From 8fc5488d18ac3005df9e8e259b2fbc0b67664841 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 25 Jun 2025 04:58:10 +0700 Subject: additional changes --- indoteknik_custom/models/tukar_guling.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d75dad41..0c6e5eca 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -302,14 +302,15 @@ class TukarGuling(models.Model): related_pickings = self.env['stock.picking'].search([ ('origin', '=', record.origin), ('state', '=', 'done'), + ('picking_type_id', 'in', [29, 30]) ]) if not related_pickings: raise UserError( "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) # filter based on stockin.picking picking type - bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK - bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT + bu_pick_to_return = self.env['stock.picking'] or related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK + bu_out_to_return = record.operations or related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") -- cgit v1.2.3 From f8765bb7a9b6095c3b79d71f65ce8ae4a041da6d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 25 Jun 2025 08:04:39 +0700 Subject: don --- indoteknik_custom/models/tukar_guling.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 0c6e5eca..e00c21f8 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -309,8 +309,8 @@ class TukarGuling(models.Model): "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) # filter based on stockin.picking picking type - bu_pick_to_return = self.env['stock.picking'] or related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30) # BU/PICK - bu_out_to_return = record.operations or related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29) # BU/OUT + bu_pick_to_return = record.operations.konfirm_koli_lines.pick_id + bu_out_to_return = record.operations if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") -- cgit v1.2.3 From 3b6b3a2f7a161a08fa3995906f92019cc5ea4d17 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 10:32:15 +0700 Subject: don --- indoteknik_custom/models/tukar_guling.py | 2 +- indoteknik_custom/views/tukar_guling.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index e00c21f8..681090f0 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -308,7 +308,7 @@ class TukarGuling(models.Model): raise UserError( "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) - # filter based on stockin.picking picking type + # filter based on stock.picking picking type bu_pick_to_return = record.operations.konfirm_koli_lines.pick_id bu_out_to_return = record.operations diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 7bc783eb..9d281986 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -95,7 +95,7 @@ - + Date: Thu, 26 Jun 2025 15:06:59 +0700 Subject: jago --- indoteknik_custom/models/tukar_guling.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 681090f0..77538c91 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -24,8 +24,8 @@ class TukarGuling(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.code', '=', 'outgoing')], help='Nomor BU/Out atau BU/Pick') - spesific_operations = fields.Many2one('stock.picking', 'Spesific Operations', domain = [('origin', '=', origin), ('state', '=', 'done')]) + domain=[('picking_type_id.sequence_code', 'in', ['OUT', 'PICK']), + ('state', '=', 'done')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ @@ -45,10 +45,6 @@ class TukarGuling(models.Model): @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" - if self.origin: - return {'domain': { - 'specific_transfers': [('origin', '=', self.origin), ('state', '=', 'done')] - }} if self.operations: from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) -- cgit v1.2.3 From 1e01a6845781821fcf65a42c4528b3efe144e28c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 15:23:24 +0700 Subject: show bu pick where the bu out is not done --- indoteknik_custom/models/tukar_guling.py | 18 +++++++----------- indoteknik_custom/views/tukar_guling.xml | 1 + 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 77538c91..6fbca20c 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -24,8 +24,10 @@ class TukarGuling(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.sequence_code', 'in', ['OUT', 'PICK']), - ('state', '=', 'done')], help='Nomor BU/Out atau BU/Pick') + domain=[('picking_type_id.sequence_code', '=', 'OUT'), + ('state', '=', 'done'), '&', + ('picking_type_id.sequence_code', '=', 'PICK'), + ('state', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ @@ -76,11 +78,6 @@ class TukarGuling(models.Model): elif hasattr(self.operations, 'move_lines') and self.operations.move_lines: moves_to_check = self.operations.move_lines - # Debug logging - _logger = logging.getLogger(__name__) - _logger.info(f"BU/OUT: {self.operations.name}, State: {self.operations.state}") - _logger.info(f"Total moves found: {len(moves_to_check)}") - for move in moves_to_check: _logger.info( f"Move: {move.name}, Product: {move.product_id.name if move.product_id else 'No Product'}, Qty: {move.product_uom_qty}, State: {move.state}") @@ -115,7 +112,7 @@ class TukarGuling(models.Model): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() if not self.operations: - raise UserError("Pilih BU/OUT terlebih dahulu!") + raise UserError("Pilih BU/OUT atau BU/PICK terlebih dahulu!") # Clear existing lines self.line_ids = [(5, 0, 0)] @@ -144,7 +141,7 @@ class TukarGuling(models.Model): def _check_required_bu_fields(self): for record in self: if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations: - raise ValidationError("BU/Out harus diisi!") + raise ValidationError("Operations harus diisi") @api.constrains('line_ids', 'state') def _check_product_lines(self): @@ -178,7 +175,6 @@ class TukarGuling(models.Model): def create(self, vals): # Generate sequence number if not vals.get('name') or vals['name'] == 'New': - # Pastikan sequence code 'tukar.guling' ada sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling')], limit=1) if sequence: vals['name'] = sequence.next_by_id() @@ -257,7 +253,7 @@ class TukarGuling(models.Model): self.ensure_one() if not self.operations: - raise UserError("BU/Out harus diisi!") + raise UserError("Operations harus diisi!") if not self.return_type: raise UserError("Return Type harus diisi!") diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 9d281986..633f1da4 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -86,6 +86,7 @@ 'required': [('return_type', 'in', ['revisi_so'])] }"/> + -- cgit v1.2.3 From b30cbc06bb4075b6c1fdab056cb5f5ea8d077d6c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 15:28:21 +0700 Subject: show bu pick where the bu out is not done --- indoteknik_custom/models/tukar_guling.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 6fbca20c..6b111029 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -27,7 +27,7 @@ class TukarGuling(models.Model): domain=[('picking_type_id.sequence_code', '=', 'OUT'), ('state', '=', 'done'), '&', ('picking_type_id.sequence_code', '=', 'PICK'), - ('state', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') + ('linked_manual_bu_out.state', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ -- cgit v1.2.3 From 69eed6936f7749809ee57bc4fa4f408fc93da6d3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 15:30:17 +0700 Subject: show bu pick --- indoteknik_custom/models/tukar_guling.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 6b111029..c1bd82bb 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -24,10 +24,8 @@ class TukarGuling(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.sequence_code', '=', 'OUT'), - ('state', '=', 'done'), '&', - ('picking_type_id.sequence_code', '=', 'PICK'), - ('linked_manual_bu_out.state', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') + domain=[('picking_type_id.sequence_code', 'in', 'OUT'), + ('state', '=', 'done')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ -- cgit v1.2.3 From bc99ea265c50e22d0a6cd74e1e5a4d5f27988701 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 16:59:47 +0700 Subject: create ort only when retur from bu pick --- indoteknik_custom/models/tukar_guling.py | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index c1bd82bb..601603d7 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -24,8 +24,8 @@ class TukarGuling(models.Model): name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.sequence_code', 'in', 'OUT'), - ('state', '=', 'done')], help='Nomor BU/Out atau BU/Pick') + domain=[('picking_type_id.sequence_code', 'in', ['OUT', 'PICK']), + ('state', '=', 'done'), ('linked_manual_bu_out', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ @@ -287,8 +287,6 @@ class TukarGuling(models.Model): if not record.operations: raise UserError("BU/OUT dari field operations tidak ditemukan.") - operation_picking = record.operations - related_pickings = self.env['stock.picking'].search([ ('origin', '=', record.origin), ('state', '=', 'done'), @@ -414,24 +412,26 @@ class TukarGuling(models.Model): return return_picking # Buat return dari BU/OUT - for picking in bu_out_to_return: - name = _create_return_from_picking(picking) - if name: - created_returns.append(name) + if record.operations.picking_type_id == 30: + for picking in [bu_pick_to_return]: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) # Buat return dari BU/PICK - for picking in bu_pick_to_return: - name = _create_return_from_picking(picking) - if name: - created_returns.append(name) - - # Buat return dari SRT dan ort - if record.return_type == 'tukar_guling': - target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)] - for picking in target: - retur = _create_return_from_picking(picking) - if retur: - created_returns.append(retur) + else: + for picking in [bu_out_to_return]: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) + + # Buat return dari SRT dan ort + if record.return_type == 'tukar_guling' and record.operations.picking_type_id.id != 30: + target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)] + for picking in target: + retur = _create_return_from_picking(picking) + if retur: + created_returns.append(retur) if not created_returns: -- cgit v1.2.3 From 2c3358216c4a2b09ef0df3a2f8998889198ad310 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 26 Jun 2025 21:33:44 +0700 Subject: add validations --- indoteknik_custom/models/stock_picking_return.py | 9 ++++++++- indoteknik_custom/models/tukar_guling.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 74bf6407..982f3ebb 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -12,6 +12,10 @@ class StockReturnPicking(models.TransientModel): ], string='Jenis Retur', default='revisi_so') def create_returns(self): + picking = self.picking_id + if picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': + raise UserError("❌ BU/PICK tidak dapat di retur karena BU/OUT Sudah Done") + if self._context.get('from_ui', True) and self.return_type == 'tukar_guling': return self._redirect_to_tukar_guling() return super(StockReturnPicking, self).create_returns() @@ -21,6 +25,9 @@ class StockReturnPicking(models.TransientModel): self.ensure_one() picking = self.picking_id + if picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': + raise UserError("❌ BU/PICK tidak dapat di retur karena BU/OUT Sudah Done") + # Get valid return lines with better error handling valid_lines = [] @@ -145,4 +152,4 @@ class ReturnPickingLine(models.TransientModel): raise UserError( _("Quantity yang Anda masukkan (%.2f) tidak boleh melebihi quantity done yaitu: %.2f untuk produk %s") % (rec.quantity, qty_done, rec.product_id.name) - ) \ No newline at end of file + ) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 601603d7..e1513c2e 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -243,8 +243,30 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() + picking = self.operations + if self.state != 'draft': raise UserError("Submit hanya bisa dilakukan dari Draft.") + + if not self.operations: + raise UserError("Operations harus diisi!") + + if not self.return_type: + raise UserError("Return Type harus diisi!") + + self._validate_product_lines() + + if picking.picking_type_id.sequence_code == 'OUT' or picking.picking_type_id.id == 29: + if picking.state != 'done': + raise UserError("BU/OUT Harus Sudah DONE") + + elif picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30: + linked_bu_out = picking.linked_manual_bu_out + if linked_bu_out.state == 'done': + raise UserError("BU/PICK yang bisa diretur ketika BU/OUT yang belum DONE") + else: + raise UserError("Picking Type harus BU/OUT atau BU/PICK") + self.state = 'approval_sales' def action_approve(self): -- cgit v1.2.3 From 605385c9c0bf3ed95ada1628c02f00b53dc19eb6 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 27 Jun 2025 15:45:45 +0700 Subject: validation rev --- indoteknik_custom/__manifest__.py | 2 +- indoteknik_custom/models/stock_picking_return.py | 16 ++-- indoteknik_custom/models/tukar_guling.py | 112 +++++++++++++---------- 3 files changed, 72 insertions(+), 58 deletions(-) diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py index 7a1cf2a3..16b0e332 100755 --- a/indoteknik_custom/__manifest__.py +++ b/indoteknik_custom/__manifest__.py @@ -169,7 +169,7 @@ 'views/stock_inventory.xml', 'views/sale_order_delay.xml', 'views/tukar_guling.xml', - 'views/tukar_guling_return_views.xml' + # 'views/tukar_guling_return_views.xml' # 'views/tukar_guling_po.xml', ], 'demo': [], diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 982f3ebb..97b622b4 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -6,17 +6,17 @@ from odoo import models, fields, api, _ class StockReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' - return_type = fields.Selection([ - ('revisi_so', 'Revisi SO'), - ('tukar_guling', 'Tukar Guling') - ], string='Jenis Retur', default='revisi_so') + # return_type = fields.Selection([ + # ('revisi_so', 'Revisi SO'), + # ('tukar_guling', 'Tukar Guling') + # ], string='Jenis Retur', default='revisi_so') def create_returns(self): picking = self.picking_id - if picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': + if picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': raise UserError("❌ BU/PICK tidak dapat di retur karena BU/OUT Sudah Done") - if self._context.get('from_ui', True) and self.return_type == 'tukar_guling': + if self._context.get('from_ui', True): return self._redirect_to_tukar_guling() return super(StockReturnPicking, self).create_returns() @@ -25,7 +25,7 @@ class StockReturnPicking(models.TransientModel): self.ensure_one() picking = self.picking_id - if picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': + if picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': raise UserError("❌ BU/PICK tidak dapat di retur karena BU/OUT Sudah Done") # Get valid return lines with better error handling @@ -70,7 +70,7 @@ class StockReturnPicking(models.TransientModel): # Prepare context for Tukar Guling form context = { 'default_operations': picking.id, - 'default_return_type': 'tukar_guling', + # 'default_return_type': 'tukar_guling', 'default_date': fields.Datetime.now(), 'default_state': 'draft', 'default_ba_num': _('Retur dari %s') % picking.name, diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index e1513c2e..df501261 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -23,9 +23,23 @@ class TukarGuling(models.Model): # origin_so = fields.Many2one('sale.order', string='Origin SO') name = fields.Char('Number', required=True, copy=False, readonly=True, default='New') date = fields.Datetime('Date', default=fields.Datetime.now, required=True) - operations = fields.Many2one('stock.picking', 'Operations', - domain=[('picking_type_id.sequence_code', 'in', ['OUT', 'PICK']), - ('state', '=', 'done'), ('linked_manual_bu_out', '!=', 'done')], help='Nomor BU/Out atau BU/Pick') + operations = fields.Many2one( + 'stock.picking', + string='Operations', + domain=[ + '|', + # BU/OUT + '&', + ('picking_type_id.id', '=', 29), + ('state', '=', 'done'), + '&', + '&', + ('picking_type_id.id', '=', 30), + ('state', '=', 'done'), + ('linked_manual_bu_out', '!=', 'done'), + ], + help='Nomor BU/OUT atau BU/PICK' + ) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ @@ -169,6 +183,12 @@ class TukarGuling(models.Model): return True + def _is_already_returned(self, picking): + return self.env['stock.picking'].search_count([ + ('origin', '=', 'Return of %s' % picking.name), + ('state', '!=', 'cancel') + ]) > 0 + @api.model def create(self, vals): # Generate sequence number @@ -214,6 +234,8 @@ class TukarGuling(models.Model): return new_record def write(self, vals): + if self._is_already_returned(self.operations): + raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: @@ -242,35 +264,29 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() + picking = self.env['stock.picking'] + if picking.picking_type_id.id == 29: + if picking.picking_type_id.state != 'done': + raise UserError("BU/OUT belum Done!") + elif picking.picking_type_id.id == 30: + linked_bu_out = picking.linked_manual_bu_out + if linked_bu_out and linked_bu_out.state == 'done': + raise UserError("Tidak bisa retur BU/PICK karena BU/OUT suda Done!") + else: + raise UserError("Dokumen Mungkin sudah done") - picking = self.operations - - if self.state != 'draft': - raise UserError("Submit hanya bisa dilakukan dari Draft.") - - if not self.operations: - raise UserError("Operations harus diisi!") - - if not self.return_type: - raise UserError("Return Type harus diisi!") - + if self._is_already_returned(self.operations): + raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") self._validate_product_lines() - if picking.picking_type_id.sequence_code == 'OUT' or picking.picking_type_id.id == 29: - if picking.state != 'done': - raise UserError("BU/OUT Harus Sudah DONE") - - elif picking.picking_type_id.sequence_code == 'PICK' or picking.picking_type_id.id == 30: - linked_bu_out = picking.linked_manual_bu_out - if linked_bu_out.state == 'done': - raise UserError("BU/PICK yang bisa diretur ketika BU/OUT yang belum DONE") - else: - raise UserError("Picking Type harus BU/OUT atau BU/PICK") + if self.state != 'draft': + raise UserError("Submit hanya bisa dilakukan dari Draft.") self.state = 'approval_sales' def action_approve(self): self.ensure_one() + self._validate_product_lines() if not self.operations: raise UserError("Operations harus diisi!") @@ -309,6 +325,8 @@ class TukarGuling(models.Model): if not record.operations: raise UserError("BU/OUT dari field operations tidak ditemukan.") + operation_picking = record.operations + related_pickings = self.env['stock.picking'].search([ ('origin', '=', record.origin), ('state', '=', 'done'), @@ -327,7 +345,6 @@ class TukarGuling(models.Model): created_returns = [] - # Lokasi default untuk retur srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) @@ -354,25 +371,25 @@ class TukarGuling(models.Model): BU_STOCK_LOCATION_ID = 57 # Determine locations based on picking type - if picking.picking_type_id.id == 30: # -> ngeretur bu pick + if picking.picking_type_id.id == 30: # -> ngeretur bu pick return_type = ort_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID if not default_location_id or not default_location_dest_id: raise UserError("Lokasi Origin atau Destination salah.") - elif picking.picking_type_id.id == 29: # -> ngeretur bu out + elif picking.picking_type_id.id == 29: # -> ngeretur bu out return_type = srt_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID if not default_location_id or not default_location_dest_id: raise UserError("Lokasi Origin atau Destination salah.") - elif picking.picking_type_id.id == 74: # -> ngeretur srt + elif picking.picking_type_id.id == 74: # -> ngeretur srt return_type = bu_pick_type default_location_id = BU_STOCK_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID if not default_location_id or not default_location_dest_id: raise UserError("Lokasi Origin atau Destination salah.") - elif picking.picking_type_id.id == 73: # -> ngeretur ort + elif picking.picking_type_id.id == 73: # -> ngeretur ort return_type = bu_out_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID @@ -434,27 +451,24 @@ class TukarGuling(models.Model): return return_picking # Buat return dari BU/OUT - if record.operations.picking_type_id == 30: - for picking in [bu_pick_to_return]: - name = _create_return_from_picking(picking) - if name: - created_returns.append(name) + for picking in bu_out_to_return: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) # Buat return dari BU/PICK - else: - for picking in [bu_out_to_return]: - name = _create_return_from_picking(picking) - if name: - created_returns.append(name) - - # Buat return dari SRT dan ort - if record.return_type == 'tukar_guling' and record.operations.picking_type_id.id != 30: - target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)] - for picking in target: - retur = _create_return_from_picking(picking) - if retur: - created_returns.append(retur) - + for picking in bu_pick_to_return: + name = _create_return_from_picking(picking) + if name: + created_returns.append(name) + + # Buat return dari SRT dan ort + if record.return_type == 'tukar_guling': + target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)] + for picking in target: + retur = _create_return_from_picking(picking) + if retur: + created_returns.append(retur) if not created_returns: raise UserError("wkwkwk") @@ -498,4 +512,4 @@ class TukarGulingLine(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' - tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') \ No newline at end of file + tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref') -- cgit v1.2.3 From 0e2fe03295f96560500c53ee61104a0a9f563576 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 27 Jun 2025 15:53:11 +0700 Subject: remove ask return in stock picking --- indoteknik_custom/models/stock_picking.py | 66 ++++++++++++------------ indoteknik_custom/models/stock_picking_return.py | 6 +++ indoteknik_custom/views/stock_picking.xml | 10 ++-- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py index 6d868db4..1827c489 100644 --- a/indoteknik_custom/models/stock_picking.py +++ b/indoteknik_custom/models/stock_picking.py @@ -1074,38 +1074,40 @@ class StockPicking(models.Model): self.approval_receipt_status = 'pengajuan1' def ask_return_approval(self): - for pick in self: - if self.env.user.is_accounting: - pick.approval_return_status = 'approved' - continue - else: - pick.approval_return_status = 'pengajuan1' - - action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') - - if self.picking_type_code == 'outgoing': - if self.env.user.id in [3988, 3401, 20] or ( - self.env.user.has_group( - 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin - ): - action['context'] = {'picking_ids': [x.id for x in self]} - return action - elif not self.env.user.has_group( - 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: - raise UserError('Harus Purchasing yang Ask Return') - else: - raise UserError('Harus Sales Admin yang Ask Return') - - elif self.picking_type_code == 'incoming': - if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( - self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin - ): - action['context'] = {'picking_ids': [x.id for x in self]} - return action - elif not self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin: - raise UserError('Harus Sales Admin yang Ask Return') - else: - raise UserError('Harus Purchasing yang Ask Return') + pass + raise UserError("Bisa langsung Validate") + # for pick in self: + # if self.env.user.is_accounting: + # pick.approval_return_status = 'approved' + # continue + # else: + # pick.approval_return_status = 'pengajuan1' + # + # action = self.env['ir.actions.act_window']._for_xml_id('indoteknik_custom.action_stock_return_note_wizard') + # + # if self.picking_type_code == 'outgoing': + # if self.env.user.id in [3988, 3401, 20] or ( + # self.env.user.has_group( + # 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin + # ): + # action['context'] = {'picking_ids': [x.id for x in self]} + # return action + # elif not self.env.user.has_group( + # 'indoteknik_custom.group_role_purchasing') and 'Return of' in self.origin: + # raise UserError('Harus Purchasing yang Ask Return') + # else: + # raise UserError('Harus Sales Admin yang Ask Return') + # + # elif self.picking_type_code == 'incoming': + # if self.env.user.has_group('indoteknik_custom.group_role_purchasing') or ( + # self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin + # ): + # action['context'] = {'picking_ids': [x.id for x in self]} + # return action + # elif not self.env.user.id in [3988, 3401, 20] and 'Return of' in self.origin: + # raise UserError('Harus Sales Admin yang Ask Return') + # else: + # raise UserError('Harus Purchasing yang Ask Return') def calculate_line_no(self): diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 97b622b4..f7900a27 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -11,8 +11,14 @@ class StockReturnPicking(models.TransientModel): # ('tukar_guling', 'Tukar Guling') # ], string='Jenis Retur', default='revisi_so') + def create_returns(self): picking = self.picking_id + # guling = self.env['tukar.guling'] + # if guling._is_already_returned(picking): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + # if self._is_already_returned(picking): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if picking.picking_type_id.id == 30 and picking.linked_manual_bu_out.state == 'done': raise UserError("❌ BU/PICK tidak dapat di retur karena BU/OUT Sudah Done") diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml index c088e00c..f4159b1b 100644 --- a/indoteknik_custom/views/stock_picking.xml +++ b/indoteknik_custom/views/stock_picking.xml @@ -50,11 +50,11 @@ type="object" attrs="{'invisible': ['|', ('state', 'in', ['done']), ('approval_receipt_status', '=', 'pengajuan1')]}" /> - +

-
- + + + + + + - - - + + + + options="{'no_create': True, 'no_create_edit': True}"/> + options="{'no_create': True, 'no_create_edit': True}"/> @@ -114,4 +113,4 @@ - + \ No newline at end of file -- cgit v1.2.3 From c1bde5a538b04186a5c83aea2817cd4f05f2acd7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 4 Jul 2025 09:26:26 +0700 Subject: fix orig id and dest id --- indoteknik_custom/models/tukar_guling.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index eeec2d80..db82ce1b 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -371,21 +371,29 @@ class TukarGuling(models.Model): # Determine locations based on picking type if picking.picking_type_id.id == 30: + # BU/PICK → ORT return_type = ort_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID + + elif picking.picking_type_id.id == 74: + # ORT → BU/PICK + return_type = bu_pick_type + default_location_id = BU_STOCK_LOCATION_ID + default_location_dest_id = BU_OUTPUT_LOCATION_ID + elif picking.picking_type_id.id == 29: + # BU/OUT → SRT return_type = srt_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID - elif picking.picking_type_id.id == 74: + + elif picking.picking_type_id.id == 73: + # SRT → BU/OUT return_type = bu_out_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID - elif picking.picking_type_id.id == 73: - return_type = bu_pick_type - default_location_id = BU_STOCK_LOCATION_ID - default_location_dest_id = BU_OUTPUT_LOCATION_ID + else: return None return_context = dict(self.env.context) @@ -456,16 +464,16 @@ class TukarGuling(models.Model): created_returns.append(ort) if record.return_type == 'tukar_guling': - if srt: - bu_out = _create_return_from_picking(srt) - if bu_out: - created_returns.append(bu_out) - if ort: bu_pick = _create_return_from_picking(ort) if bu_pick: created_returns.append(bu_pick) + if srt: + bu_out = _create_return_from_picking(srt) + if bu_out: + created_returns.append(bu_out) + if not created_returns: raise UserError("wkwkwk") -- cgit v1.2.3 From 1118c1c0762ff0ad98bb8d6e64467b32635dadea Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 4 Jul 2025 09:31:57 +0700 Subject: tukar guling sequence --- indoteknik_custom/views/ir_sequence.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 17c9dd8c..3cc43c05 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -201,7 +201,7 @@ Pengajuan Return PO tukar.guling.po - CCM-po/ + VCM 5 1 1 -- cgit v1.2.3 From 43b88a8d7814281e4e20c3a22c0c1780e4caf54e Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 4 Jul 2025 17:14:41 +0700 Subject: tukar guling po (-) sequence --- indoteknik_custom/models/tukar_guling_po.py | 173 +++++++++++++--------------- 1 file changed, 81 insertions(+), 92 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 4ed363cf..92a58d21 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -25,7 +25,7 @@ class TukarGulingPO(models.Model): 'stock.picking', string='Operations', domain=[ - ('picking_type_id.id', 'in', [75, 32]), + ('picking_type_id.id', 'in', [75, 28]), ('state', '=', 'done') ],help='Nomor BU INPUT atau BU PUT' ) @@ -224,7 +224,7 @@ class TukarGulingPO(models.Model): return new_record def write(self, vals): - if self.operations.picking_type_id.id != 32: + if self.operations.picking_type_id.id != 28: if self._is_already_returned(self.operations): raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if 'operations' in vals and not vals.get('origin'): @@ -259,11 +259,11 @@ class TukarGulingPO(models.Model): if picking.picking_type_id.id == 75: if picking.state != 'done': raise UserError("BU/PUT belum Done!") - elif picking.picking_type_id.id == 32: + elif picking.picking_type_id.id == 28: linked_bu_out = picking.linked_manual_bu_out if linked_bu_out and linked_bu_out.state == 'done': raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT suda Done!") - if picking.picking_type_id.id != 75 or picking.picking_type_id.id != 32: + if picking.picking_type_id.id != 75 or picking.picking_type_id.id != 28: if self._is_already_returned(self.operations): raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") self._validate_product_lines() @@ -312,79 +312,58 @@ class TukarGulingPO(models.Model): def _create_pickings(self): for record in self: if not record.operations: - raise UserError("BU/OUT dari field operations tidak ditemukan.") - - related_pickings = self.env['stock.picking'].search([ - ('origin', '=', record.origin), - ('state', '=', 'done'), - ('picking_type_id', 'in', [75, 32]) - ]) - if not related_pickings: - raise UserError( - "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin) - - # filter based on stock.picking picking type - bu_input_to_return = False - if record.operations.purchase_id: - bu_input_to_return = record.operations.purchase_id.picking_ids.filtered( - lambda p: p.picking_type_id.id == 75 and p.state == 'done' - ) - if bu_input_to_return: - bu_input_to_return = bu_input_to_return[0] - - # BU PUT = operations - bu_put_to_return = record.operations - - if not bu_input_to_return and not bu_put_to_return: - raise UserError("Tidak ada BU INPUT atau BU PUT yang siap diretur.") + raise UserError("BU Operations belum dipilih.") created_returns = [] - # Lokasi default untuk retur - vrt_type = self.env['stock.picking.type'].browse(77) - prt_type = self.env['stock.picking.type'].browse(76) - bu_input_type = self.env['stock.picking.type'].browse(32) - bu_put_type = self.env['stock.picking.type'].browse(75) - - stock_location = self.env['stock.location'] - - # srt_src = stock_location.browse(5) - # srt_dest = stock_location.browse(60) - # - # ort_src = stock_location.browse(60) - # ort_dest = stock_location.browse(57) - # - # if not ort_src or not ort_dest or not srt_src or not srt_dest: - # raise UserError("salahwoi") + # Ambil BU INPUT & BU PUT dari group yang sama + group = record.operations.group_id + bu_input_to_return = bu_put_to_return = False + + if group: + po_pickings = self.env['stock.picking'].search([ + ('group_id', '=', group.id), + ('state', '=', 'done') + ]) + bu_input_to_return = po_pickings.filtered(lambda p: p.picking_type_id.id == 28) + bu_put_to_return = po_pickings.filtered(lambda p: p.picking_type_id.id == 75) + bu_input_to_return = bu_input_to_return[0] if bu_input_to_return else False + bu_put_to_return = bu_put_to_return[0] if bu_put_to_return else False + else: + raise UserError("Group ID tidak ditemukan pada BU Operations.") - # Fungsi membuat retur dari picking tertentu + # Fungsi buat return picking def _create_return_from_picking(picking): - grup = self.operations.group_id + grup = record.operations.group_id - PARTNER_LOCATION_ID = 5 - # BU_OUTPUT_LOCATION_ID = 60 - BU_INPUT_LOCATION_ID = 60 + PARTNER_LOCATION_ID = 4 + BU_INPUT_LOCATION_ID = 58 BU_STOCK_LOCATION_ID = 57 - # Determine locations based on picking type - if picking.picking_type_id.id == 77: - return_type = vrt_type - default_location_id = BU_STOCK_LOCATION_ID - default_location_dest_id = BU_INPUT_LOCATION_ID - elif picking.picking_type_id.id == 76: - return_type = prt_type + # Lokasi sesuai type + if picking.picking_type_id.id == 28: + # Retur dari BU INPUT → hasilnya jadi PRT (BU Input → Partner) + # tapi wizard tetap diinput sebagai picking_id=28, dari input ke partner default_location_id = BU_INPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID + elif picking.picking_type_id.id == 75: - return_type = bu_put_type + # Retur dari BU PUT → hasilnya jadi VRT (BU Stock → BU Input) + default_location_id = BU_STOCK_LOCATION_ID + default_location_dest_id = BU_INPUT_LOCATION_ID + + elif picking.picking_type_id.id == 77: + # Retur dari VRT → hasilnya jadi PUT lagi (BU Input → BU Stock) default_location_id = BU_INPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID - elif picking.picking_type_id.id == 32: - return_type = bu_input_type + + elif picking.picking_type_id.id == 76: + # Retur dari PRT → hasilnya jadi INPUT lagi (Partner → BU Input) default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_INPUT_LOCATION_ID else: return None + return_context = dict(self.env.context) return_context.update({ 'active_id': picking.id, @@ -399,72 +378,82 @@ class TukarGulingPO(models.Model): 'original_location_id': default_location_id }) - # Create return lines + # Buat lines return_lines = [] for line in record.line_ids: - move = picking.move_lines.filtered(lambda wkwk: wkwk.product_id == line.product_id) + move = picking.move_lines.filtered(lambda m: m.product_id == line.product_id) if move: return_lines.append((0, 0, { 'product_id': line.product_id.id, 'quantity': line.product_uom_qty, - 'move_id': move.id, + 'move_id': move[0].id, })) - if not move: - raise UserError("eror woi") + if not return_lines: return None return_wizard.product_return_moves = return_lines - - _logger.info("Creating return for picking %s", picking.name) - _logger.info("Default location src: %s", default_location_id) - _logger.info("Default location dest: %s", default_location_dest_id) return_vals = return_wizard.create_returns() - return_id = return_vals.get('res_id') - return_picking = self.env['stock.picking'].browse(return_id) + return_picking = self.env['stock.picking'].browse(return_vals.get('res_id')) - if not return_picking: - raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - - # Force the destination location + # Paksa locations di picking & moves return_picking.write({ - 'location_dest_id': default_location_dest_id, 'location_id': default_location_id, + 'location_dest_id': default_location_dest_id, 'group_id': grup.id, 'tukar_guling_po_id': record.id, }) + for move in return_picking.move_lines: + move.write({ + 'location_id': default_location_id, + 'location_dest_id': default_location_dest_id, + }) + + # Rename BU INPUT jadi BU/INPUT/ + if return_picking.picking_type_id.id == 28 and return_picking.name.startswith('BU/IN/'): + old_name = return_picking.name + return_picking.name = return_picking.name.replace('BU/IN/', 'BU/INPUT/', 1) + _logger.info("Rename %s -> %s", old_name, return_picking.name) + return return_picking + if record.operations.picking_typ_id.id == 28: + bu_prt = _create_return_from_picking(record.operations) + if bu_prt: + created_returns.append(bu_prt) + # Eksekusi sesuai kondisi operations if record.operations.picking_type_id.id == 76: - prt = _create_return_from_picking(record.operations) + # Kalau operations = PRT → return jadi BU INPUT + bu_input = _create_return_from_picking(record.operations) + if bu_input: + created_returns.append(bu_input) + elif record.operations.picking_type_id.id == 77: + # Kalau operations = VRT → return jadi BU PUT + bu_put = _create_return_from_picking(record.operations) + if bu_put: + created_returns.append(bu_put) + else: + # Standard: retur bu_input & bu_put + prt = _create_return_from_picking(bu_input_to_return) if prt: created_returns.append(prt) - else: - # CASE: Retur dari BU/OUT + vrt = _create_return_from_picking(bu_put_to_return) if vrt: created_returns.append(vrt) - prt = None - if bu_input_to_return: - prt = _create_return_from_picking(bu_input_to_return) - if prt: - created_returns.append(prt) - if record.return_type == 'tukar_guling': + vrt = _create_return_from_picking(bu_put_to_return) if vrt: - bu_put = _create_return_from_picking(vrt) - if bu_put: - created_returns.append(bu_put) + created_returns.append(vrt) + prt = _create_return_from_picking(bu_input_to_return) if prt: - bu_input = _create_return_from_picking(prt) - if bu_input: - created_returns.append(bu_input) + created_returns.append(prt) if not created_returns: - raise UserError("wkwkwk") + raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") -- cgit v1.2.3 From f316cfaaf269cf57488bbfae027df95f83a6ec28 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 5 Jul 2025 08:40:34 +0700 Subject: fix sequence and return --- indoteknik_custom/models/tukar_guling_po.py | 39 +++++++++++------------------ indoteknik_custom/views/ir_sequence.xml | 2 +- 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 92a58d21..f885017c 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -418,40 +418,31 @@ class TukarGulingPO(models.Model): return return_picking - if record.operations.picking_typ_id.id == 28: - bu_prt = _create_return_from_picking(record.operations) - if bu_prt: - created_returns.append(bu_prt) - # Eksekusi sesuai kondisi operations - if record.operations.picking_type_id.id == 76: - # Kalau operations = PRT → return jadi BU INPUT - bu_input = _create_return_from_picking(record.operations) - if bu_input: - created_returns.append(bu_input) - elif record.operations.picking_type_id.id == 77: - # Kalau operations = VRT → return jadi BU PUT - bu_put = _create_return_from_picking(record.operations) - if bu_put: - created_returns.append(bu_put) - else: - # Standard: retur bu_input & bu_put - prt = _create_return_from_picking(bu_input_to_return) + if record.operations.picking_type_id.id == 28: + prt = _create_return_from_picking(record.operations) if prt: created_returns.append(prt) - + else: vrt = _create_return_from_picking(bu_put_to_return) if vrt: created_returns.append(vrt) - if record.return_type == 'tukar_guling': - vrt = _create_return_from_picking(bu_put_to_return) - if vrt: - created_returns.append(vrt) - + prt = None + if bu_input_to_return: prt = _create_return_from_picking(bu_input_to_return) if prt: created_returns.append(prt) + if record.return_type == 'tukar_guling': + if prt: + bu_input = _create_return_from_picking(prt) + if bu_input: + created_returns.append(bu_input) + if vrt: + bu_put = _create_return_from_picking(vrt) + if bu_put: + created_returns.append(bu_put) + if not created_returns: raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 3cc43c05..b02e927a 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -198,7 +198,7 @@ 1 1 - + Pengajuan Return PO tukar.guling.po VCM -- cgit v1.2.3 From d51133889b2bfbc7fa2848382f233f5ce268d4d0 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Sat, 5 Jul 2025 11:39:08 +0700 Subject: push --- indoteknik_custom/models/stock_picking_return.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index a9781d3c..40cc30c9 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -5,7 +5,7 @@ import logging _logger = logging.getLogger(__name__) -class StockReturnPicking(models.TransientModel): +class ReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' # return_type = fields.Selection([ -- cgit v1.2.3 From 1ed9ce9ab59c12fa378cfab02f8919e08f424853 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 8 Jul 2025 09:31:01 +0700 Subject: rev 77 --- indoteknik_custom/models/stock_picking_return.py | 2 +- indoteknik_custom/models/tukar_guling.py | 34 +++++++++++++++++++++--- indoteknik_custom/views/tukar_guling.xml | 2 +- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index 40cc30c9..a9781d3c 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -5,7 +5,7 @@ import logging _logger = logging.getLogger(__name__) -class ReturnPicking(models.TransientModel): +class StockReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' # return_type = fields.Selection([ diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index db82ce1b..339a1ff1 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -59,6 +59,10 @@ class TukarGuling(models.Model): @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" + for rec in self: + if rec.operations and rec.operations.picking_type_id.id == 30: + rec.return_type = 'revisi_so' + if self.operations: from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) @@ -234,9 +238,11 @@ class TukarGuling(models.Model): return new_record def write(self, vals): - if self.operations.picking_type_id.id != 30: - if self._is_already_returned(self.operations): - raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + if self.operations.picking_type_id.id == 30 and self.return_type == 'tukar_guling': + raise UserError ("BU/PICK tidak boleh retur tukar guling") + # if self.operations.picking_type_id.id != 30: + # if self._is_already_returned(self.operations): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: @@ -244,6 +250,12 @@ class TukarGuling(models.Model): return super(TukarGuling, self).write(vals) + def unlink(self): + for record in self: + if record.state == 'done': + raise UserError("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu") + return super(TukarGuling, self).unlink() + def action_view_picking(self): self.ensure_one() action = self.env.ref('stock.action_picking_tree_all').read()[0] @@ -266,6 +278,8 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() picking = self.operations + if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling': + raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") if picking.picking_type_id.id == 29: if picking.state != 'done': raise UserError("BU/OUT belum Done!") @@ -287,6 +301,9 @@ class TukarGuling(models.Model): self.ensure_one() self._validate_product_lines() + if self.operations.picking_type_id.id == 30 and self.return_type == 'tukar_guling': + raise UserError ("BU/PICK tidak boleh retur tukar guling") + if not self.operations: raise UserError("Operations harus diisi!") @@ -315,6 +332,14 @@ class TukarGuling(models.Model): def action_cancel(self): self.ensure_one() + # picking = self.env['stock.picking'] + bu_done = self.picking_ids.filtered(lambda p: p.state == 'done') + if bu_done: + raise UserError("Dokuemn BU sudah Done, tidak bisa di cancel") + ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'done') + for picking in ongoing_bu: + picking.action_cancel() + # if self.state == 'done': # raise UserError("Tidak bisa cancel jika sudah done") self.state = 'cancel' @@ -339,6 +364,9 @@ class TukarGuling(models.Model): bu_pick_to_return = record.operations.konfirm_koli_lines.pick_id bu_out_to_return = record.operations + # if bu_pick_to_return and self.return_type == 'tukar_guling': + # raise UserError("BU/PICK tidak boleh di retur tukar guling") + if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 01721b43..dc5abec7 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -50,7 +50,7 @@ attrs="{'invisible': [('state', 'not in', ['approval_sales', 'approval_logistic', 'approval_finance'])]}"/> -- cgit v1.2.3 From aa217ff1809015908d7aa16683de9b9ca34e1910 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 8 Jul 2025 10:54:37 +0700 Subject: rev 77 po fix sequence --- indoteknik_custom/models/tukar_guling_po.py | 2 +- indoteknik_custom/views/ir_sequence.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index f885017c..2e0ab604 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -55,7 +55,7 @@ class TukarGulingPO(models.Model): vals['name'] = sequence.next_by_id() else: # Fallback jika sequence belum dibuat - vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'embo==' + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'new' # Auto-fill origin from operations if not vals.get('origin') and vals.get('operations'): diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index b02e927a..8ae532cc 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -201,7 +201,7 @@ Pengajuan Return PO tukar.guling.po - VCM + VCM/ 5 1 1 -- cgit v1.2.3 From 21128e0f165045558c2c8ef6faf199d4379614b1 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 9 Jul 2025 09:54:13 +0700 Subject: rev 77 vals --- indoteknik_custom/models/tukar_guling.py | 50 +++++++++++++++++++++++++---- indoteknik_custom/models/tukar_guling_po.py | 32 ++++++++++++++++++ indoteknik_custom/views/tukar_guling.xml | 2 +- 3 files changed, 76 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 9fe7527c..e2f68e6c 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -251,9 +251,25 @@ class TukarGuling(models.Model): return new_record def write(self, vals): + self.ensure_one() self._check_invoice_on_revisi_so() - if self.operations.picking_type_id.id == 30 and self.return_type == 'tukar_guling': - raise UserError ("BU/PICK tidak boleh retur tukar guling") + operasi = self.operations.picking_type_id.id + tipe = self.return_type + pp = vals.get('return_type', tipe) + + if not self.operations: + raise UserError("Operations harus diisi!") + + if not self.return_type: + raise UserError("Return Type harus diisi!") + + if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done': + raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done") + if operasi == 30 and pp == 'tukar_guling': + raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") + else: + _logger.info("hehhe") + # if self.operations.picking_type_id.id != 30: # if self._is_already_returned(self.operations): # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") @@ -267,7 +283,11 @@ class TukarGuling(models.Model): def unlink(self): for record in self: if record.state == 'done': - raise UserError("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu") + raise UserError( + "Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu jika ingin menghapus") + ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'done') + for picking in ongoing_bu: + picking.action_cancel() return super(TukarGuling, self).unlink() def action_view_picking(self): @@ -291,6 +311,15 @@ class TukarGuling(models.Model): def action_submit(self): self.ensure_one() + + existing_tukar_guling = self.env['tukar.guling'].search([ + ('operations', '=', self.operations.id), + ('id', '!=', self.id), + ('state', '!=', 'cancel'), + ], limit=1) + + if existing_tukar_guling: + raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name) picking = self.operations if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling': raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") @@ -307,7 +336,6 @@ class TukarGuling(models.Model): self._check_invoice_on_revisi_so() self._validate_product_lines() - if self.state != 'draft': raise UserError("Submit hanya bisa dilakukan dari Draft.") self.state = 'approval_sales' @@ -317,8 +345,16 @@ class TukarGuling(models.Model): self._validate_product_lines() self._check_invoice_on_revisi_so() - if self.operations.picking_type_id.id == 30 and self.return_type == 'tukar_guling': - raise UserError ("BU/PICK tidak boleh retur tukar guling") + operasi = self.operations.picking_type_id.id + tipe = self.return_type + pp = vals.get('return_type', tipe) + + if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done': + raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done") + if operasi == 30 and pp == 'tukar_guling': + raise UserError("❌ BU/PICK tidak boleh di retur tukar guling") + else: + _logger.info("hehhe") if not self.operations: raise UserError("Operations harus diisi!") @@ -351,7 +387,7 @@ class TukarGuling(models.Model): # picking = self.env['stock.picking'] bu_done = self.picking_ids.filtered(lambda p: p.state == 'done') if bu_done: - raise UserError("Dokuemn BU sudah Done, tidak bisa di cancel") + raise UserError("Dokuemen BU sudah Done, tidak bisa di cancel") ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'done') for picking in ongoing_bu: picking.action_cancel() diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 2e0ab604..e9dfda33 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -65,6 +65,20 @@ class TukarGulingPO(models.Model): return super(TukarGulingPO, self).create(vals) + @api.constrains('return_type', 'operations') + def _check_bill_on_revisi_po(self): + for record in self: + if record.return_type == 'revisi_po' and record.origin: + bills = self.env['account.move'].search([ + ('invoice_origin', 'ilike', record.origin), + ('move_type', '=', 'in_invoice'), # hanya vendor bill + ('state', 'not in', ['draft', 'cancel']) + ]) + if bills: + raise ValidationError( + _("Tidak bisa memilih Return Type 'Revisi PO' karena PO %s sudah dibuat vendor bill.") % record.origin + ) + @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" @@ -224,6 +238,7 @@ class TukarGulingPO(models.Model): return new_record def write(self, vals): + self._check_bill_on_revisi_po() if self.operations.picking_type_id.id != 28: if self._is_already_returned(self.operations): raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") @@ -234,6 +249,15 @@ class TukarGulingPO(models.Model): return super(TukarGulingPO, self).write(vals) + def unlink(self): + for record in self: + if record.state == 'done': + raise UserError ("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu") + ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done') + for picking in ongoing_bu: + picking.action_cancel() + return super(TukarGulingPO, self).unlink() + def action_view_picking(self): self.ensure_one() action = self.env.ref('stock.action_picking_tree_all').read()[0] @@ -255,6 +279,7 @@ class TukarGulingPO(models.Model): def action_submit(self): self.ensure_one() + self._check_bill_on_revisi_po() picking = self.operations if picking.picking_type_id.id == 75: if picking.state != 'done': @@ -276,6 +301,7 @@ class TukarGulingPO(models.Model): def action_approve(self): self.ensure_one() self._validate_product_lines() + self._check_bill_on_revisi_po() if not self.operations: raise UserError("Operations harus diisi!") @@ -307,6 +333,12 @@ class TukarGulingPO(models.Model): self.ensure_one() # if self.state == 'done': # raise UserError("Tidak bisa cancel jika sudah done") + bu_done = self.po_picking_ids.filtered(lambda p: p.state == 'done') + if bu_done: + raise UserError("Dokuemn BU sudah Done, tidak bisa di cancel") + ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done') + for picking in ongoing_bu: + picking.action_cancel() self.state = 'cancel' def _create_pickings(self): diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 201031c4..41e9a18d 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -50,7 +50,7 @@ attrs="{'invisible': [('state', 'not in', ['approval_sales', 'approval_logistic', 'approval_finance'])]}"/> - -
-

- -

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- - + + pengajuan.tukar.guling.form + tukar.guling + +
+
+
+ +
+ +
+
+

+ +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ \ No newline at end of file -- cgit v1.2.3 From 3b1bc7642b6a5d0ac19dd74563c5b353a7f3b8ba Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 16 Jul 2025 10:27:02 +0700 Subject: vals qty mapping koli tukar guling --- indoteknik_custom/models/tukar_guling.py | 20 ++++++++++++++++++++ indoteknik_custom/views/tukar_guling.xml | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index f0fe13f6..333e2c8e 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -57,6 +57,26 @@ class TukarGuling(models.Model): line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') mapping_koli_ids = fields.One2many('tukar.guling.mapping.koli', 'tukar_guling_id', string='Mapping Koli') + @api.constrains('mapping_koli_ids') + def _check_mapping_koli(self): + for record in self: + if record.operations.picking_type_id.id == 29: # Only for BU/OUT + if not record.mapping_koli_ids: + raise UserError("❌ Mapping Koli belum diisi") + + # Calculate totals as integers + total_mapping_qty = sum(int(mapping.qty_done) for mapping in record.mapping_koli_ids) + total_line_qty = sum(int(line.product_uom_qty) for line in record.line_ids) + + # Strict integer comparison + if total_mapping_qty != total_line_qty: + raise UserError( + "❌ Total quantity mapping koli (%d) tidak sama dengan quantity retur (%d)" % + (total_mapping_qty, total_line_qty) + ) + else: + _logger.info("qty koli sesuai") + @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 903a47fd..c36089ad 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -99,8 +99,8 @@
- - + + -- cgit v1.2.3 From 0e330178d087b2b4cb519c4e078f0fe25a76dfe5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 16 Jul 2025 13:37:08 +0700 Subject: fix qty prod line and add qty return --- indoteknik_custom/models/stock_picking_return.py | 1 + indoteknik_custom/models/tukar_guling.py | 51 ++++++++++++++++-------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index fa557ce8..e274a147 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -103,6 +103,7 @@ class StockReturnPicking(models.TransientModel): 'pick_id': picking.id, # ID BU/PICK itu sendiri 'product_id': move_line.product_id.id, 'qty_done': move_line.qty_done, + 'qty_return': move_line.qty_done, })) sequence += 10 diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 333e2c8e..a2168f5b 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -65,13 +65,13 @@ class TukarGuling(models.Model): raise UserError("❌ Mapping Koli belum diisi") # Calculate totals as integers - total_mapping_qty = sum(int(mapping.qty_done) for mapping in record.mapping_koli_ids) + total_mapping_qty = sum(int(mapping.qty_done) for mapping in record.mapping_koli_ids.qty_return) total_line_qty = sum(int(line.product_uom_qty) for line in record.line_ids) # Strict integer comparison if total_mapping_qty != total_line_qty: raise UserError( - "❌ Total quantity mapping koli (%d) tidak sama dengan quantity retur (%d)" % + "❌ Total quantity return di mapping koli (%d) tidak sama dengan quantity retur product lines (%d)" % (total_mapping_qty, total_line_qty) ) else: @@ -92,6 +92,28 @@ class TukarGuling(models.Model): # Hanya update origin, jangan ubah lines if self.operations.origin: self.origin = self.operations.origin + _logger.info("📌 Menggunakan product lines dari return wizard, tidak populate ulang.") + + # 🚀 Tapi tetap populate mapping koli jika BU/OUT + if self.operations.picking_type_id.id == 29: + mapping_koli_data = [] + sequence = 10 + tg_product_ids = self.line_ids.mapped('product_id.id') + + for koli_line in self.operations.konfirm_koli_lines: + pick_move = koli_line.pick_id.move_line_ids_without_package + if pick_move.product_id.id in tg_product_ids: + mapping_koli_data.append((0, 0, { + 'sequence': sequence, + 'pick_id': koli_line.pick_id.id, + 'product_id': pick_move.product_id.id, + 'qty_done': pick_move.qty_done + })) + sequence += 10 + + self.mapping_koli_ids = mapping_koli_data + _logger.info(f"✅ Created {len(mapping_koli_data)} mapping koli lines (from return wizard)") + return # keluar supaya tidak populate ulang lines # Clear existing lines hanya jika tidak dari return picking self.line_ids = [(5, 0, 0)] @@ -108,14 +130,12 @@ class TukarGuling(models.Model): # Untuk Odoo 14, gunakan move_ids_without_package atau move_lines moves_to_check = [] - # 1. move_ids_without_package (standard di Odoo 14) if hasattr(self.operations, 'move_ids_without_package') and self.operations.move_ids_without_package: moves_to_check = self.operations.move_ids_without_package - # 2. move_lines (backup untuk versi lama) elif hasattr(self.operations, 'move_lines') and self.operations.move_lines: moves_to_check = self.operations.move_lines - # Collect product data for both lines and mapping koli + # Collect product data product_data = {} for move in moves_to_check: if move.product_id and move.product_uom_qty > 0: @@ -128,7 +148,7 @@ class TukarGuling(models.Model): 'name': move.name or move.product_id.display_name } - # Create lines_data for product lines + # Buat lines_data for product_id, data in product_data.items(): lines_data.append((0, 0, { 'sequence': sequence, @@ -143,24 +163,23 @@ class TukarGuling(models.Model): self.line_ids = lines_data _logger.info(f"Created {len(lines_data)} product lines") - # Prepare mapping koli based on picking type + # Prepare mapping koli jika BU/OUT mapping_koli_data = [] sequence = 10 - # Case 1: BU/OUT (picking_type_id.id == 29) if self.operations.picking_type_id.id == 29: - # Ambil dari konfirm_koli_lines BU/OUT + tg_product_ids = [p for p in product_data] for koli_line in self.operations.konfirm_koli_lines: - if koli_line.pick_id.move_line_ids_without_package.product_id.id in product_data: + pick_move = koli_line.pick_id.move_line_ids_without_package + if pick_move.product_id.id in tg_product_ids: mapping_koli_data.append((0, 0, { 'sequence': sequence, - 'pick_id': koli_line.pick_id.move_line_ids_without_package.picking_id.id, - 'product_id': koli_line.pick_id.move_line_ids_without_package.product_id.id, - 'qty_done': koli_line.pick_id.move_line_ids_without_package.qty_done + 'pick_id': koli_line.pick_id.id, + 'product_id': pick_move.product_id.id, + 'qty_done': pick_move.qty_done })) sequence += 10 - if mapping_koli_data: self.mapping_koli_ids = mapping_koli_data _logger.info(f"Created {len(mapping_koli_data)} mapping koli lines") @@ -169,7 +188,6 @@ class TukarGuling(models.Model): else: _logger.info("No product lines created - no valid moves found") else: - # Clear lines jika operations dikosongkan, kecuali dari return picking from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) @@ -631,5 +649,6 @@ class TukarGulingMappingKoli(models.Model): tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling') pick_id = fields.Many2one('stock.picking', string='BU PICK') product_id = fields.Many2one('product.product', string='Product') - qty_done = fields.Float(string='Qty Done di BU PICK') + qty_done = fields.Float(string='Qty Done BU PICK') + qty_return = fields.Float(string='Qty yang mau diretur') sequence = fields.Integer(string='Sequence', default=10) -- cgit v1.2.3 From efa1650aae2bc2dca99624092adcc21f87dab648 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 16 Jul 2025 17:40:22 +0700 Subject: retur blm sesuai --- indoteknik_custom/models/tukar_guling.py | 99 +++++++++++++++++--------------- indoteknik_custom/views/tukar_guling.xml | 21 +++---- 2 files changed, 65 insertions(+), 55 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index a2168f5b..2c39b547 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -57,25 +57,23 @@ class TukarGuling(models.Model): line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') mapping_koli_ids = fields.One2many('tukar.guling.mapping.koli', 'tukar_guling_id', string='Mapping Koli') - @api.constrains('mapping_koli_ids') def _check_mapping_koli(self): for record in self: if record.operations.picking_type_id.id == 29: # Only for BU/OUT if not record.mapping_koli_ids: raise UserError("❌ Mapping Koli belum diisi") - # Calculate totals as integers - total_mapping_qty = sum(int(mapping.qty_done) for mapping in record.mapping_koli_ids.qty_return) + # Calculate totals + total_mapping_qty = sum(int(mapping.qty_return) for mapping in record.mapping_koli_ids) total_line_qty = sum(int(line.product_uom_qty) for line in record.line_ids) - # Strict integer comparison if total_mapping_qty != total_line_qty: raise UserError( "❌ Total quantity return di mapping koli (%d) tidak sama dengan quantity retur product lines (%d)" % (total_mapping_qty, total_line_qty) ) else: - _logger.info("qty koli sesuai") + _logger.info("✅ Qty mapping koli sesuai dengan product lines") @api.onchange('operations') def _onchange_operations(self): @@ -107,7 +105,8 @@ class TukarGuling(models.Model): 'sequence': sequence, 'pick_id': koli_line.pick_id.id, 'product_id': pick_move.product_id.id, - 'qty_done': pick_move.qty_done + 'qty_done': pick_move.qty_done, + 'qty_return': 0 })) sequence += 10 @@ -482,47 +481,39 @@ class TukarGuling(models.Model): if not record.operations: raise UserError("BU/OUT dari field operations tidak ditemukan.") + created_returns = [] + bu_pick_to_return = record.operations.konfirm_koli_lines.pick_id bu_out_to_return = record.operations if not bu_pick_to_return and not bu_out_to_return: raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") - created_returns = [] - - # Picking types & locations srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) bu_pick_type = self.env['stock.picking.type'].browse(30) bu_out_type = self.env['stock.picking.type'].browse(29) - def _create_return_from_picking(picking): + PARTNER_LOCATION_ID = 5 + BU_OUTPUT_LOCATION_ID = 60 + BU_STOCK_LOCATION_ID = 57 + + def _create_return_from_picking_grouped(picking): if not picking: return None - grup = record.operations.group_id - PARTNER_LOCATION_ID = 5 - BU_OUTPUT_LOCATION_ID = 60 - BU_STOCK_LOCATION_ID = 57 + grup = record.operations.group_id if picking.picking_type_id.id == 30: - # BU/PICK → ORT - return_type = ort_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID elif picking.picking_type_id.id == 74: - # ORT → BU/PICK - return_type = bu_pick_type default_location_id = BU_STOCK_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID elif picking.picking_type_id.id == 29: - # BU/OUT → SRT - return_type = srt_type default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_OUTPUT_LOCATION_ID elif picking.picking_type_id.id == 73: - # SRT → BU/OUT - return_type = bu_out_type default_location_id = BU_OUTPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID else: @@ -542,58 +533,75 @@ class TukarGuling(models.Model): 'original_location_id': default_location_id }) + # 🔥 If BU/OUT, ambil qty dari mapping koli (group by product) return_lines = [] - for line in record.line_ids: - move = picking.move_lines.filtered(lambda m: m.product_id == line.product_id) - if move: + if picking.picking_type_id.id == 29 and record.mapping_koli_ids: + grouped_qty = {} + for map_line in record.mapping_koli_ids: + pid = map_line.product_id.id + grouped_qty[pid] = grouped_qty.get(pid, 0) + map_line.qty_return + + for pid, qty in grouped_qty.items(): + move = picking.move_lines.filtered(lambda mv: mv.product_id.id == pid) + if move: + return_lines.append((0, 0, { + 'product_id': pid, + 'quantity': qty, + 'move_id': move[0].id, + })) + else: + raise UserError(_("Tidak ditemukan move line di picking %s untuk produk %s") + % (picking.name, map_line.product_id.display_name)) + else: + # Default kalau bukan BU/OUT (misalnya ORT), retur full qty done + for move in picking.move_lines: return_lines.append((0, 0, { - 'product_id': line.product_id.id, - 'quantity': line.product_uom_qty, - 'move_id': move[0].id, + 'product_id': move.product_id.id, + 'quantity': move.quantity_done or move.product_uom_qty, + 'move_id': move.id, })) - else: - raise UserError( - _("Tidak ditemukan move line di picking %s untuk produk %s") - % (picking.name, line.product_id.display_name) - ) if not return_lines: raise UserError(_("Tidak ada product line valid untuk retur picking %s") % picking.name) return_wizard.product_return_moves = return_lines return_vals = return_wizard.create_returns() - return_id = return_vals.get('res_id') - return_picking = self.env['stock.picking'].browse(return_id) + return_picking = self.env['stock.picking'].browse(return_vals.get('res_id')) if not return_picking: raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) return_picking.write({ - 'location_dest_id': default_location_dest_id, 'location_id': default_location_id, + 'location_dest_id': default_location_dest_id, 'group_id': grup.id, 'tukar_guling_id': record.id, }) + for move in return_picking.move_lines: + move.write({ + 'location_id': default_location_id, + 'location_dest_id': default_location_dest_id, + }) + return return_picking - # === PERBAIKI URUTAN === - srt = _create_return_from_picking(bu_out_to_return) + # === FLOW === + srt = _create_return_from_picking_grouped(bu_out_to_return) if srt: created_returns.append(srt) - picks = record.operations.konfirm_koli_lines.pick_id - for picking in picks: - ort = _create_return_from_picking(picking) + for picking in bu_pick_to_return: + ort = _create_return_from_picking_grouped(picking) if ort: created_returns.append(ort) if record.return_type == 'tukar_guling': - bu_pick = _create_return_from_picking(ort) + bu_pick = _create_return_from_picking_grouped(ort) if bu_pick: created_returns.append(bu_pick) if record.return_type == 'tukar_guling' and srt: - bu_out = _create_return_from_picking(srt) + bu_out = _create_return_from_picking_grouped(srt) if bu_out: created_returns.append(bu_out) @@ -601,6 +609,7 @@ class TukarGuling(models.Model): raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") + class TukarGulingLine(models.Model): _name = 'tukar.guling.line' _description = 'Tukar Guling Line' @@ -650,5 +659,5 @@ class TukarGulingMappingKoli(models.Model): pick_id = fields.Many2one('stock.picking', string='BU PICK') product_id = fields.Many2one('product.product', string='Product') qty_done = fields.Float(string='Qty Done BU PICK') - qty_return = fields.Float(string='Qty yang mau diretur') - sequence = fields.Integer(string='Sequence', default=10) + qty_return = fields.Float(string='Qty diretur') + sequence = fields.Integer(string='Sequence', default=10) \ No newline at end of file diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index c36089ad..acc48d3f 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -86,13 +86,13 @@ - - - + + + - - + + @@ -101,10 +101,11 @@ - - - - + + + + + -- cgit v1.2.3 From 2c5f2513e9380da52d9893c8dc5ad148c298a882 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 16 Jul 2025 21:49:53 +0700 Subject: temp --- indoteknik_custom/models/tukar_guling.py | 227 ++++++++++++++++--------------- 1 file changed, 119 insertions(+), 108 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 2c39b547..295ca5d9 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -481,13 +481,8 @@ class TukarGuling(models.Model): if not record.operations: raise UserError("BU/OUT dari field operations tidak ditemukan.") - created_returns = [] - - bu_pick_to_return = record.operations.konfirm_koli_lines.pick_id - bu_out_to_return = record.operations - - if not bu_pick_to_return and not bu_out_to_return: - raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.") + bu_out = record.operations + mapping_koli = record.mapping_koli_ids srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) @@ -498,116 +493,132 @@ class TukarGuling(models.Model): BU_OUTPUT_LOCATION_ID = 60 BU_STOCK_LOCATION_ID = 57 - def _create_return_from_picking_grouped(picking): - if not picking: - return None - - grup = record.operations.group_id - - if picking.picking_type_id.id == 30: - default_location_id = BU_OUTPUT_LOCATION_ID - default_location_dest_id = BU_STOCK_LOCATION_ID - elif picking.picking_type_id.id == 74: - default_location_id = BU_STOCK_LOCATION_ID - default_location_dest_id = BU_OUTPUT_LOCATION_ID - elif picking.picking_type_id.id == 29: - default_location_id = PARTNER_LOCATION_ID - default_location_dest_id = BU_OUTPUT_LOCATION_ID - elif picking.picking_type_id.id == 73: - default_location_id = BU_OUTPUT_LOCATION_ID - default_location_dest_id = PARTNER_LOCATION_ID - else: - return None + created_returns = [] - return_context = dict(self.env.context) - return_context.update({ - 'active_id': picking.id, - 'default_location_id': default_location_id, - 'default_location_dest_id': default_location_dest_id, - 'from_ui': False, - }) + ### ============= SRT dari BU/OUT ================== + srt_return_lines = [] + for prod in mapping_koli.mapped('product_id'): + qty_total_return = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod)) + move = bu_out.move_lines.filtered(lambda m: m.product_id == prod) + if not move: + raise UserError(f"Tidak ditemukan move BU/OUT untuk product {prod.display_name}") + srt_return_lines.append((0, 0, { + 'product_id': prod.id, + 'quantity': qty_total_return, + 'move_id': move[0].id, + })) - return_wizard = self.env['stock.return.picking'].with_context(return_context).create({ - 'picking_id': picking.id, - 'location_id': default_location_dest_id, - 'original_location_id': default_location_id + srt_picking = None + if srt_return_lines: + srt_context = { + 'active_id': bu_out.id, + 'default_location_id': PARTNER_LOCATION_ID, + 'default_location_dest_id': BU_OUTPUT_LOCATION_ID, + 'from_ui': False, + } + srt_wizard = self.env['stock.return.picking'].with_context(srt_context).create({ + 'picking_id': bu_out.id, + 'location_id': BU_OUTPUT_LOCATION_ID, + 'original_location_id': PARTNER_LOCATION_ID, + 'product_return_moves': srt_return_lines }) - - # 🔥 If BU/OUT, ambil qty dari mapping koli (group by product) - return_lines = [] - if picking.picking_type_id.id == 29 and record.mapping_koli_ids: - grouped_qty = {} - for map_line in record.mapping_koli_ids: - pid = map_line.product_id.id - grouped_qty[pid] = grouped_qty.get(pid, 0) + map_line.qty_return - - for pid, qty in grouped_qty.items(): - move = picking.move_lines.filtered(lambda mv: mv.product_id.id == pid) - if move: - return_lines.append((0, 0, { - 'product_id': pid, - 'quantity': qty, - 'move_id': move[0].id, - })) - else: - raise UserError(_("Tidak ditemukan move line di picking %s untuk produk %s") - % (picking.name, map_line.product_id.display_name)) - else: - # Default kalau bukan BU/OUT (misalnya ORT), retur full qty done - for move in picking.move_lines: - return_lines.append((0, 0, { - 'product_id': move.product_id.id, - 'quantity': move.quantity_done or move.product_uom_qty, - 'move_id': move.id, - })) - - if not return_lines: - raise UserError(_("Tidak ada product line valid untuk retur picking %s") % picking.name) - - return_wizard.product_return_moves = return_lines - return_vals = return_wizard.create_returns() - return_picking = self.env['stock.picking'].browse(return_vals.get('res_id')) - - if not return_picking: - raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) - - return_picking.write({ - 'location_id': default_location_id, - 'location_dest_id': default_location_dest_id, - 'group_id': grup.id, + srt_vals = srt_wizard.create_returns() + srt_picking = self.env['stock.picking'].browse(srt_vals.get('res_id')) + srt_picking.write({ + 'location_id': PARTNER_LOCATION_ID, + 'location_dest_id': BU_OUTPUT_LOCATION_ID, + 'group_id': bu_out.group_id.id, 'tukar_guling_id': record.id, }) - - for move in return_picking.move_lines: - move.write({ - 'location_id': default_location_id, - 'location_dest_id': default_location_dest_id, + created_returns.append(srt_picking) + + ### ============= ORT dari BU/PICK ================== + ort_pickings = [] + for pick in mapping_koli.mapped('pick_id'): + ort_return_lines = [] + pick_lines = mapping_koli.filtered(lambda m: m.pick_id == pick) + for mk in pick_lines: + move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id) + if not move: + raise UserError( + f"Tidak ditemukan move di BU/PICK {pick.name} untuk {mk.product_id.display_name}") + ort_return_lines.append((0, 0, { + 'product_id': mk.product_id.id, + 'quantity': mk.qty_return, + 'move_id': move[0].id + })) + if ort_return_lines: + ort_context = { + 'active_id': pick.id, + 'default_location_id': BU_STOCK_LOCATION_ID, + 'default_location_dest_id': BU_OUTPUT_LOCATION_ID, + 'from_ui': False, + } + ort_wizard = self.env['stock.return.picking'].with_context(ort_context).create({ + 'picking_id': pick.id, + 'location_id': BU_OUTPUT_LOCATION_ID, + 'original_location_id': BU_STOCK_LOCATION_ID, + 'product_return_moves': ort_return_lines }) - - return return_picking - - # === FLOW === - srt = _create_return_from_picking_grouped(bu_out_to_return) - if srt: - created_returns.append(srt) - - for picking in bu_pick_to_return: - ort = _create_return_from_picking_grouped(picking) - if ort: - created_returns.append(ort) - if record.return_type == 'tukar_guling': - bu_pick = _create_return_from_picking_grouped(ort) - if bu_pick: - created_returns.append(bu_pick) - - if record.return_type == 'tukar_guling' and srt: - bu_out = _create_return_from_picking_grouped(srt) - if bu_out: - created_returns.append(bu_out) + ort_vals = ort_wizard.create_returns() + ort_picking = self.env['stock.picking'].browse(ort_vals.get('res_id')) + ort_picking.write({ + 'location_id': BU_STOCK_LOCATION_ID, + 'location_dest_id': BU_OUTPUT_LOCATION_ID, + 'group_id': bu_out.group_id.id, + 'tukar_guling_id': record.id, + }) + ort_pickings.append(ort_picking) + created_returns.append(ort_picking) + + ### ============= BU/PICK & BU/OUT baru (tukar guling) ============== + if record.return_type == 'tukar_guling': + # Dari SRT → BU/OUT baru + if srt_picking: + bu_out_new = self.env['stock.return.picking'].with_context({ + 'active_id': srt_picking.id, + 'default_location_id': BU_OUTPUT_LOCATION_ID, + 'default_location_dest_id': PARTNER_LOCATION_ID, + 'from_ui': False, + }).create({ + 'picking_id': srt_picking.id, + 'location_id': PARTNER_LOCATION_ID, + 'original_location_id': BU_OUTPUT_LOCATION_ID + }).create_returns() + new_out = self.env['stock.picking'].browse(bu_out_new.get('res_id')) + new_out.write({ + 'location_id': BU_OUTPUT_LOCATION_ID, + 'location_dest_id': PARTNER_LOCATION_ID, + 'group_id': bu_out.group_id.id, + 'tukar_guling_id': record.id, + }) + created_returns.append(new_out) + + # Dari ORT → BU/PICK baru + for ort_p in ort_pickings: + bu_pick_new = self.env['stock.return.picking'].with_context({ + 'active_id': ort_p.id, + 'default_location_id': BU_OUTPUT_LOCATION_ID, + 'default_location_dest_id': BU_STOCK_LOCATION_ID, + 'from_ui': False, + }).create({ + 'picking_id': ort_p.id, + 'location_id': BU_STOCK_LOCATION_ID, + 'original_location_id': BU_OUTPUT_LOCATION_ID + }).create_returns() + new_pick = self.env['stock.picking'].browse(bu_pick_new.get('res_id')) + new_pick.write({ + 'location_id': BU_OUTPUT_LOCATION_ID, + 'location_dest_id': BU_STOCK_LOCATION_ID, + 'group_id': bu_out.group_id.id, + 'tukar_guling_id': record.id, + }) + created_returns.append(new_pick) if not created_returns: - raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") + raise UserError("Tidak ada dokumen retur berhasil dibuat.") + _logger.info("✅ Created %s returns: %s", len(created_returns), ", ".join([p.name for p in created_returns])) class TukarGulingLine(models.Model): -- cgit v1.2.3 From 3c7d7783aa15fee124d94839822614de4049b9c1 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 17 Jul 2025 13:17:15 +0700 Subject: Done Tukar Guling SO --- indoteknik_custom/models/tukar_guling.py | 137 ++++++++++++++++++++----------- 1 file changed, 91 insertions(+), 46 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 295ca5d9..ded4e2a3 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -477,6 +477,7 @@ class TukarGuling(models.Model): self.state = 'cancel' def _create_pickings(self): + _logger.info("🛠 Starting _create_pickings()") for record in self: if not record.operations: raise UserError("BU/OUT dari field operations tidak ditemukan.") @@ -484,141 +485,185 @@ class TukarGuling(models.Model): bu_out = record.operations mapping_koli = record.mapping_koli_ids + # Constants + PARTNER_LOCATION_ID = 5 + BU_OUTPUT_LOCATION_ID = 60 + BU_STOCK_LOCATION_ID = 57 + + # Picking Types srt_type = self.env['stock.picking.type'].browse(73) ort_type = self.env['stock.picking.type'].browse(74) bu_pick_type = self.env['stock.picking.type'].browse(30) bu_out_type = self.env['stock.picking.type'].browse(29) - PARTNER_LOCATION_ID = 5 - BU_OUTPUT_LOCATION_ID = 60 - BU_STOCK_LOCATION_ID = 57 - created_returns = [] - ### ============= SRT dari BU/OUT ================== + ### ======== SRT dari BU/OUT ========= srt_return_lines = [] for prod in mapping_koli.mapped('product_id'): - qty_total_return = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod)) + qty_total = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod)) move = bu_out.move_lines.filtered(lambda m: m.product_id == prod) if not move: - raise UserError(f"Tidak ditemukan move BU/OUT untuk product {prod.display_name}") + raise UserError(f"Move BU/OUT tidak ditemukan untuk produk {prod.display_name}") srt_return_lines.append((0, 0, { 'product_id': prod.id, - 'quantity': qty_total_return, - 'move_id': move[0].id, + 'quantity': qty_total, + 'move_id': move.id, })) + _logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}") srt_picking = None if srt_return_lines: - srt_context = { + srt_wizard = self.env['stock.return.picking'].with_context({ 'active_id': bu_out.id, 'default_location_id': PARTNER_LOCATION_ID, 'default_location_dest_id': BU_OUTPUT_LOCATION_ID, 'from_ui': False, - } - srt_wizard = self.env['stock.return.picking'].with_context(srt_context).create({ + }).create({ 'picking_id': bu_out.id, - 'location_id': BU_OUTPUT_LOCATION_ID, - 'original_location_id': PARTNER_LOCATION_ID, + 'location_id': PARTNER_LOCATION_ID, + 'original_location_id': BU_OUTPUT_LOCATION_ID, 'product_return_moves': srt_return_lines }) srt_vals = srt_wizard.create_returns() - srt_picking = self.env['stock.picking'].browse(srt_vals.get('res_id')) + srt_picking = self.env['stock.picking'].browse(srt_vals['res_id']) srt_picking.write({ 'location_id': PARTNER_LOCATION_ID, 'location_dest_id': BU_OUTPUT_LOCATION_ID, 'group_id': bu_out.group_id.id, 'tukar_guling_id': record.id, + 'sale_order': record.origin }) created_returns.append(srt_picking) + _logger.info(f"✅ SRT created: {srt_picking.name}") - ### ============= ORT dari BU/PICK ================== + ### ======== ORT dari BU/PICK ========= ort_pickings = [] for pick in mapping_koli.mapped('pick_id'): ort_return_lines = [] - pick_lines = mapping_koli.filtered(lambda m: m.pick_id == pick) - for mk in pick_lines: + for mk in mapping_koli.filtered(lambda m: m.pick_id == pick): move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id) if not move: raise UserError( - f"Tidak ditemukan move di BU/PICK {pick.name} untuk {mk.product_id.display_name}") + f"Move tidak ditemukan di BU/PICK {pick.name} untuk {mk.product_id.display_name}") ort_return_lines.append((0, 0, { 'product_id': mk.product_id.id, 'quantity': mk.qty_return, - 'move_id': move[0].id + 'move_id': move.id, })) + _logger.info(f"📟 ORT line: {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}") + if ort_return_lines: - ort_context = { + ort_wizard = self.env['stock.return.picking'].with_context({ 'active_id': pick.id, - 'default_location_id': BU_STOCK_LOCATION_ID, - 'default_location_dest_id': BU_OUTPUT_LOCATION_ID, + 'default_location_id': BU_OUTPUT_LOCATION_ID, + 'default_location_dest_id': BU_STOCK_LOCATION_ID, 'from_ui': False, - } - ort_wizard = self.env['stock.return.picking'].with_context(ort_context).create({ + }).create({ 'picking_id': pick.id, 'location_id': BU_OUTPUT_LOCATION_ID, 'original_location_id': BU_STOCK_LOCATION_ID, 'product_return_moves': ort_return_lines }) ort_vals = ort_wizard.create_returns() - ort_picking = self.env['stock.picking'].browse(ort_vals.get('res_id')) + ort_picking = self.env['stock.picking'].browse(ort_vals['res_id']) ort_picking.write({ - 'location_id': BU_STOCK_LOCATION_ID, - 'location_dest_id': BU_OUTPUT_LOCATION_ID, + 'location_id': BU_OUTPUT_LOCATION_ID, + 'location_dest_id': BU_STOCK_LOCATION_ID, 'group_id': bu_out.group_id.id, 'tukar_guling_id': record.id, + 'sale_order': record.origin }) - ort_pickings.append(ort_picking) created_returns.append(ort_picking) + ort_pickings.append(ort_picking) + _logger.info(f"✅ ORT created: {ort_picking.name}") - ### ============= BU/PICK & BU/OUT baru (tukar guling) ============== + ### ======== Tukar Guling: BU/OUT dan BU/PICK baru ======== if record.return_type == 'tukar_guling': - # Dari SRT → BU/OUT baru + # BU/OUT Baru dari SRT if srt_picking: - bu_out_new = self.env['stock.return.picking'].with_context({ + return_lines = [] + for move in srt_picking.move_lines: + if move.product_uom_qty > 0: + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.product_uom_qty, + 'move_id': move.id, + })) + _logger.info( + f"🔁 BU/OUT baru dari SRT | {move.product_id.display_name} | qty={move.product_uom_qty}") + + bu_out_wizard = self.env['stock.return.picking'].with_context({ 'active_id': srt_picking.id, 'default_location_id': BU_OUTPUT_LOCATION_ID, 'default_location_dest_id': PARTNER_LOCATION_ID, 'from_ui': False, }).create({ 'picking_id': srt_picking.id, - 'location_id': PARTNER_LOCATION_ID, - 'original_location_id': BU_OUTPUT_LOCATION_ID - }).create_returns() - new_out = self.env['stock.picking'].browse(bu_out_new.get('res_id')) + 'location_id': BU_OUTPUT_LOCATION_ID, + 'original_location_id': PARTNER_LOCATION_ID, + 'product_return_moves': return_lines + }) + bu_out_vals = bu_out_wizard.create_returns() + new_out = self.env['stock.picking'].browse(bu_out_vals['res_id']) new_out.write({ 'location_id': BU_OUTPUT_LOCATION_ID, 'location_dest_id': PARTNER_LOCATION_ID, 'group_id': bu_out.group_id.id, 'tukar_guling_id': record.id, + 'sale_order': record.origin }) created_returns.append(new_out) + _logger.info(f"✅ BU/OUT Baru dari SRT created: {new_out.name}") - # Dari ORT → BU/PICK baru + # BU/PICK Baru dari ORT for ort_p in ort_pickings: - bu_pick_new = self.env['stock.return.picking'].with_context({ + return_lines = [] + for move in ort_p.move_lines: + if move.product_uom_qty > 0: + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.product_uom_qty, + 'move_id': move.id + })) + _logger.info( + f"🔁 BU/PICK baru dari ORT {ort_p.name} | {move.product_id.display_name} | qty={move.product_uom_qty}") + + if not return_lines: + _logger.warning(f"❌ Tidak ada qty > 0 di ORT {ort_p.name}, dilewati.") + continue + + bu_pick_wizard = self.env['stock.return.picking'].with_context({ 'active_id': ort_p.id, - 'default_location_id': BU_OUTPUT_LOCATION_ID, - 'default_location_dest_id': BU_STOCK_LOCATION_ID, + 'default_location_id': BU_STOCK_LOCATION_ID, + 'default_location_dest_id': BU_OUTPUT_LOCATION_ID, 'from_ui': False, }).create({ 'picking_id': ort_p.id, 'location_id': BU_STOCK_LOCATION_ID, - 'original_location_id': BU_OUTPUT_LOCATION_ID - }).create_returns() - new_pick = self.env['stock.picking'].browse(bu_pick_new.get('res_id')) + 'original_location_id': BU_OUTPUT_LOCATION_ID, + 'product_return_moves': return_lines + }) + bu_pick_vals = bu_pick_wizard.create_returns() + new_pick = self.env['stock.picking'].browse(bu_pick_vals['res_id']) new_pick.write({ - 'location_id': BU_OUTPUT_LOCATION_ID, - 'location_dest_id': BU_STOCK_LOCATION_ID, + 'location_id': BU_STOCK_LOCATION_ID, + 'location_dest_id': BU_OUTPUT_LOCATION_ID, 'group_id': bu_out.group_id.id, 'tukar_guling_id': record.id, + 'sale_order': record.origin }) + new_pick.action_assign() # Penting agar bisa trigger check koli + new_pick.action_confirm() created_returns.append(new_pick) + _logger.info(f"✅ BU/PICK Baru dari ORT created: {new_pick.name}") if not created_returns: raise UserError("Tidak ada dokumen retur berhasil dibuat.") - _logger.info("✅ Created %s returns: %s", len(created_returns), ", ".join([p.name for p in created_returns])) + _logger.info("✅ Finished _create_pickings(). Created %s returns: %s", + len(created_returns), + ", ".join([p.name for p in created_returns])) class TukarGulingLine(models.Model): -- cgit v1.2.3 From c18b38bc03d9c260532f4a8e956b51421283fa73 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 17 Jul 2025 13:42:53 +0700 Subject: FIx bug Tukar Guling SO --- indoteknik_custom/models/tukar_guling.py | 71 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index ded4e2a3..20d43e4c 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -580,41 +580,6 @@ class TukarGuling(models.Model): ### ======== Tukar Guling: BU/OUT dan BU/PICK baru ======== if record.return_type == 'tukar_guling': - # BU/OUT Baru dari SRT - if srt_picking: - return_lines = [] - for move in srt_picking.move_lines: - if move.product_uom_qty > 0: - return_lines.append((0, 0, { - 'product_id': move.product_id.id, - 'quantity': move.product_uom_qty, - 'move_id': move.id, - })) - _logger.info( - f"🔁 BU/OUT baru dari SRT | {move.product_id.display_name} | qty={move.product_uom_qty}") - - bu_out_wizard = self.env['stock.return.picking'].with_context({ - 'active_id': srt_picking.id, - 'default_location_id': BU_OUTPUT_LOCATION_ID, - 'default_location_dest_id': PARTNER_LOCATION_ID, - 'from_ui': False, - }).create({ - 'picking_id': srt_picking.id, - 'location_id': BU_OUTPUT_LOCATION_ID, - 'original_location_id': PARTNER_LOCATION_ID, - 'product_return_moves': return_lines - }) - bu_out_vals = bu_out_wizard.create_returns() - new_out = self.env['stock.picking'].browse(bu_out_vals['res_id']) - new_out.write({ - 'location_id': BU_OUTPUT_LOCATION_ID, - 'location_dest_id': PARTNER_LOCATION_ID, - 'group_id': bu_out.group_id.id, - 'tukar_guling_id': record.id, - 'sale_order': record.origin - }) - created_returns.append(new_out) - _logger.info(f"✅ BU/OUT Baru dari SRT created: {new_out.name}") # BU/PICK Baru dari ORT for ort_p in ort_pickings: @@ -658,6 +623,42 @@ class TukarGuling(models.Model): created_returns.append(new_pick) _logger.info(f"✅ BU/PICK Baru dari ORT created: {new_pick.name}") + # BU/OUT Baru dari SRT + if srt_picking: + return_lines = [] + for move in srt_picking.move_lines: + if move.product_uom_qty > 0: + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.product_uom_qty, + 'move_id': move.id, + })) + _logger.info( + f"🔁 BU/OUT baru dari SRT | {move.product_id.display_name} | qty={move.product_uom_qty}") + + bu_out_wizard = self.env['stock.return.picking'].with_context({ + 'active_id': srt_picking.id, + 'default_location_id': BU_OUTPUT_LOCATION_ID, + 'default_location_dest_id': PARTNER_LOCATION_ID, + 'from_ui': False, + }).create({ + 'picking_id': srt_picking.id, + 'location_id': BU_OUTPUT_LOCATION_ID, + 'original_location_id': PARTNER_LOCATION_ID, + 'product_return_moves': return_lines + }) + bu_out_vals = bu_out_wizard.create_returns() + new_out = self.env['stock.picking'].browse(bu_out_vals['res_id']) + new_out.write({ + 'location_id': BU_OUTPUT_LOCATION_ID, + 'location_dest_id': PARTNER_LOCATION_ID, + 'group_id': bu_out.group_id.id, + 'tukar_guling_id': record.id, + 'sale_order': record.origin + }) + created_returns.append(new_out) + _logger.info(f"✅ BU/OUT Baru dari SRT created: {new_out.name}") + if not created_returns: raise UserError("Tidak ada dokumen retur berhasil dibuat.") -- cgit v1.2.3 From 2b55913a52b05f3f62786d7ae56070e96878178c Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 17 Jul 2025 19:15:42 +0700 Subject: fix cannot retur bu pick tukar guling so --- indoteknik_custom/models/tukar_guling.py | 42 +++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 20d43e4c..d8e30006 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -539,19 +539,37 @@ class TukarGuling(models.Model): ### ======== ORT dari BU/PICK ========= ort_pickings = [] - for pick in mapping_koli.mapped('pick_id'): + is_retur_from_bu_pick = record.operations.picking_type_id.id == 30 + picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped('pick_id') or line.product_uom_qty + + for pick in picks_to_return: ort_return_lines = [] - for mk in mapping_koli.filtered(lambda m: m.pick_id == pick): - move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id) - if not move: - raise UserError( - f"Move tidak ditemukan di BU/PICK {pick.name} untuk {mk.product_id.display_name}") - ort_return_lines.append((0, 0, { - 'product_id': mk.product_id.id, - 'quantity': mk.qty_return, - 'move_id': move.id, - })) - _logger.info(f"📟 ORT line: {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}") + if is_retur_from_bu_pick: + # Ambil dari tukar.guling.line + for line in record.line_ids: + move = pick.move_lines.filtered(lambda m: m.product_id == line.product_id) + if not move: + raise UserError( + f"Move tidak ditemukan di BU/PICK {pick.name} untuk {line.product_id.display_name}") + ort_return_lines.append((0, 0, { + 'product_id': line.product_id.id, + 'quantity': line.product_uom_qty, + 'move_id': move.id, + })) + _logger.info(f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}") + else: + # Ambil dari mapping koli + for mk in mapping_koli.filtered(lambda m: m.pick_id == pick): + move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id) + if not move: + raise UserError( + f"Move tidak ditemukan di BU/PICK {pick.name} untuk {mk.product_id.display_name}") + ort_return_lines.append((0, 0, { + 'product_id': mk.product_id.id, + 'quantity': mk.qty_return, + 'move_id': move.id, + })) + _logger.info(f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}") if ort_return_lines: ort_wizard = self.env['stock.return.picking'].with_context({ -- cgit v1.2.3 From 82c1a95f447d191018bed2f3a3c93831f6d398cc Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 18 Jul 2025 00:27:15 +0700 Subject: try po --- indoteknik_custom/models/tukar_guling_po.py | 45 +++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 88c4722a..997e1963 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -93,8 +93,27 @@ class TukarGulingPO(models.Model): self.origin = self.operations.origin return - # Clear existing lines hanya jika tidak dari return picking - self.line_ids = [(5, 0, 0)] + if from_return_picking: + # Gunakan qty dari context (stock return wizard) + default_lines = self.env.context.get('default_line_ids', []) + parsed_lines = [] + sequence = 10 + for line_data in default_lines: + if isinstance(line_data, (list, tuple)) and len(line_data) == 3: + vals = line_data[2] + parsed_lines.append((0, 0, { + 'sequence': sequence, + 'product_id': vals.get('product_id'), + 'product_uom_qty': vals.get('quantity'), + 'product_uom': self.env['product.product'].browse(vals.get('product_id')).uom_id.id, + 'name': self.env['product.product'].browse(vals.get('product_id')).display_name, + })) + sequence += 10 + + self.line_ids = parsed_lines + return + else: + self.line_ids = [(5, 0, 0)] # Set origin dari operations if self.operations.origin: @@ -445,19 +464,15 @@ class TukarGulingPO(models.Model): }) return_lines = [] - for line in record.line_ids: - move = picking.move_lines.filtered(lambda m: m.product_id == line.product_id) - if move: - return_lines.append((0, 0, { - 'product_id': line.product_id.id, - 'quantity': line.product_uom_qty, - 'move_id': move[0].id, - })) - else: - raise UserError( - _("Tidak ditemukan move line di picking %s untuk produk %s") % - (picking.name, line.product_id.display_name) - ) + + for move in picking.move_lines: + line = record.line_ids.filtered(lambda l: l.product_id == move.product_id) + qty = line.product_uom_qty if line else 0.0 + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': qty, + 'move_id': move.id, + })) if not return_lines: raise UserError(_("Tidak ada product line valid untuk retur picking %s") % picking.name) -- cgit v1.2.3 From c6f3edf6eaf705511b926b961b7ae4fcf017e17f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 18 Jul 2025 14:05:48 +0700 Subject: don --- indoteknik_custom/models/stock_picking_return.py | 4 +- indoteknik_custom/models/tukar_guling_po.py | 116 ++++++++++++++++------- 2 files changed, 84 insertions(+), 36 deletions(-) diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py index e274a147..1fc8d088 100644 --- a/indoteknik_custom/models/stock_picking_return.py +++ b/indoteknik_custom/models/stock_picking_return.py @@ -5,7 +5,7 @@ import logging _logger = logging.getLogger(__name__) -class StockReturnPicking(models.TransientModel): +class ReturnPicking(models.TransientModel): _inherit = 'stock.return.picking' # return_type = fields.Selection([ @@ -26,7 +26,7 @@ class StockReturnPicking(models.TransientModel): if self._context.get('from_ui', True): return self._redirect_to_tukar_guling() - return super(StockReturnPicking, self).create_returns() + return super(ReturnPicking, self).create_returns() def _redirect_to_tukar_guling(self): """Redirect ke Tukar Guling SO atau PO form dengan pre-filled data""" diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 997e1963..72417a72 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -238,7 +238,8 @@ class TukarGulingPO(models.Model): def _is_already_returned(self, picking): return self.env['stock.picking'].search_count([ ('origin', '=', 'Return of %s' % picking.name), - ('state', '!=', 'cancel') + # ('returned_from_id', '=', picking.id), + ('state', 'not in', ['cancel', 'draft']), ]) > 0 def copy(self, default=None): @@ -286,9 +287,9 @@ class TukarGulingPO(models.Model): if self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': raise UserError("❌ BU/INPUT tidak boleh di retur tukar guling") - if self.operations.picking_type_id.id != 28: - if self._is_already_returned(self.operations): - raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + # if self.operations.picking_type_id.id != 28: + # if self._is_already_returned(self.operations): + # raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if 'operations' in vals and not vals.get('origin'): picking = self.env['stock.picking'].browse(vals['operations']) if picking.origin: @@ -413,6 +414,14 @@ class TukarGulingPO(models.Model): group = record.operations.group_id bu_inputs = bu_puts = self.env['stock.picking'] + # Buat qty map awal dari line_ids + bu_input_qty_map = { + line.product_id.id: line.product_uom_qty + for line in record.line_ids + if line.product_id and line.product_uom_qty > 0 + } + bu_put_qty_map = bu_input_qty_map.copy() + if group: po_pickings = self.env['stock.picking'].search([ ('group_id', '=', group.id), @@ -423,27 +432,28 @@ class TukarGulingPO(models.Model): else: raise UserError("Group ID tidak ditemukan pada BU Operations.") - def _create_return_from_picking(picking): + def _create_return_from_picking(picking, qty_map): if not picking: return self.env['stock.picking'] grup = record.operations.group_id - # Tentukan location + # Tentukan lokasi PARTNER_LOCATION_ID = 4 BU_INPUT_LOCATION_ID = 58 BU_STOCK_LOCATION_ID = 57 - if picking.picking_type_id.id == 28: + picking_type = picking.picking_type_id.id + if picking_type == 28: default_location_id = BU_INPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID - elif picking.picking_type_id.id == 75: + elif picking_type == 75: default_location_id = BU_STOCK_LOCATION_ID default_location_dest_id = BU_INPUT_LOCATION_ID - elif picking.picking_type_id.id == 77: + elif picking_type == 77: default_location_id = BU_INPUT_LOCATION_ID default_location_dest_id = BU_STOCK_LOCATION_ID - elif picking.picking_type_id.id == 76: + elif picking_type == 76: default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_INPUT_LOCATION_ID else: @@ -464,18 +474,46 @@ class TukarGulingPO(models.Model): }) return_lines = [] + moves = getattr(picking, 'move_ids_without_package', False) or picking.move_lines + + for move in moves: + product = move.product_id + if not product: + continue + + pid = product.id + available_qty = qty_map.get(pid, 0.0) + move_qty = move.product_uom_qty + allocate_qty = min(available_qty, move_qty) + + if allocate_qty <= 0: + continue - for move in picking.move_lines: - line = record.line_ids.filtered(lambda l: l.product_id == move.product_id) - qty = line.product_uom_qty if line else 0.0 return_lines.append((0, 0, { - 'product_id': move.product_id.id, - 'quantity': qty, + 'product_id': pid, + 'quantity': allocate_qty, 'move_id': move.id, })) + qty_map[pid] -= allocate_qty + + _logger.info(f"📦 Alokasi {allocate_qty} untuk {product.display_name} | Sisa: {qty_map[pid]}") if not return_lines: - raise UserError(_("Tidak ada product line valid untuk retur picking %s") % picking.name) + # Tukar Guling lanjut dari PRT/VRT + if picking.picking_type_id.id in [76, 77]: + for move in moves: + if move.product_uom_qty > 0: + return_lines.append((0, 0, { + 'product_id': move.product_id.id, + 'quantity': move.product_uom_qty, + 'move_id': move.id, + })) + _logger.info( + f"🔁 TG lanjutan: Alokasi {move.product_uom_qty} untuk {move.product_id.display_name}") + else: + _logger.warning( + f"⏭️ Skipped return picking {picking.name}, tidak ada qty yang bisa dialokasikan.") + return self.env['stock.picking'] return_wizard.product_return_moves = return_lines return_vals = return_wizard.create_returns() @@ -488,42 +526,52 @@ class TukarGulingPO(models.Model): 'tukar_guling_po_id': record.id, }) - for move in return_picking.move_lines: - move.write({ - 'location_id': default_location_id, - 'location_dest_id': default_location_dest_id, - }) - return return_picking - # === Eksekusi pembuatan picking === + # ============================ + # Eksekusi utama return logic + # ============================ + if record.operations.picking_type_id.id == 28: - # Kalau dari BU INPUT → hanya PRT - prt = _create_return_from_picking(record.operations) + # Dari BU INPUT langsung buat PRT + prt = _create_return_from_picking(record.operations, bu_input_qty_map) if prt: created_returns |= prt else: - # 1. Dari BU PUT buat VRT - for bu_put in bu_puts: - vrt = _create_return_from_picking(bu_put) + # ✅ Pairing BU PUT ↔ BU INPUT + # Temukan index dari BU PUT yang dipilih user + try: + bu_put_index = sorted(bu_puts, key=lambda p: p.name).index(record.operations) + except ValueError: + raise UserError("Dokumen BU PUT yang dipilih tidak ditemukan dalam daftar BU PUT.") + + # Ambil pasangannya di BU INPUT (asumsi urutan sejajar) + sorted_bu_puts = sorted(bu_puts, key=lambda p: p.name) + sorted_bu_inputs = sorted(bu_inputs, key=lambda p: p.name) + + if bu_put_index >= len(sorted_bu_inputs): + raise UserError("Tidak ditemukan pasangan BU INPUT untuk BU PUT yang dipilih.") + + paired = [(sorted_bu_puts[bu_put_index], sorted_bu_inputs[bu_put_index])] + + for bu_put, bu_input in paired: + vrt = _create_return_from_picking(bu_put, bu_put_qty_map) if vrt: created_returns |= vrt - # 2. Dari BU INPUT buat PRT - for bu_input in bu_inputs: - prt = _create_return_from_picking(bu_input) + prt = _create_return_from_picking(bu_input, bu_input_qty_map) if prt: created_returns |= prt - # 3. Kalau tukar guling buat lanjut INPUT & PUT + # 🌀 Tukar Guling: buat dokumen baru dari PRT & VRT if record.return_type == 'tukar_guling': for prt in created_returns.filtered(lambda p: p.picking_type_id.id == 76): - bu_input = _create_return_from_picking(prt) + bu_input = _create_return_from_picking(prt, bu_input_qty_map) if bu_input: created_returns |= bu_input for vrt in created_returns.filtered(lambda p: p.picking_type_id.id == 77): - bu_put = _create_return_from_picking(vrt) + bu_put = _create_return_from_picking(vrt, bu_put_qty_map) if bu_put: created_returns |= bu_put -- cgit v1.2.3 From 2c39fffe188f44b93b73d8f5470fb228225cb631 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 18 Jul 2025 17:25:29 +0700 Subject: readonly qty done mapping tuk.gul --- indoteknik_custom/views/tukar_guling.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index acc48d3f..44b5134e 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -104,7 +104,7 @@ - + -- cgit v1.2.3 From be0e6d6e04c85f0c2f77a490074dbeb7de98be0f Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 18 Jul 2025 17:29:14 +0700 Subject: validasi qty barang tidak sesuai --- indoteknik_custom/models/tukar_guling.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index d8e30006..a08d29bd 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -413,6 +413,14 @@ class TukarGuling(models.Model): raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT suda Done!") if self._is_already_returned(self.operations): raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + + for line in self.line_ids: + mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id) + total_qty = sum(l.qty_return for l in mapping_lines) + if total_qty != line.product_uom_qty: + raise UserError( + _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name) + self._check_invoice_on_revisi_so() self._validate_product_lines() @@ -429,6 +437,13 @@ class TukarGuling(models.Model): operasi = self.operations.picking_type_id.id tipe = self.return_type + for line in self.line_ids: + mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id) + total_qty = sum(l.qty_return for l in mapping_lines) + if total_qty != line.product_uom_qty: + raise UserError( + _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name) + if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done': raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done") if operasi == 30 and tipe == 'tukar_guling': -- cgit v1.2.3 From 69f0fbf98183ea00bb069df3ecd40e91df7081c3 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 22 Jul 2025 13:00:41 +0700 Subject: fix error when 2 item return --- indoteknik_custom/models/tukar_guling.py | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index a08d29bd..e546ad9c 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -99,16 +99,16 @@ class TukarGuling(models.Model): tg_product_ids = self.line_ids.mapped('product_id.id') for koli_line in self.operations.konfirm_koli_lines: - pick_move = koli_line.pick_id.move_line_ids_without_package - if pick_move.product_id.id in tg_product_ids: - mapping_koli_data.append((0, 0, { - 'sequence': sequence, - 'pick_id': koli_line.pick_id.id, - 'product_id': pick_move.product_id.id, - 'qty_done': pick_move.qty_done, - 'qty_return': 0 - })) - sequence += 10 + for move in koli_line.pick_id.move_line_ids_without_package: + if move.product_id.id in tg_product_ids: + mapping_koli_data.append((0, 0, { + 'sequence': sequence, + 'pick_id': koli_line.pick_id.id, + 'product_id': move.product_id.id, + 'qty_done': move.qty_done, + 'qty_return': 0 + })) + sequence += 10 self.mapping_koli_ids = mapping_koli_data _logger.info(f"✅ Created {len(mapping_koli_data)} mapping koli lines (from return wizard)") @@ -128,7 +128,6 @@ class TukarGuling(models.Model): # Untuk Odoo 14, gunakan move_ids_without_package atau move_lines moves_to_check = [] - if hasattr(self.operations, 'move_ids_without_package') and self.operations.move_ids_without_package: moves_to_check = self.operations.move_ids_without_package elif hasattr(self.operations, 'move_lines') and self.operations.move_lines: @@ -160,7 +159,7 @@ class TukarGuling(models.Model): if lines_data: self.line_ids = lines_data - _logger.info(f"Created {len(lines_data)} product lines") + _logger.info(f"✅ Created {len(lines_data)} product lines") # Prepare mapping koli jika BU/OUT mapping_koli_data = [] @@ -169,23 +168,23 @@ class TukarGuling(models.Model): if self.operations.picking_type_id.id == 29: tg_product_ids = [p for p in product_data] for koli_line in self.operations.konfirm_koli_lines: - pick_move = koli_line.pick_id.move_line_ids_without_package - if pick_move.product_id.id in tg_product_ids: - mapping_koli_data.append((0, 0, { - 'sequence': sequence, - 'pick_id': koli_line.pick_id.id, - 'product_id': pick_move.product_id.id, - 'qty_done': pick_move.qty_done - })) - sequence += 10 + for move in koli_line.pick_id.move_line_ids_without_package: + if move.product_id.id in tg_product_ids: + mapping_koli_data.append((0, 0, { + 'sequence': sequence, + 'pick_id': koli_line.pick_id.id, + 'product_id': move.product_id.id, + 'qty_done': move.qty_done + })) + sequence += 10 if mapping_koli_data: self.mapping_koli_ids = mapping_koli_data - _logger.info(f"Created {len(mapping_koli_data)} mapping koli lines") + _logger.info(f"✅ Created {len(mapping_koli_data)} mapping koli lines") else: - _logger.info("No mapping koli lines created") + _logger.info("⚠️ No mapping koli lines created") else: - _logger.info("No product lines created - no valid moves found") + _logger.info("⚠️ No product lines created - no valid moves found") else: from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) @@ -196,6 +195,7 @@ class TukarGuling(models.Model): self.origin = False + def action_populate_lines(self): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() -- cgit v1.2.3 From 1de8ad79dd0f0832dd14de9c1d004884f153bec4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 22 Jul 2025 16:42:10 +0700 Subject: Chatter and sequence --- indoteknik_custom/models/tukar_guling.py | 40 +++++++++++++++++++++++++------- indoteknik_custom/views/ir_sequence.xml | 6 +++-- indoteknik_custom/views/tukar_guling.xml | 17 ++++++++++---- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index e546ad9c..f27446d0 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -1,15 +1,25 @@ from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError import logging +from datetime import datetime _logger = logging.getLogger(__name__) +#TODO +# 1. tracking status dokumen BU [X] +# 2. ganti nama dokumen +# 3. Tracking ketika create dokumen [X] +# 4. Tracking ketika ganti field operations, date approval (sales, finance, logistic) [X] +# 5. Ganti proses approval ke Sales, Finance, Logistic [X] +# 6. Make sure bu pick dan out tidak bisa diedit ketika ort dan srt blm done +# 7. change approval class TukarGuling(models.Model): _name = 'tukar.guling' _description = 'Tukar Guling' _order = 'date desc, id desc' _rec_name = 'name' + _inherit = ['mail.thread', 'mail.activity.mixin'] origin = fields.Char(string='Origin SO') if_so = fields.Boolean('Is SO', default=True) @@ -38,24 +48,28 @@ class TukarGuling(models.Model): ('state', '=', 'done'), ('linked_manual_bu_out', '!=', 'done'), ], - help='Nomor BU/OUT atau BU/PICK' + help='Nomor BU/OUT atau BU/PICK', tracking=3, + required=True ) ba_num = fields.Text('Nomor BA') notes = fields.Text('Notes') return_type = fields.Selection(String='Return Type', selection=[ ('tukar_guling', 'Tukar Guling'), # -> barang yang sama - ('revisi_so', 'Revisi SO')], required=True) + ('revisi_so', 'Revisi SO')], required=True, tracking=3) state = fields.Selection(string='Status', selection=[ ('draft', 'Draft'), ('approval_sales', ' Approval Sales'), - ('approval_logistic', 'Approval Logistic'), ('approval_finance', 'Approval Finance'), + ('approval_logistic', 'Approval Logistic'), ('done', 'Done'), ('cancel', 'Canceled') ], default='draft', tracking=True, required=True) line_ids = fields.One2many('tukar.guling.line', 'tukar_guling_id', string='Product Lines') mapping_koli_ids = fields.One2many('tukar.guling.mapping.koli', 'tukar_guling_id', string='Mapping Koli') + date_done = fields.Datetime('Approved Date Finance', tracking=3, readonly=True) + date_sales = fields.Datetime('Approved Date Sales', tracking=3, readonly=True) + date_logistic = fields.Datetime('Approved Date Logistic', tracking=3, readonly=True) def _check_mapping_koli(self): for record in self: @@ -295,7 +309,9 @@ class TukarGuling(models.Model): if picking.origin: vals['origin'] = picking.origin - return super(TukarGuling, self).create(vals) + res = super(TukarGuling, self).create(vals) + res.message_post(body=_("CCM Created By %s" ) % self.env.user.name) + return res def copy(self, default=None): if default is None: @@ -457,23 +473,29 @@ class TukarGuling(models.Model): if not self.return_type: raise UserError("Return Type harus diisi!") + now = datetime.now() + # Cek hak akses berdasarkan state for rec in self: if rec.state == 'approval_sales': if not rec.env.user.has_group('indoteknik_custom.group_role_sales'): raise UserError("Hanya Sales Manager yang boleh approve tahap ini.") rec.state = 'approval_logistic' - - elif rec.state == 'approval_logistic': - if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'): - raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") - rec.state = 'approval_finance' + rec.date_sales = now elif rec.state == 'approval_finance': if not rec.env.user.has_group('indoteknik_custom.group_role_fat'): raise UserError("Hanya Finance Manager yang boleh approve tahap ini.") rec.state = 'done' + rec.date_done = now rec._create_pickings() + + elif rec.state == 'approval_logistic': + if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'): + raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.") + rec.state = 'approval_finance' + rec.date_logistic = now + else: raise UserError("Status ini tidak bisa di-approve.") diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml index 3f0ba0b6..03cfbc1a 100644 --- a/indoteknik_custom/views/ir_sequence.xml +++ b/indoteknik_custom/views/ir_sequence.xml @@ -203,7 +203,8 @@ Pengajuan Return SO tukar.guling - CCM/ + TRUE + CCM/%(year)s/ 5 1 1 @@ -211,7 +212,8 @@ Pengajuan Return PO tukar.guling.po - VCM/ + TRUE + VCM/%(year)s/ 5 1 1 diff --git a/indoteknik_custom/views/tukar_guling.xml b/indoteknik_custom/views/tukar_guling.xml index 44b5134e..6b3492ae 100644 --- a/indoteknik_custom/views/tukar_guling.xml +++ b/indoteknik_custom/views/tukar_guling.xml @@ -24,14 +24,16 @@ + - + + @@ -47,7 +49,7 @@ attrs="{'invisible': [('state', '!=', 'draft')]}"/> - +
- + -->
@@ -176,7 +176,7 @@ - +
- + -- cgit v1.2.3 From 05d63523d021deb820724b90b1c36411d270f39f Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 24 Jul 2025 13:12:31 +0700 Subject: undo refund system --- indoteknik_custom/views/account_move.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml index 59e89210..9b1c791b 100644 --- a/indoteknik_custom/views/account_move.xml +++ b/indoteknik_custom/views/account_move.xml @@ -36,7 +36,7 @@
- + -- cgit v1.2.3 From bdf6d7dbd8850d525809726036c6783b9113028f Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Thu, 24 Jul 2025 13:33:07 +0700 Subject: (andri) fix raise ppn notif --- indoteknik_custom/models/purchase_order.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py index 5b9e1acb..a46c51d1 100755 --- a/indoteknik_custom/models/purchase_order.py +++ b/indoteknik_custom/models/purchase_order.py @@ -1043,7 +1043,7 @@ class PurchaseOrder(models.Model): for line in self.order_line: if line.taxes_id != reference_taxes: - raise UserError("PPN harus sama untuk semua baris pada line.") + raise UserError(f"PPN harus sama untuk semua baris pada line {line.product_id.name}") def check_data_vendor(self): vendor = self.partner_id -- cgit v1.2.3 From cbe2c5730bcd82ce594e03425c987b17c583b893 Mon Sep 17 00:00:00 2001 From: it-fixcomart Date: Thu, 24 Jul 2025 14:44:23 +0700 Subject: add journals in so --- indoteknik_custom/models/sale_order.py | 42 ++++++++++++++++++++++++++++++---- indoteknik_custom/views/sale_order.xml | 8 +++---- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index 995cafba..febdaabd 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -364,6 +364,17 @@ class SaleOrder(models.Model): compute='_compute_advance_payment_move', string='Advance Payment Move', ) + advance_payment_move_ids = fields.Many2many( + 'account.move', + compute='_compute_advance_payment_moves', + string='All Advance Payment Moves', + ) + + advance_payment_move_count = fields.Integer( + string='Jumlah Jurnal Uang Muka', + compute='_compute_advance_payment_moves', + store=False + ) @api.depends('order_line.product_id', 'date_order') def _compute_et_products(self): @@ -3191,15 +3202,38 @@ class SaleOrder(models.Model): ('state', '=', 'posted'), ], limit=1, order="id desc") order.advance_payment_move_id = move + + @api.depends('invoice_ids') + def _compute_advance_payment_moves(self): + for order in self: + moves = self.env['account.move'].search([ + ('sale_id', '=', order.id), + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ]) + order.advance_payment_move_ids = moves + + @api.depends('invoice_ids') + def _compute_advance_payment_moves(self): + for order in self: + moves = self.env['account.move'].search([ + ('sale_id', '=', order.id), + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ]) + order.advance_payment_move_ids = moves + order.advance_payment_move_count = len(moves) - def action_open_advance_payment_move(self): + def action_open_advance_payment_moves(self): self.ensure_one() - if not self.advance_payment_move_id: + moves = self.advance_payment_move_ids + if not moves: return return { 'type': 'ir.actions.act_window', + 'name': 'Journals Sales Order', 'res_model': 'account.move', - 'res_id': self.advance_payment_move_id.id, - 'view_mode': 'form', + 'view_mode': 'tree,form', + 'domain': [('id', 'in', moves.ids)], 'target': 'current', } \ No newline at end of file diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml index 346dc0f8..5bcd7641 100755 --- a/indoteknik_custom/views/sale_order.xml +++ b/indoteknik_custom/views/sale_order.xml @@ -43,13 +43,13 @@ attrs="{'invisible': ['|', ('state', 'not in', ['sale', 'done']), ('has_refund', '=', True)]}" /> -->
- - - refund.sale.order.tree - refund.sale.order - - - - - - - - - - - - - - - - - - - - - - - - refund.sale.order.form - refund.sale.order - -
-
-
- -
- -
- - - -

- -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
- - -
-
-
-
- - - Refund Sales Order - refund.sale.order - tree,form - - - - - -- cgit v1.2.3 From 77e76f376b78733cad58aa196e3ead3c6a04ff42 Mon Sep 17 00:00:00 2001 From: "Indoteknik ." Date: Sat, 26 Jul 2025 09:27:53 +0700 Subject: (andri) fix --- indoteknik_custom/security/ir.model.access.csv | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv index 0ac3e86c..015b257c 100755 --- a/indoteknik_custom/security/ir.model.access.csv +++ b/indoteknik_custom/security/ir.model.access.csv @@ -183,8 +183,7 @@ access_production_purchase_match,access.production.purchase.match,model_producti access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1 access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1 access_approval_payment_term,access.approval.payment.term,model_approval_payment_term,,1,1,1,1 -access_refund_sale_order,access.refund.sale.order,model_refund_sale_order,base.group_user,1,1,1,1 -access_refund_sale_order_line,access.refund.sale.order.line,model_refund_sale_order_line,base.group_user,1,1,1,1 + access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1 access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1 -- cgit v1.2.3