diff options
| author | Miqdad <ahmadmiqdad27@gmail.com> | 2025-06-19 13:05:31 +0700 |
|---|---|---|
| committer | Miqdad <ahmadmiqdad27@gmail.com> | 2025-06-19 13:05:31 +0700 |
| commit | 32724232b991afaff527cf5ff9e58a2cad7ea824 (patch) | |
| tree | a2f1dfef6f34390cce274bc1a099381797285627 | |
| parent | 1542b2373ef4cff98ded7c9bbf426e18b5524162 (diff) | |
<miqdad> Fix sequence
| -rw-r--r-- | indoteknik_custom/models/stock_picking_return.py | 436 | ||||
| -rw-r--r-- | 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() |
