summaryrefslogtreecommitdiff
path: root/indoteknik_custom/models/tukar_guling.py
blob: dfa62dc87b80e550b96d465ce455cf995c166251 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
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'
    )
    # origin_so = fields.Many2one('sale.order', string='Origin SO')
    name = fields.Char('Number', required=True, copy=False, readonly=True, default='New')
    date = fields.Datetime('Date', default=fields.Datetime.now, required=True)
    operations = fields.Many2one('stock.picking', 'Operations',
                                 domain=[('picking_type_id.code', '=', 'outgoing')], help='Nomor BU/Out atau BU/Pick')
    ba_num = fields.Text('Nomor BA')
    notes = fields.Text('Notes')
    return_type = fields.Selection(String='Return Type', selection=[
        ('tukar_guling', 'Tukar Guling'),  # -> barang yang sama
        ('revisi_so', 'Revisi SO')])
    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:
            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

            # Debug logging
            _logger = logging.getLogger(__name__)
            _logger.info(f"BU/OUT: {self.operations.name}, State: {self.operations.state}")
            _logger.info(f"Total moves found: {len(moves_to_check)}")

            for move in moves_to_check:
                _logger.info(
                    f"Move: {move.name}, Product: {move.product_id.name if move.product_id else 'No Product'}, Qty: {move.product_uom_qty}, State: {move.state}")

                # Ambil semua move yang ada quantity
                if move.product_id and move.product_uom_qty > 0:
                    lines_data.append((0, 0, {
                        'sequence': sequence,
                        'product_id': move.product_id.id,
                        'product_uom_qty': move.product_uom_qty,
                        'product_uom': move.product_uom.id,
                        'name': move.name or move.product_id.display_name,
                    }))
                    sequence += 10

            if lines_data:
                self.line_ids = lines_data
                _logger.info(f"Created {len(lines_data)} lines")
            else:
                _logger.info("No lines created - no valid moves found")
        else:
            # Clear lines jika operations dikosongkan, 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 terlebih dahulu!")

        # Clear existing lines
        self.line_ids = [(5, 0, 0)]

        lines_data = []
        sequence = 10

        # Ambil semua stock moves dari operations
        for move in self.operations.move_ids:
            if move.product_uom_qty > 0:
                lines_data.append((0, 0, {
                    'sequence': sequence,
                    'product_id': move.product_id.id,
                    'product_uom_qty': move.product_uom_qty,
                    'product_uom': move.product_uom.id,
                    'name': move.name or move.product_id.display_name,
                }))
                sequence += 10

        if lines_data:
            self.line_ids = lines_data
        else:
            raise UserError("Tidak ditemukan barang di BU/OUT yang dipilih!")

    @api.constrains('return_type', 'operations')
    def _check_required_bu_fields(self):
        for record in self:
            if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations:
                raise ValidationError("BU/Out harus diisi!")

    @api.constrains('line_ids', 'state')
    def _check_product_lines(self):
        """Constraint: Product lines harus ada jika state bukan draft"""
        for record in self:
            if record.state in ('approval_sales', 'approval_logistic', 'approval_finance',
                                'done') and not record.line_ids:
                raise ValidationError("Product lines harus diisi sebelum submit atau approve!")

    def _validate_product_lines(self):
        """Helper method untuk validasi product lines"""
        self.ensure_one()

        # Check ada product lines
        if not self.line_ids:
            raise UserError("Belum ada product lines yang ditambahkan!")

        # Check product sudah diisi
        empty_lines = self.line_ids.filtered(lambda line: not line.product_id)
        if empty_lines:
            raise UserError("Ada product lines yang belum diisi productnya!")

        # Check quantity > 0
        zero_qty_lines = self.line_ids.filtered(lambda line: line.product_uom_qty <= 0)
        if zero_qty_lines:
            raise UserError("Quantity product tidak boleh kosong atau 0!")

        return True

    @api.model
    def create(self, vals):
        # Generate sequence number
        if not vals.get('name') or vals['name'] == 'New':
            # Pastikan sequence code 'tukar.guling' ada
            sequence = self.env['ir.sequence'].search([('code', '=', 'tukar.guling')], limit=1)
            if sequence:
                vals['name'] = sequence.next_by_id()
            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 = {}

        # 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 'copy'

        default.update({
            'state': 'draft',
            'date': fields.Datetime.now(),
        })

        new_record = super(TukarGuling, 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 '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(TukarGuling, self).write(vals)

    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
        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()

        if self.state != 'draft':
            raise UserError("Submit hanya bisa dilakukan dari Draft.")
        self.state = 'approval_sales'

    def action_approve(self):
        self.ensure_one()

        if not self.operations:
            raise UserError("BU/Out 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_sales':
                if not rec.env.user.has_group('indoteknik_custom.group_role_sales'):
                    raise UserError("Hanya Sales Manager yang boleh approve tahap ini.")
                rec.state = 'approval_logistic'

            elif rec.state == 'approval_logistic':
                if not rec.env.user.has_group('indoteknik_custom.group_role_logistic'):
                    raise UserError("Hanya Logistic Manager yang boleh approve tahap ini.")
                rec.state = 'approval_finance'

            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.")

            operation_picking = record.operations

            # 1. Cari semua picking DONE berdasarkan origin SO
            related_pickings = self.env['stock.picking'].search([
                ('origin', '=', record.origin),
                ('state', '=', 'done'),
            ])
            if not related_pickings:
                raise UserError(
                    "Tidak ditemukan BU/PICK atau BU/OUT dari SO: %s" % record.origin)

            # filter based on stockin.picking picking type
            bu_pick_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 30)  # BU/PICK
            bu_out_to_return = related_pickings.filtered(lambda ktl: ktl.picking_type_id.id == 29)  # BU/OUT

            if not bu_pick_to_return and not bu_out_to_return:
                raise UserError("Tidak ada BU/PICK atau BU/OUT yang selesai untuk diretur.")

            created_returns = []


            # Lokasi default untuk retur
            srt_type = self.env['stock.picking.type'].browse(73)
            ort_type = self.env['stock.picking.type'].browse(74)
            bu_pick_type = self.env['stock.picking.type'].browse(30)
            bu_out_type = self.env['stock.picking.type'].browse(29)

            stock_location = self.env['stock.location']

            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_STOCK_LOCATION_ID = 57

                # Determine locations based on picking type
                if picking.picking_type_id.id == 30: # -> ngeretur bu pick
                    return_type = ort_type
                    default_location_id = BU_OUTPUT_LOCATION_ID
                    default_location_dest_id = BU_STOCK_LOCATION_ID
                    if not default_location_id or not default_location_dest_id:
                        raise UserError("Lokasi Origin atau Destination salah.")
                elif picking.picking_type_id.id == 29: # -> ngeretur bu out
                    return_type = srt_type
                    default_location_id = PARTNER_LOCATION_ID
                    default_location_dest_id = BU_OUTPUT_LOCATION_ID
                    if not default_location_id or not default_location_dest_id:
                        raise UserError("Lokasi Origin atau Destination salah.")
                elif picking.picking_type_id.id == 74: # -> ngeretur srt
                    return_type = bu_pick_type
                    default_location_id = BU_STOCK_LOCATION_ID
                    default_location_dest_id = BU_OUTPUT_LOCATION_ID
                    if not default_location_id or not default_location_dest_id:
                        raise UserError("Lokasi Origin atau Destination salah.")
                elif picking.picking_type_id.id == 73: # -> ngeretur ort
                    return_type = bu_out_type
                    default_location_id = BU_OUTPUT_LOCATION_ID
                    default_location_dest_id = PARTNER_LOCATION_ID
                    if not default_location_id or not default_location_dest_id:
                        raise UserError("Lokasi Origin atau Destination salah.")
                else:
                    raise UserError("Hayo")
                    return None

                return_context = dict(self.env.context)
                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 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_id': record.id,
                })

                return return_picking

            # Buat return dari BU/OUT
            for picking in bu_out_to_return:
                name = _create_return_from_picking(picking)
                if name:
                    created_returns.append(name)

            # Buat return dari BU/PICK
            for picking in bu_pick_to_return:
                name = _create_return_from_picking(picking)
                if name:
                    created_returns.append(name)

            # Buat return dari SRT dan ort
            if record.return_type == 'tukar_guling':
                target = [woi for woi in created_returns if woi.picking_type_id.id in (74, 73)]
                for picking in target:
                    retur = _create_return_from_picking(picking)
                    if retur:
                        created_returns.append(retur)


            if not created_returns:
                raise UserError("wkwkwk")


