From de50bcf16b21abf7b8e45fb59b366c594bc00038 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 3 Jul 2025 19:55:49 +0700 Subject: start tukar guling po --- indoteknik_custom/models/tukar_guling_po.py | 485 ++++++++++++++++++++++++++++ 1 file changed, 485 insertions(+) create mode 100644 indoteknik_custom/models/tukar_guling_po.py (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py new file mode 100644 index 00000000..4ed363cf --- /dev/null +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -0,0 +1,485 @@ +from email.policy import default + +from odoo import models, fields, api, _ +from odoo.exceptions import UserError, ValidationError +import logging + +_logger = logging.getLogger(__name__) + +class TukarGulingPO(models.Model): + _name = 'tukar.guling.po' + _description = 'Tukar Guling PO' + + origin = fields.Char(string='Origin PO') + is_po = fields.Boolean('Is PO', default=True) + is_so = fields.Boolean('Is SO', default=False) + name = fields.Char(string='Name', required=True) + po_picking_ids = fields.One2many( + 'stock.picking', + 'tukar_guling_po_id', + string='Picking Reference', + ) + 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', + string='Operations', + domain=[ + ('picking_type_id.id', 'in', [75, 32]), + ('state', '=', 'done') + ],help='Nomor BU INPUT atau BU PUT' + ) + ba_num = fields.Char('Nomor BA') + return_type = fields.Selection([ + ('revisi_po', 'Revisi PO'), + ('tukar_guling', 'Tukar Guling'), + ], string='Return Type', required=True) + notes = fields.Text('Notes') + tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', ondelete='cascade') + line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines') + state = fields.Selection([ + ('draft', 'Draft'), + ('approval_purchase', 'Approval Purchasing'), + ('approval_logistic', 'Approval Logistic'), + ('approval_finance', 'Approval Finance'), + ('done', 'Done'), + ('cancel', 'Cancel'), + ], string='Status', default='draft') + + @api.model + def create(self, vals): + # Generate sequence number + if not vals.get('name') or vals['name'] == 'New': + sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling.po')], 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.po') or 'embo==' + + # 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(TukarGulingPO, self).create(vals) + + @api.onchange('operations') + def _onchange_operations(self): + """Auto-populate lines ketika operations dipilih""" + if self.operations: + 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 + 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 + + 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, 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() + if not self.operations: + raise UserError("Pilih BU/OUT atau BU/PICK 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_po', 'tukar_guling'] and not record.operations: + raise ValidationError("Operations 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 + + def _is_already_returned(self, picking): + return self.env['stock.picking'].search_count([ + ('origin', '=', 'Return of %s' % picking.name), + ('state', '!=', 'cancel') + ]) > 0 + + def copy(self, default=None): + if default is None: + default = {} + + # Generate new sequence untuk duplicate + sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling.po')], limit=1) + if sequence: + default['name'] = sequence.next_by_id() + else: + default['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') or 'copy' + + default.update({ + 'state': 'draft', + '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 write(self, vals): + if self.operations.picking_type_id.id != 32: + 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: + vals['origin'] = picking.origin + + return super(TukarGulingPO, self).write(vals) + + def action_view_picking(self): + self.ensure_one() + action = self.env.ref('stock.action_picking_tree_all').read()[0] + pickings = self.po_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 + return action + + 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() + picking = self.operations + if picking.picking_type_id.id == 75: + if picking.state != 'done': + raise UserError("BU/PUT belum Done!") + elif picking.picking_type_id.id == 32: + 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 self._is_already_returned(self.operations): + raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") + self._validate_product_lines() + + + if self.state != 'draft': + raise UserError("Submit hanya bisa dilakukan dari Draft.") + self.state = 'approval_purchase' + + def action_approve(self): + self.ensure_one() + self._validate_product_lines() + + if not self.operations: + raise UserError("Operations harus diisi!") + + if not self.return_type: + raise UserError("Return Type harus diisi!") + + # Cek hak akses berdasarkan state + for rec in self: + if rec.state == 'approval_purchase': + 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' + + 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._create_pickings() + 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' + + 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.") + + 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") + + # Fungsi membuat retur dari picking tertentu + def _create_return_from_picking(picking): + grup = self.operations.group_id + + PARTNER_LOCATION_ID = 5 + # BU_OUTPUT_LOCATION_ID = 60 + BU_INPUT_LOCATION_ID = 60 + 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 + 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 + 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 + 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, + 'default_location_id': default_location_id, + 'default_location_dest_id': default_location_dest_id, + 'from_ui': False, + }) + + 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 + }) + + # Create return lines + return_lines = [] + 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': line.product_id.id, + 'quantity': line.product_uom_qty, + 'move_id': move.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) + + if not return_picking: + raise UserError("Retur gagal dibuat. Hasil create_returns: %s" % str(return_vals)) + + # 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_po_id': record.id, + }) + + return return_picking + + if record.operations.picking_type_id.id == 76: + prt = _create_return_from_picking(record.operations) + 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': + if vrt: + bu_put = _create_return_from_picking(vrt) + if bu_put: + created_returns.append(bu_put) + + if prt: + bu_input = _create_return_from_picking(prt) + if bu_input: + created_returns.append(bu_input) + + if not created_returns: + raise UserError("wkwkwk") + + + +class TukarGulingLinePO(models.Model): + _name = 'tukar.guling.line.po' + _description = 'Tukar Guling PO Line' + + sequence = fields.Integer('Sequence', default=10, copy=False) + product_id = fields.Many2one('product.product', string='Product', required=True) + tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', ondelete='cascade') + 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') + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO Ref') \ No newline at end of file -- 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(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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 +++++++++++------------------ 1 file changed, 15 insertions(+), 24 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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.") -- 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 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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'): -- 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_po.py | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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): -- cgit v1.2.3 From aedf25e194d4ddddab9b158b473164151109edaf Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 9 Jul 2025 10:16:19 +0700 Subject: rev 77 vals bu input --- indoteknik_custom/models/tukar_guling_po.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index e9dfda33..aa8fd1f3 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -239,6 +239,13 @@ class TukarGulingPO(models.Model): def write(self, vals): self._check_bill_on_revisi_po() + tipe = vals.get('return_type', self.return_type) + + + 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.") -- cgit v1.2.3 From 717dbfa7070e94c0af2bb39e2cebb4dc71d123b9 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 9 Jul 2025 13:14:59 +0700 Subject: rev 77 vals vals --- indoteknik_custom/models/tukar_guling_po.py | 41 +++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 8 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index aa8fd1f3..9bcced0d 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -241,11 +241,22 @@ class TukarGulingPO(models.Model): self._check_bill_on_revisi_po() tipe = vals.get('return_type', self.return_type) + if self.operations and self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling': + group = self.operations.group_id + if group: + # Cari BU/PUT dalam group yang sama + bu_put = self.env['stock.picking'].search([ + ('group_id', '=', group.id), + ('picking_type_id.id', '=', 75), # 75 = ID BU/PUT + ('state', '=', 'done') + ], limit=1) + + if bu_put: + raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah Done!") 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.") @@ -287,19 +298,31 @@ class TukarGulingPO(models.Model): def action_submit(self): self.ensure_one() self._check_bill_on_revisi_po() + self._validate_product_lines() + + if self.operations and self.operations.picking_type_id.id == 28 and self.return_type == 'tukar_guling': + group = self.operations.group_id + if group: + # Cari BU/PUT dalam group yang sama + bu_put = self.env['stock.picking'].search([ + ('group_id', '=', group.id), + ('picking_type_id.id', '=', 75), + ('state', '=', 'done') + ], limit=1) + + if bu_put: + raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah Done!") + picking = self.operations if picking.picking_type_id.id == 75: if picking.state != 'done': raise UserError("BU/PUT belum Done!") - 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 != 28: - if self._is_already_returned(self.operations): - raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") - self._validate_product_lines() + raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!") + if self._is_already_returned(self.operations): + raise UserError("BU ini sudah pernah diretur oleh dokumen lain.") if self.state != 'draft': raise UserError("Submit hanya bisa dilakukan dari Draft.") @@ -457,10 +480,12 @@ class TukarGulingPO(models.Model): return return_picking + # Kalau retur BU/INPUT Buat prt saja if record.operations.picking_type_id.id == 28: prt = _create_return_from_picking(record.operations) if prt: created_returns.append(prt) + # Kalau retur BU/PUT else: vrt = _create_return_from_picking(bu_put_to_return) if vrt: -- cgit v1.2.3 From 2f90a316e0895b7b3167ee1ff36d282736456a2d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 14 Jul 2025 20:06:30 +0700 Subject: remove replace bu input name --- indoteknik_custom/models/tukar_guling_po.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 9bcced0d..3698f95a 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -472,11 +472,11 @@ class TukarGulingPO(models.Model): '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) + # # 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 -- cgit v1.2.3 From 15198bbee7d54580fadec9be6cbf69fb4a2d500d Mon Sep 17 00:00:00 2001 From: Miqdad Date: Mon, 14 Jul 2025 20:25:52 +0700 Subject: vals PO --- indoteknik_custom/models/tukar_guling_po.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 3698f95a..ba2b3800 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -6,6 +6,7 @@ import logging _logger = logging.getLogger(__name__) + class TukarGulingPO(models.Model): _name = 'tukar.guling.po' _description = 'Tukar Guling PO' @@ -27,7 +28,7 @@ class TukarGulingPO(models.Model): domain=[ ('picking_type_id.id', 'in', [75, 28]), ('state', '=', 'done') - ],help='Nomor BU INPUT atau BU PUT' + ], help='Nomor BU INPUT atau BU PUT' ) ba_num = fields.Char('Nomor BA') return_type = fields.Selection([ @@ -143,6 +144,15 @@ class TukarGulingPO(models.Model): self.origin = False + def _check_not_allow_tukar_guling_on_bu_input(self, return_type=None): + operasi = self.operations.picking_type_id.id + tipe = return_type or self.return_type + + if operasi == 28 and self.operations.linked_manual_bu_out.state == 'done': + raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah done") + if operasi == 28 and tipe == 'tukar_guling': + raise UserError("❌ BU/INPUT tidak boleh di retur tukar guling") + def action_populate_lines(self): """Manual button untuk populate lines - sebagai alternatif""" self.ensure_one() @@ -270,7 +280,7 @@ class TukarGulingPO(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") ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done') for picking in ongoing_bu: picking.action_cancel() @@ -299,6 +309,7 @@ class TukarGulingPO(models.Model): self.ensure_one() self._check_bill_on_revisi_po() self._validate_product_lines() + self._check_not_allow_tukar_guling_on_bu_input() if self.operations and self.operations.picking_type_id.id == 28 and self.return_type == 'tukar_guling': group = self.operations.group_id @@ -332,6 +343,7 @@ class TukarGulingPO(models.Model): self.ensure_one() self._validate_product_lines() self._check_bill_on_revisi_po() + self._check_not_allow_tukar_guling_on_bu_input() if not self.operations: raise UserError("Operations harus diisi!") @@ -511,7 +523,6 @@ class TukarGulingPO(models.Model): raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") - class TukarGulingLinePO(models.Model): _name = 'tukar.guling.line.po' _description = 'Tukar Guling PO Line' @@ -526,4 +537,4 @@ class TukarGulingLinePO(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' - tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO Ref') \ No newline at end of file + tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO Ref') -- cgit v1.2.3 From 4f8f59f274e8ea19fcaa5f1c6b0e6e30400c66f7 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 15 Jul 2025 09:12:40 +0700 Subject: try --- indoteknik_custom/models/tukar_guling_po.py | 89 ++++++++++++++--------------- 1 file changed, 43 insertions(+), 46 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index ba2b3800..6d7d7335 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -390,49 +390,40 @@ class TukarGulingPO(models.Model): created_returns = [] - # Ambil BU INPUT & BU PUT dari group yang sama group = record.operations.group_id - bu_input_to_return = bu_put_to_return = False + bu_inputs = bu_puts = self.env['stock.picking'] 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 + bu_inputs = po_pickings.filtered(lambda p: p.picking_type_id.id == 28) + bu_puts = po_pickings.filtered(lambda p: p.picking_type_id.id == 75) else: raise UserError("Group ID tidak ditemukan pada BU Operations.") - # Fungsi buat return picking + PARTNER_LOCATION_ID = 4 + BU_INPUT_LOCATION_ID = 58 + BU_STOCK_LOCATION_ID = 57 + def _create_return_from_picking(picking): - grup = record.operations.group_id + if not picking: + return None - PARTNER_LOCATION_ID = 4 - BU_INPUT_LOCATION_ID = 58 - BU_STOCK_LOCATION_ID = 57 + grup = record.operations.group_id - # Lokasi sesuai type + # Mapping lokasi sesuai picking 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: - # 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 == 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: @@ -452,7 +443,7 @@ class TukarGulingPO(models.Model): 'original_location_id': default_location_id }) - # Buat lines + # Sesuai line tukar guling return_lines = [] for line in record.line_ids: move = picking.move_lines.filtered(lambda m: m.product_id == line.product_id) @@ -462,6 +453,11 @@ class TukarGulingPO(models.Model): '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) + ) if not return_lines: return None @@ -470,7 +466,9 @@ class TukarGulingPO(models.Model): return_vals = return_wizard.create_returns() return_picking = self.env['stock.picking'].browse(return_vals.get('res_id')) - # Paksa locations di picking & moves + 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, @@ -478,46 +476,45 @@ class TukarGulingPO(models.Model): 'tukar_guling_po_id': record.id, }) + # Paksa lokasi di move lines juga 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 - # Kalau retur BU/INPUT Buat prt saja + # === Eksekusi pembuatan picking === if record.operations.picking_type_id.id == 28: + # Kalau dari BU INPUT → hanya PRT prt = _create_return_from_picking(record.operations) if prt: created_returns.append(prt) - # Kalau retur BU/PUT else: - 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) + # 1. Dari BU PUT buat VRT + for bu_put in bu_puts: + vrt = _create_return_from_picking(bu_put) + if vrt: + created_returns.append(vrt) + + # 2. Dari BU INPUT buat PRT + for bu_input in bu_inputs: + prt = _create_return_from_picking(bu_input) 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) + # 3. Kalau tukar guling buat lanjut INPUT & PUT + 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) + if bu_input: + created_returns.append(bu_input) + + for vrt in created_returns.filtered(lambda p: p.picking_type_id.id == 77): + 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.") -- cgit v1.2.3 From 85caa56671d90cde807c44179680ef790d1a58c5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Tue, 15 Jul 2025 11:59:36 +0700 Subject: tukar guling po vals rev --- indoteknik_custom/models/tukar_guling_po.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 6d7d7335..3292eb7d 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -311,7 +311,7 @@ class TukarGulingPO(models.Model): self._validate_product_lines() self._check_not_allow_tukar_guling_on_bu_input() - if self.operations and self.operations.picking_type_id.id == 28 and self.return_type == 'tukar_guling': + if self.operations.picking_type_id.id == 28: group = self.operations.group_id if group: # Cari BU/PUT dalam group yang sama @@ -325,11 +325,12 @@ class TukarGulingPO(models.Model): raise UserError("❌ Tidak bisa retur BU/INPUT karena BU/PUT sudah Done!") picking = self.operations - if picking.picking_type_id.id == 75: + pick_id = self.operations.picking_type_id.id + if pick_id == 75: if picking.state != 'done': raise UserError("BU/PUT belum Done!") - if picking.picking_type_id.id != 75 or picking.picking_type_id.id != 28: + if pick_id not in [75, 28]: raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!") if self._is_already_returned(self.operations): -- cgit v1.2.3 From 77d3bd2541a52270f03b84e38dd691630bcd82af Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 16 Jul 2025 08:47:04 +0700 Subject: mapping koli tukar guling --- indoteknik_custom/models/tukar_guling_po.py | 37 +++++++++++++---------------- 1 file changed, 16 insertions(+), 21 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 3292eb7d..88c4722a 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -389,7 +389,7 @@ class TukarGulingPO(models.Model): if not record.operations: raise UserError("BU Operations belum dipilih.") - created_returns = [] + created_returns = self.env['stock.picking'] group = record.operations.group_id bu_inputs = bu_puts = self.env['stock.picking'] @@ -404,17 +404,17 @@ class TukarGulingPO(models.Model): else: raise UserError("Group ID tidak ditemukan pada BU Operations.") - PARTNER_LOCATION_ID = 4 - BU_INPUT_LOCATION_ID = 58 - BU_STOCK_LOCATION_ID = 57 - def _create_return_from_picking(picking): if not picking: - return None + return self.env['stock.picking'] grup = record.operations.group_id - # Mapping lokasi sesuai picking type + # Tentukan location + PARTNER_LOCATION_ID = 4 + BU_INPUT_LOCATION_ID = 58 + BU_STOCK_LOCATION_ID = 57 + if picking.picking_type_id.id == 28: default_location_id = BU_INPUT_LOCATION_ID default_location_dest_id = PARTNER_LOCATION_ID @@ -428,7 +428,7 @@ class TukarGulingPO(models.Model): default_location_id = PARTNER_LOCATION_ID default_location_dest_id = BU_INPUT_LOCATION_ID else: - return None + return self.env['stock.picking'] return_context = dict(self.env.context) return_context.update({ @@ -444,7 +444,6 @@ class TukarGulingPO(models.Model): 'original_location_id': default_location_id }) - # Sesuai line tukar guling return_lines = [] for line in record.line_ids: move = picking.move_lines.filtered(lambda m: m.product_id == line.product_id) @@ -456,20 +455,17 @@ class TukarGulingPO(models.Model): })) else: raise UserError( - _("Tidak ditemukan move line di picking %s untuk produk %s") - % (picking.name, line.product_id.display_name) + _("Tidak ditemukan move line di picking %s untuk produk %s") % + (picking.name, line.product_id.display_name) ) if not return_lines: - return None + 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, @@ -477,7 +473,6 @@ class TukarGulingPO(models.Model): 'tukar_guling_po_id': record.id, }) - # Paksa lokasi di move lines juga for move in return_picking.move_lines: move.write({ 'location_id': default_location_id, @@ -491,31 +486,31 @@ class TukarGulingPO(models.Model): # Kalau dari BU INPUT → hanya PRT prt = _create_return_from_picking(record.operations) if prt: - created_returns.append(prt) + created_returns |= prt else: # 1. Dari BU PUT buat VRT for bu_put in bu_puts: vrt = _create_return_from_picking(bu_put) if vrt: - created_returns.append(vrt) + created_returns |= vrt # 2. Dari BU INPUT buat PRT for bu_input in bu_inputs: prt = _create_return_from_picking(bu_input) if prt: - created_returns.append(prt) + created_returns |= prt # 3. Kalau tukar guling buat lanjut INPUT & PUT 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) if bu_input: - created_returns.append(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) if bu_put: - created_returns.append(bu_put) + created_returns |= bu_put if not created_returns: raise UserError("Tidak ada dokumen retur yang berhasil dibuat.") -- 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(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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/tukar_guling_po.py | 116 ++++++++++++++++++++-------- 1 file changed, 82 insertions(+), 34 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') 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 1dbe906db264189b69a10633b3e7516650b3dfc5 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 23 Jul 2025 13:11:17 +0700 Subject: PO fix sequence on create and add vendor name --- indoteknik_custom/models/tukar_guling_po.py | 45 +++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 12 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 72417a72..d2390854 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -10,7 +10,9 @@ _logger = logging.getLogger(__name__) class TukarGulingPO(models.Model): _name = 'tukar.guling.po' _description = 'Tukar Guling PO' + _inherit = ['mail.thread', 'mail.activity.mixin'] + vendor_id = fields.Many2one('res.partner', string='Vendor Name', readonly=True) origin = fields.Char(string='Origin PO') is_po = fields.Boolean('Is PO', default=True) is_so = fields.Boolean('Is SO', default=False) @@ -50,21 +52,22 @@ class TukarGulingPO(models.Model): @api.model def create(self, vals): # Generate sequence number + # ven_name = self.origin.search([('name', 'ilike', vals['origin'])]) if not vals.get('name') or vals['name'] == 'New': - sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling.po')], 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.po') or 'new' + vals['name'] = self.env['ir.sequence'].next_by_code('tukar.guling.po') # 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 + if picking.group_id.id: + vals['vendor_id'] = picking.group_id.partner_id.id + + res = super(TukarGulingPO, self).create(vals) + res.message_post(body=_("Tukar Guling PO Created By %s") % self.env.user.name) - return super(TukarGulingPO, self).create(vals) + return res @api.constrains('return_type', 'operations') def _check_bill_on_revisi_po(self): @@ -377,16 +380,16 @@ class TukarGulingPO(models.Model): if rec.state == 'approval_purchase': 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' 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 = '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 = 'done' rec._create_pickings() else: @@ -594,3 +597,21 @@ class TukarGulingLinePO(models.Model): class StockPicking(models.Model): _inherit = 'stock.picking' tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO Ref') + + + def button_validate(self): + res = super(StockPicking, self).button_validate() + for picking in self: + if picking.tukar_guling_po_id: + message = _( + "📦 %s Validated by %s Status Changed %s at %s." + ) % ( + picking.name, + # picking.picking_type_id.name, + picking.env.user.name, + picking.state, + fields.Datetime.now().strftime("%d/%m/%Y %H:%M") + ) + picking.tukar_guling_po_id.message_post(body=message) + + return res \ No newline at end of file -- cgit v1.2.3 From c7c058bcffef14b7fc08ee459d4198f1dd89dfc2 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 23 Jul 2025 13:16:47 +0700 Subject: fix state vcm --- indoteknik_custom/models/tukar_guling_po.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index d2390854..6a349a09 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -43,8 +43,8 @@ class TukarGulingPO(models.Model): state = fields.Selection([ ('draft', 'Draft'), ('approval_purchase', 'Approval Purchasing'), - ('approval_logistic', 'Approval Logistic'), ('approval_finance', 'Approval Finance'), + ('approval_logistic', 'Approval Logistic'), ('done', 'Done'), ('cancel', 'Cancel'), ], string='Status', default='draft') @@ -214,7 +214,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_purchase', 'approval_logistic', 'approval_finance', + if record.state in ('approval_purchase', 'approval_finance', 'approval_logistic', 'done') and not record.line_ids: raise ValidationError("Product lines harus diisi sebelum submit atau approve!") -- cgit v1.2.3 From aa3b585bb0531c8d7af4402a58dd297ab2a17e9a Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 23 Jul 2025 13:34:28 +0700 Subject: approved date --- indoteknik_custom/models/tukar_guling_po.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 6a349a09..3f072b88 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -3,6 +3,7 @@ from email.policy import default from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError import logging +from datetime import datetime _logger = logging.getLogger(__name__) @@ -24,22 +25,25 @@ 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) + date_purchase = fields.Datetime('Date Approve Purchase', required=True, readonly=True) + date_finance = fields.Datetime('Date Approve Finance', required=True, readonly=True) + date_logistic = fields.Datetime('Date Approve Logistic', required=True, readonly=True) operations = fields.Many2one( 'stock.picking', string='Operations', domain=[ ('picking_type_id.id', 'in', [75, 28]), ('state', '=', 'done') - ], help='Nomor BU INPUT atau BU PUT' + ], help='Nomor BU INPUT atau BU PUT', tracking=3 ) - ba_num = fields.Char('Nomor BA') + ba_num = fields.Char('Nomor BA', tracking=3) return_type = fields.Selection([ ('revisi_po', 'Revisi PO'), ('tukar_guling', 'Tukar Guling'), - ], string='Return Type', required=True) - notes = fields.Text('Notes') + ], string='Return Type', required=True, tracking=3) + notes = fields.Text('Notes', tracking=3) tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', ondelete='cascade') - line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines') + line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines', tracking=3) state = fields.Selection([ ('draft', 'Draft'), ('approval_purchase', 'Approval Purchasing'), @@ -47,7 +51,7 @@ class TukarGulingPO(models.Model): ('approval_logistic', 'Approval Logistic'), ('done', 'Done'), ('cancel', 'Cancel'), - ], string='Status', default='draft') + ], string='Status', default='draft', tracking=3) @api.model def create(self, vals): @@ -375,23 +379,28 @@ class TukarGulingPO(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_purchase': 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_finance' + rec.date_purchase = 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 = 'approval_logistic' + rec.date_finance = now 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 = 'done' rec._create_pickings() + rec.date_logistic = now else: raise UserError("Status ini tidak bisa di-approve.") -- cgit v1.2.3 From 712417255817d5142c28464818ca040a53517a09 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Wed, 23 Jul 2025 14:51:03 +0700 Subject: rev message post --- indoteknik_custom/models/tukar_guling_po.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 3f072b88..5f990525 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -69,7 +69,7 @@ class TukarGulingPO(models.Model): vals['vendor_id'] = picking.group_id.partner_id.id res = super(TukarGulingPO, self).create(vals) - res.message_post(body=_("Tukar Guling PO Created By %s") % self.env.user.name) + res.message_post(body=_("VCM Created By %s") % self.env.user.name) return res -- cgit v1.2.3 From b8efc85091fe0af596872bffeb3cf6c78fe2beed Mon Sep 17 00:00:00 2001 From: Miqdad Date: Thu, 24 Jul 2025 17:35:11 +0700 Subject: cant delete when done and in approval state --- indoteknik_custom/models/tukar_guling_po.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index b3279077..7c9680f8 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -90,6 +90,9 @@ class TukarGulingPO(models.Model): @api.onchange('operations') def _onchange_operations(self): """Auto-populate lines ketika operations dipilih""" + if self.operations.picking_type_id.id not in [75, 28]: + raise UserError("❌ Picking type harus BU/INPUT atau BU/PUT") + if self.operations: from_return_picking = self.env.context.get('from_return_picking', False) or \ self.env.context.get('default_line_ids', False) @@ -275,6 +278,8 @@ class TukarGulingPO(models.Model): return new_record def write(self, vals): + if self.operations.picking_type_id.id not in [75, 28]: + raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!") self._check_bill_on_revisi_po() tipe = vals.get('return_type', self.return_type) @@ -621,6 +626,18 @@ class TukarGulingLinePO(models.Model): product_uom = fields.Many2one('uom.uom', string='Unit of Measure') name = fields.Text('Description') + @api.constrains('product_uom_qty') + def _check_qty_change_allowed(self): + for rec in self: + if rec.tukar_guling_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']: + raise ValidationError("Tidak bisa mengubah Quantity karena status dokumen bukan Draft atau Cancel.") + + def unlink(self): + for rec in self: + if rec.tukar_guling_po_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']: + raise UserError("Tidak bisa menghapus data karena status dokumen bukan Draft atau Cancel.") + return super(TukarGulingLinePO, self).unlink() + class StockPicking(models.Model): _inherit = 'stock.picking' -- cgit v1.2.3 From 5d8b4784752a24ac7b73b4b53fd7468ee881f9f4 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 25 Jul 2025 10:28:55 +0700 Subject: change desc --- indoteknik_custom/models/tukar_guling_po.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 7c9680f8..2dc11efb 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -10,7 +10,7 @@ _logger = logging.getLogger(__name__) class TukarGulingPO(models.Model): _name = 'tukar.guling.po' - _description = 'Tukar Guling PO' + _description = 'Pengajuan Retur PO' _inherit = ['mail.thread', 'mail.activity.mixin'] vendor_id = fields.Many2one('res.partner', string='Vendor Name', readonly=True) -- cgit v1.2.3 From 9be22926964e36580ab51e5d30858c4d89071231 Mon Sep 17 00:00:00 2001 From: Miqdad Date: Fri, 25 Jul 2025 11:44:06 +0700 Subject: typo --- indoteknik_custom/models/tukar_guling_po.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indoteknik_custom/models/tukar_guling_po.py') diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py index 2dc11efb..14f2cc96 100644 --- a/indoteknik_custom/models/tukar_guling_po.py +++ b/indoteknik_custom/models/tukar_guling_po.py @@ -629,7 +629,7 @@ class TukarGulingLinePO(models.Model): @api.constrains('product_uom_qty') def _check_qty_change_allowed(self): for rec in self: - if rec.tukar_guling_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']: + if rec.tukar_guling_po_id and rec.tukar_guling_po_id.state not in ['draft', 'cancel']: raise ValidationError("Tidak bisa mengubah Quantity karena status dokumen bukan Draft atau Cancel.") def unlink(self): -- cgit v1.2.3