class TukarGulingLine(models.Model):
    _name = 'tukar.guling.line'
    _description = 'Tukar Guling Line'
    _order = 'sequence, id'

    sequence = fields.Integer('Sequence', default=10, copy=False)
    tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling', required=True, ondelete='cascade')
    product_id = fields.Many2one('product.product', string='Product', required=True)
    product_uom_qty = fields.Float('Quantity', digits='Product Unit of Measure', required=True, default=1.0)
    product_uom = fields.Many2one('uom.uom', string='Unit of Measure')
    name = fields.Text('Description')

    @api.model_create_multi
    def create(self, vals_list):
        """Override create to auto-assign sequence"""
        for vals in vals_list:
            if 'sequence' not in vals or vals.get('sequence', 0) <= 0:
                # Get max sequence untuk tukar_guling yang sama
                tukar_guling_id = vals.get('tukar_guling_id')
                if tukar_guling_id:
                    max_seq = self.search([
                        ('tukar_guling_id', '=', tukar_guling_id)
                    ], order='sequence desc', limit=1)
                    vals['sequence'] = (max_seq.sequence or 0) + 10
                else:
                    vals['sequence'] = 10
        return super(TukarGulingLine, self).create(vals_list)

    @api.onchange('product_id')
    def _onchange_product_id(self):
        if self.product_id:
            self.name = self.product_id.display_name
            self.product_uom = self.product_id.uom_id


class StockPicking(models.Model):
    _inherit = 'stock.picking'

    tukar_guling_id = fields.Many2one('tukar.guling', string='Tukar Guling Ref')