diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-08-29 14:23:18 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-08-29 14:23:18 +0700 |
| commit | 3139c592dd1f74b7c6a6c4917419628895296406 (patch) | |
| tree | c77d8f30e9e6404be3237733b6f3e3e656f6729d | |
| parent | 8fb3427f625867b7d47b7ed0d40f994fa52c00e6 (diff) | |
<hafid> refund abis testing aman
| -rw-r--r-- | indoteknik_custom/models/refund_sale_order.py | 306 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 36 | ||||
| -rw-r--r-- | indoteknik_custom/models/tukar_guling.py | 61 | ||||
| -rw-r--r-- | indoteknik_custom/views/refund_sale_order.xml | 76 |
4 files changed, 374 insertions, 105 deletions
diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py index 0b4d2893..086c7a81 100644 --- a/indoteknik_custom/models/refund_sale_order.py +++ b/indoteknik_custom/models/refund_sale_order.py @@ -10,7 +10,7 @@ from lxml import etree class RefundSaleOrder(models.Model): _name = 'refund.sale.order' _description = 'Refund Sales Order' - _inherit = ['mail.thread'] + _inherit = ['mail.thread', 'mail.activity.mixin'] _rec_name = 'name' name = fields.Char(string='Refund Number', default='New', copy=False, readonly=True) @@ -49,6 +49,7 @@ class RefundSaleOrder(models.Model): bank = fields.Char(string='Bank', required=True) account_name = fields.Char(string='Account Name', required=True) account_no = fields.Char(string='Account No', required=True) + kcp = fields.Char(string='Alamat KCP') finance_note = fields.Text(string='Finance Note') invoice_names = fields.Html(string="Group Invoice Number", compute="_compute_invoice_names") so_names = fields.Html(string="Group SO Number", compute="_compute_so_names") @@ -63,8 +64,19 @@ class RefundSaleOrder(models.Model): ], string='Refund Type', required=True) tukar_guling_ids = fields.One2many( - 'tukar.guling', 'refund_id', - string="Tukar Guling" + 'tukar.guling', 'refund_id', string="Pengajuan Return SO", + ) + + picking_ids = fields.Many2many( + 'stock.picking', + string="Pickings", + compute="_compute_picking_ids", + ) + + transfer_move_id = fields.Many2one( + 'account.move', + string="Journal Payment", + help="Pilih transaksi salah transfer dari jurnal Uang Muka (journal_id=11) yang tidak terkait SO." ) tukar_guling_count = fields.Integer( @@ -72,6 +84,11 @@ class RefundSaleOrder(models.Model): compute="_compute_tukar_guling_count" ) + has_picking = fields.Boolean( + string="Has Picking", + compute="_compute_has_picking", + ) + refund_type_display = fields.Char(string="Refund Type Label", compute="_compute_refund_type_display") line_ids = fields.One2many('refund.sale.order.line', 'refund_id', string='Refund Lines') @@ -152,6 +169,34 @@ class RefundSaleOrder(models.Model): help="Sisa uang masuk yang masih bisa direfund (hanya berlaku untuk 1 SO)", ) show_return_alert = fields.Boolean(compute="_compute_show_return_alert") + show_approval_alert = fields.Boolean(compute="_compute_show_approval_alert") + + + @api.onchange('refund_type', 'partner_id') + def _onchange_refund_type_partner(self): + if self.refund_type == 'salah_transfer' and self.partner_id: + return { + 'domain': { + 'transfer_move_id': [ + ('journal_id', '=', 11), + ('line_ids.partner_id', '=', self.partner_id.id), + ('state', '=', 'posted'), + ('sale_id', '=', False), + ] + } + } + else: + return { + 'domain': {'transfer_move_id': [('id', '=', 0)]} + } + + @api.onchange('transfer_move_id') + def _onchange_transfer_move_id(self): + """Set nilai uang_masuk dari move yang dipilih""" + if self.transfer_move_id and self.refund_type == 'salah_transfer': + self.uang_masuk = self.transfer_move_id.amount_total_signed + elif self.refund_type != 'salah_transfer' and not self.sale_order_ids: + self.uang_masuk = 0.0 @api.model def create(self, vals): @@ -176,6 +221,9 @@ class RefundSaleOrder(models.Model): so_ids = so_cmd[0][2] if so_cmd and so_cmd[0][0] == 6 else [] if so_ids: sale_orders = self.env['sale.order'].browse(so_ids) + partner = sale_orders.mapped('partner_id.id') + if len(partner) > 1: + raise UserError("❌ Tidak dapat membuat refund untuk Multi SO dengan Customer berbeda. Harus memiliki Customer yang sama.") vals['partner_id'] = sale_orders[0].partner_id.id invoices = sale_orders.mapped('invoice_ids').filtered( @@ -188,10 +236,10 @@ class RefundSaleOrder(models.Model): refund_type = vals.get('refund_type') invoice_ids_data = vals.get('invoice_ids', []) invoice_ids = invoice_ids_data[0][2] if invoice_ids_data and invoice_ids_data[0][0] == 6 else [] - if invoice_ids and refund_type and refund_type not in ['uang', 'barang_kosong_sebagian', 'barang_kosong', 'retur_half', 'retur']: + if invoice_ids and refund_type and refund_type not in ['uang', 'barang_kosong_sebagian', 'barang_kosong', 'retur_half']: raise UserError("Refund type Hanya Bisa Lebih Bayar, Barang Kosong Sebagian, atau Retur jika ada invoice") - if not invoice_ids and refund_type and refund_type in ['uang', 'barang_kosong_sebagian']: + if not invoice_ids and refund_type and refund_type in ['uang', 'barang_kosong_sebagian', 'retur_half']: raise UserError("Refund type Lebih Bayar dan Barang Kosong Sebagian Hanya Bisa dipilih Jika Ada Invoice") if refund_type in ['barang_kosong', 'barang_kosong_sebagian'] and so_ids: @@ -206,7 +254,7 @@ class RefundSaleOrder(models.Model): elif refund_type == 'barang_kosong_sebagian': partial_delivery_lines = sale_orders.mapped('order_line').filtered( - lambda l: l.qty_delivered > 0 and l.product_uom_qty > l.qty_delivered + lambda l: l.qty_delivered >= 0 and l.product_uom_qty > l.qty_delivered ) if not partial_delivery_lines: raise UserError("❌ Tidak ada barang yang tidak Terkirim/Kosong di SO yang dipilih.") @@ -215,21 +263,37 @@ class RefundSaleOrder(models.Model): if not so_ids and refund_type != 'salah_transfer': raise ValidationError("Jika tidak ada Sales Order yang dipilih, maka Tipe Refund hanya boleh 'Salah Transfer'.") - if refund_type == 'barang_kosong_sebagian' and so_ids: - sale_orders = self.env['sale.order'].browse(so_ids) - vals['uang_masuk'] = sum(sale_orders.mapped('amount_total')) - - moves = self.env['account.move'].search([ - ('sale_id', 'in', so_ids), - ('journal_id', '=', 11), - ('state', '=', 'posted'), + if refund_type == 'salah_transfer' and vals.get('transfer_move_id'): + move = self.env['account.move'].browse(vals['transfer_move_id']) + if move: + vals['uang_masuk'] = move.amount_total_signed + vals['remaining_refundable'] = 0 + else: + # ==== perhitungan normal ==== + moves = self.env['account.move'].search([ + ('sale_id', 'in', so_ids), + ('journal_id', '=', 11), + ('state', '=', 'posted'), ]) - total_uang_muka = sum(moves.mapped('amount_total_signed')) + total_uang_muka = sum(moves.mapped('amount_total_signed')) if moves else 0.0 + total_midtrans = sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) if so_ids else 0.0 + total_pembayaran = total_uang_muka + total_midtrans - uang_masuk = total_uang_muka if moves else sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) - vals['uang_masuk'] = uang_masuk - total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) if invoice_ids else 0.0 + existing_refunds = self.env['refund.sale.order'].search([ + ('sale_order_ids', 'in', so_ids) + ], order='id desc', limit=1) + if existing_refunds: + sisa_uang_masuk = existing_refunds.remaining_refundable + else: + sisa_uang_masuk = total_pembayaran + + if sisa_uang_masuk < 0: + raise UserError("❌ Tidak ada sisa transaksi untuk di-refund.") + + vals['uang_masuk'] = sisa_uang_masuk + + total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) if invoice_ids else 0.0 vals['total_invoice'] = total_invoice amount_refund = vals.get('amount_refund', 0.0) if amount_refund <= 0.00: @@ -240,16 +304,13 @@ class RefundSaleOrder(models.Model): if existing_refund: raise UserError("❌ Refund multi SO hanya bisa 1 kali.") vals['remaining_refundable'] = 0.0 - elif so_ids and len(so_ids) == 1: - so = self.env['sale.order'].browse(so_ids[0]) - existing_refunds = self.search([('sale_order_ids', 'in', so_ids)]) - total_refunded = sum(existing_refunds.mapped('amount_refund')) + amount_refund - remaining = uang_masuk - total_refunded + elif so_ids and len(so_ids) == 1 and refund_type != 'salah_transfer': + remaining = vals['uang_masuk'] - amount_refund if remaining < 0: raise ValidationError("❌ Tidak ada sisa transaksi untuk di-refund di SO ini. Semua dana sudah dikembalikan.") vals['remaining_refundable'] = remaining - return super().create(vals) + return super().create(vals) def write(self, vals): @@ -276,6 +337,9 @@ class RefundSaleOrder(models.Model): if so_ids: sale_orders = self.env['sale.order'].browse(so_ids) + partner = sale_orders.mapped('partner_id.id') + if len(partner) > 1: + raise UserError("❌ Tidak dapat membuat refund untuk Multi SO dengan Customer berbeda. Harus memiliki Customer yang sama.") vals['partner_id'] = sale_orders[0].partner_id.id sale_orders = self.env['sale.order'].browse(so_ids) @@ -294,15 +358,12 @@ class RefundSaleOrder(models.Model): refund_type = vals.get('refund_type', rec.refund_type) if refund_type in ['barang_kosong', 'barang_kosong_sebagian'] and sale_orders: - zero_delivery_lines = sale_orders.mapped('order_line').filtered(lambda l: l.qty_delivered == 0) + zero_delivery_lines = sale_orders.mapped('order_line').filtered(lambda l: l.qty_delivered >= 0 or l.product_uom_qty > l.qty_delivered) if not zero_delivery_lines: raise UserError("❌ Tidak ada barang yang Tidak Terikirim di Sales Order yang dipilih.") - if not so_ids and refund_type != 'lainnya': - raise ValidationError("Jika tidak ada Sales Order yang dipilih, maka Tipe Refund hanya boleh 'Lainnya'.") - - if refund_type == 'barang_kosong_sebagian' and sale_orders: - vals['uang_masuk'] = sum(sale_orders.mapped('amount_total')) + if not so_ids and refund_type != 'salah_transfer': + raise ValidationError("Jika tidak ada Sales Order yang dipilih, maka Tipe Refund hanya boleh 'Salah Transfer'.") invoice_ids = vals.get('invoice_ids', False) @@ -320,8 +381,12 @@ class RefundSaleOrder(models.Model): if invoice_ids and vals.get('refund_type', rec.refund_type) not in ['uang', 'barang_kosong_sebagian', 'barang_kosong', 'retur_half', 'retur']: raise UserError("Refund type Hanya Bisa Lebih Bayar, Barang Kosong Sebagian, atau Retur jika ada invoice") - if not invoice_ids and vals.get('refund_type', rec.refund_type) in ['uang', 'barang_kosong_sebagian']: - raise UserError("Refund type Lebih Bayar, Barang Kosong Sebagian, atau Retur Hanya Bisa dipilih Jika Ada Invoice") + if not invoice_ids and vals.get('refund_type', rec.refund_type) in ['uang', 'barang_kosong_sebagian', 'retur_half']: + raise UserError("Refund type Lebih Bayar, Barang Kosong Sebagian, atau Retur Sebagian Hanya Bisa dipilih Jika Ada Invoice") + if refund_type == 'salah_transfer' and vals.get('transfer_move_id'): + move = self.env['account.move'].browse(vals['transfer_move_id']) + if move: + vals['uang_masuk'] = move.amount_total_signed if any(field in vals for field in ['uang_masuk', 'invoice_ids', 'ongkir', 'sale_order_ids', 'amount_refund']): total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed')) @@ -349,15 +414,23 @@ class RefundSaleOrder(models.Model): @api.onchange('ongkir', 'amount_refund') def _onchange_refund_fields(self): for rec in self: - uang_masuk = rec.uang_masuk or 0.0 - ongkir = rec.ongkir or 0.0 refund_input = rec.amount_refund or 0.0 - total_refund_with_ongkir = refund_input + ongkir - if total_refund_with_ongkir > uang_masuk: - raise UserError("❌ Refund + Ongkir tidak boleh melebihi Uang Masuk.") - remaining = uang_masuk - refund_input - rec.remaining_refundable = remaining + # ambil refund terakhir untuk SO + existing_refund = self.env['refund.sale.order'].search([ + ('sale_order_ids', 'in', rec.sale_order_ids.ids) + ], order='id desc', limit=1) + + if existing_refund: + sisa_uang_masuk = existing_refund.remaining_refundable + else: + sisa_uang_masuk = rec.uang_masuk or 0.0 + + # update field uang_masuk supaya form menampilkan sisa aktual + rec.uang_masuk = sisa_uang_masuk + + # hitung remaining setelah input refund + rec.remaining_refundable = sisa_uang_masuk - refund_input @api.depends('status_payment', 'status') def _compute_is_locked(self): @@ -399,6 +472,8 @@ class RefundSaleOrder(models.Model): all_invoices = self.env['account.move'] total_invoice = 0.0 + so_ids = self.sale_order_ids.ids + for so in self.sale_order_ids: self.ongkir += so.delivery_amt or 0.0 valid_invoices = so.invoice_ids.filtered( @@ -407,6 +482,15 @@ class RefundSaleOrder(models.Model): all_invoices |= valid_invoices total_invoice += sum(valid_invoices.mapped('amount_total_signed')) + moves = self.env['account.move'].search([ + ('sale_id', 'in', so_ids), + ('journal_id', '=', 11), + ('state', '=', 'posted'), + ]) + total_uang_muka = sum(moves.mapped('amount_total_signed')) if moves else 0.0 + total_midtrans = sum(self.env['sale.order'].browse(so_ids).mapped('gross_amount')) if so_ids else 0.0 + self.uang_masuk = total_uang_muka + total_midtrans + self.invoice_ids = all_invoices self.total_invoice = total_invoice @@ -464,9 +548,15 @@ class RefundSaleOrder(models.Model): line_vals.append((0, 0, { 'product_id': line.product_id.id, 'quantity': line.product_uom_qty, - 'ref_id': line.order_id.id, + 'from_name': so.name, + 'prod_id': so.id, 'reason': '', + 'price_unit': line.price_unit, + 'discount': line.discount, + 'tax_amt': line.price_tax, + 'tax': [(6, 0, line.tax_id.ids)], })) + self.line_ids = line_vals @@ -495,33 +585,57 @@ class RefundSaleOrder(models.Model): ]) for picking in product_out: for move in picking.move_lines: - line_vals.append((0, 0, { - 'product_id': move.product_id.id, - 'ref_id': picking.id, - 'quantity': move.product_uom_qty, - 'reason': '', - })) + so_lines = so.order_line.filtered( + lambda l: l.product_id == move.product_id + ) + for so_line in so_lines: + line_vals.append((0, 0, { + 'product_id': move.product_id.id, + 'ref_id': picking.id, + 'from_name': picking.name, + 'quantity': move.product_uom_qty, + 'reason': '', + 'price_unit': so_line.price_unit, + 'discount': so_line.discount, + 'tax': [(6, 0, so_line.tax_id.ids)], + })) has_bu_pick = any(p.picking_type_id.id == 30 for p in so.picking_ids) if not has_bu_pick: for picking in pickings_srt: for move in picking.move_lines: - line_vals.append((0, 0, { - 'product_id': move.product_id.id, - 'ref_id': picking.ref_id, - 'quantity': move.product_uom_qty, - 'reason': '', - })) + so_lines = so.order_line.filtered( + lambda l: l.product_id == move.product_id + ) + for so_line in so_lines: + line_vals.append((0, 0, { + 'product_id': move.product_id.id, + 'ref_id': picking.id, + 'from_name': picking.name, + 'quantity': move.product_uom_qty, + 'reason': '', + 'price_unit': so_line.price_unit, + 'discount': so_line.discount, + 'tax': [(6, 0, so_line.tax_id.ids)], + })) else: for picking in pickings_ort: for move in picking.move_lines: - line_vals.append((0, 0, { - 'product_id': move.product_id.id, - 'ref_id': picking.ref_id, - 'quantity': move.product_uom_qty, - 'reason': '', - })) - self.line_ids = line_vals + so_lines = so.order_line.filtered( + lambda l: l.product_id == move.product_id + ) + for so_line in so_lines: + line_vals.append((0, 0, { + 'product_id': move.product_id.id, + 'ref_id': picking.id, + 'from_name': picking.name, + 'quantity': move.product_uom_qty, + 'reason': '', + 'price_unit': so_line.price_unit, + 'discount': so_line.discount, + 'tax': [(6, 0, so_line.tax_id.ids)], + })) + self.line_ids = line_vals @api.depends('invoice_ids') @@ -602,6 +716,10 @@ class RefundSaleOrder(models.Model): raise ValidationError( f"⚠️ SO {so.name} memiliki refund tipe Retur. Selesaikan pengajuan retur untuk melanjutkan refund" ) + allowed_sales_ids = rec.sale_order_ids.mapped("user_id.id") + if self.env.user.id not in allowed_sales_ids and rec.refund_type != 'salah_transfer': + raise ValidationError("❌ Hanya Sales pemilik Sales Order terkait yang boleh meminta approval refund ini.") + if rec.status == 'draft': rec.status = 'pengajuan1' @@ -856,6 +974,41 @@ class RefundSaleOrder(models.Model): ]) rec.show_return_alert = not retur_ort and not retur_srt and rec.refund_type in ['retur', 'retur_half'] + def _compute_show_approval_alert(self): + for rec in self: + retur_ort = self.env['stock.picking'].search([ + ('state', '=', 'done'), + ('picking_type_id', '=', 74), + ('sale_id', 'in', rec.sale_order_ids.ids) + ]) + + retur_srt = self.env['stock.picking'].search([ + ('state', '=', 'done'), + ('picking_type_id', '=', 73), + ('sale_id', 'in', rec.sale_order_ids.ids) + ]) + rec.show_approval_alert = retur_ort or retur_srt and rec.refund_type in ['retur', 'retur_half'] + + @api.depends('tukar_guling_ids', 'tukar_guling_ids.picking_ids') + def _compute_picking_ids(self): + for rec in self: + rec.picking_ids = rec.tukar_guling_ids.mapped('picking_ids') + + def action_view_picking(self): + self.ensure_one() + action = self.env.ref('stock.action_picking_tree_all').read()[0] + if len(self.picking_ids) == 1: + action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')] + action['res_id'] = self.picking_ids.id + else: + action['domain'] = [('id', 'in', self.picking_ids.ids)] + return action + + @api.depends('picking_ids') + def _compute_has_picking(self): + for rec in self: + rec.has_picking = bool(rec.picking_ids) + def action_create_tukar_guling(self): for refund in self: if refund.refund_type not in ['retur', 'retur_half']: @@ -897,8 +1050,7 @@ class RefundSaleOrder(models.Model): 'origin_so': refund.sale_order_ids.id, 'operations': picking.id, 'return_type': 'revisi_so', - 'is_has_invoice': bool(refund.invoice_ids.ids), - 'invoice_id': refund.invoice_ids.ids if refund.invoice_ids.ids else None, + 'invoice_id': [(6, 0, refund.invoice_ids.ids)], 'refund_id': refund.id, 'line_ids': line_vals, 'mapping_koli_ids': koli_lines @@ -933,10 +1085,38 @@ class RefundSaleOrder(models.Model): class RefundSaleOrderLine(models.Model): _name = 'refund.sale.order.line' _description = 'Refund Sales Order Line' - _inherit = ['mail.thread'] refund_id = fields.Many2one('refund.sale.order', string='Refund Ref') product_id = fields.Many2one('product.product', string='Product') quantity = fields.Float(string='Qty') reason = fields.Char(string='Reason') - ref_id = fields.Many2one('stock.picking',string='Product Reference') + ref_id = fields.Many2one('stock.picking', string='Picking Reference') + prod_id = fields.Many2one('sale.order', string='Sales Order Reference') + from_name = fields.Char(string="Product Reference") + price_unit = fields.Float(string="Unit Price") + tax_amt = fields.Float(string="Amount Tax", compute='_compute_amounts') + discount = fields.Float(string="Discount %") + tax = fields.Many2many('account.tax',string="Taxes") + subtotal = fields.Float(string="Subtotal", compute='_compute_amounts') + total = fields.Float(string="Grand Total", compute='_compute_amounts') + + @api.depends('quantity', 'price_unit', 'discount', 'tax') + def _compute_amounts(self): + for line in self: + price_unit = line.price_unit * (1 - (line.discount or 0.0) / 100.0) + + subtotal = price_unit * line.quantity + tax_amount = 0.0 + if line.tax: + taxes = line.tax.compute_all( + price_unit=price_unit, # Gunakan harga setelah diskon + quantity=line.quantity, + product=line.product_id, + partner=line.refund_id.partner_id + ) + tax_amount = taxes['total_included'] - taxes['total_excluded'] + subtotal = taxes['total_excluded'] + + line.subtotal = subtotal + line.tax_amt = tax_amount + line.total = subtotal + tax_amount
\ No newline at end of file diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py index a357eb70..998363ef 100755 --- a/indoteknik_custom/models/sale_order.py +++ b/indoteknik_custom/models/sale_order.py @@ -3196,6 +3196,16 @@ class SaleOrder(models.Model): def button_refund(self): self.ensure_one() + + if self.state not in ['cancel', 'sale']: + raise UserError(f"❌ SO {self.name} tidak bisa direfund. Status harus Cancel atau Sale.") + if self.state == 'sale': + not_done_pickings = self.picking_ids.filtered(lambda p: p.state not in ['done', 'cancel']) + if not_done_pickings: + raise UserError( + f"❌ SO {self.name} Belum melakukan kirim barang " + f"({', '.join(not_done_pickings.mapped('name'))}). Selesaikan Pengiriman untuk melakukan refund." + ) moves = self.env['account.move'].search([ ('sale_id', '=', self.id), ('journal_id', '=', 11), @@ -3220,7 +3230,7 @@ class SaleOrder(models.Model): "(Journal Uang Muka/Midtrans Payment)." ) invoice_ids = self.invoice_ids.filtered(lambda inv: inv.state != 'cancel') - total_refunded = sum(self.refund_ids.mapped('amount_refund')) or 0.0 + total_refunded = sum(self.refund_ids.mapped('amount_refund')) sisa_uang_muka = total_uang_muka - total_refunded if sisa_uang_muka <= 0: @@ -3249,6 +3259,30 @@ class SaleOrder(models.Model): if not self: raise UserError("Tidak ada Sale Order yang dipilih.") + if len(self) > 1: + not_cancel_orders = self.filtered(lambda so: so.state != 'cancel') + if not_cancel_orders: + raise ValidationError( + f"❌ Refund Multi SO hanya bisa dibuat untuk SO dengan status Cancel. " + f"SO berikut tidak Cancel: {', '.join(not_cancel_orders.mapped('name'))}" + ) + + + invalid_status_orders = [] + for order in self: + if order.state not in ['cancel', 'sale']: + invalid_status_orders.append(order.name) + elif order.state == 'sale': + not_done_pickings = order.picking_ids.filtered(lambda p: p.state != 'done') + if not_done_pickings: + invalid_status_orders.append(order.name) + + if invalid_status_orders: + raise ValidationError( + f"❌ Refund tidak bisa dibuat untuk SO {', '.join(invalid_status_orders)}. " + f"SO harus Cancel atau Sale dengan semua Pengiriman sudah selesai." + ) + partner_set = set(self.mapped('partner_id.id')) if len(partner_set) > 1: raise UserError("Tidak dapat membuat refund untuk Multi SO dengan Customer berbeda. Harus memiliki Customer yang sama.") diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py index 1309fcfe..ff4edc84 100644 --- a/indoteknik_custom/models/tukar_guling.py +++ b/indoteknik_custom/models/tukar_guling.py @@ -96,38 +96,39 @@ class TukarGuling(models.Model): so = self.env['sale.order'].search([('name', '=', origin_str)], limit=1) rec.origin_so = so.id if so else False - @api.depends('origin', 'origin_so', 'partner_id', 'line_ids.product_id') + @api.depends('origin', 'origin_so', 'partner_id', 'line_ids.product_id', 'invoice_id', 'operations') def _compute_is_has_invoice(self): Move = self.env['account.move'] for rec in self: - rec.is_has_invoice = False - rec.invoice_id = [(5, 0, 0)] - - product_ids = rec.line_ids.mapped('product_id').ids - if not product_ids: - continue - - domain = [ - ('move_type', 'in', ['out_invoice', 'in_invoice']), - ('state', 'not in', ['draft', 'cancel']), - ('invoice_line_ids.product_id', 'in', product_ids), - ] - - if rec.partner_id: - domain.append(('partner_id', '=', rec.partner_id.id)) - - extra = [] - if rec.origin: - extra.append(('invoice_origin', 'ilike', rec.origin)) - if rec.origin_so: - extra.append(('invoice_line_ids.sale_line_ids.order_id', '=', rec.origin_so.id)) - if extra: - domain = domain + ['|'] * (len(extra) - 1) + extra - - invoices = Move.search(domain).with_context(active_test=False) - if invoices: - rec.invoice_id = [(6, 0, invoices.ids)] - rec.is_has_invoice = True + invoices = rec.invoice_id + + if not invoices: + product_ids = rec.line_ids.mapped('product_id').ids + if product_ids: + domain = [ + ('move_type', 'in', ['out_invoice', 'out_refund', 'in_invoice']), + ('state', 'not in', ['draft', 'cancel']), + ('invoice_line_ids.product_id', 'in', product_ids), + ] + + # if rec.partner_id: + # domain.append( + # ('partner_id.commercial_partner_id', '=', rec.partner_id.commercial_partner_id.id) + # ) + + extra = [] + if rec.origin: + extra.append(('invoice_origin', 'ilike', rec.origin)) + if rec.origin_so: + extra.append(('invoice_line_ids.sale_line_ids.order_id', '=', rec.origin_so.id)) + if extra: + domain += ['|'] * (len(extra) - 1) + extra + + invoices = Move.search(domain).with_context(active_test=False) + if invoices: + rec.invoice_id = [(6, 0, invoices.ids)] + + rec.is_has_invoice = bool(invoices) def set_opt(self): if not self.val_inv_opt and self.is_has_invoice == True: @@ -1009,4 +1010,4 @@ class TukarGulingMappingKoli(models.Model): for rec in self: if rec.tukar_guling_id and rec.tukar_guling_id.state not in ['draft', 'cancel']: raise UserError("Tidak bisa menghapus Mapping Koli karena status Tukar Guling bukan Draft atau Cancel.") - return super(TukarGulingMappingKoli, self).unlink() + return super(TukarGulingMappingKoli, self).unlink()
\ No newline at end of file diff --git a/indoteknik_custom/views/refund_sale_order.xml b/indoteknik_custom/views/refund_sale_order.xml index a2538f42..ae0861e2 100644 --- a/indoteknik_custom/views/refund_sale_order.xml +++ b/indoteknik_custom/views/refund_sale_order.xml @@ -90,6 +90,11 @@ attrs="{'invisible': [('show_return_alert', '=', False)]}"> ⚠️ SO belum melakukan retur barang. Silakan buat pengajuan retur. </div> + <field name="show_approval_alert" invisible="1"/> + <div class="alert alert-info" role="alert" + attrs="{'invisible': ['|', ('show_approval_alert', '=', False), ('status', 'in', ['reject', 'refund'])]}"> + ⚠️ SO sudah melakukan retur barang. Silakan lanjutkan refund. + </div> </xpath> <sheet> <div class="oe_button_box" name="button_box"> @@ -101,12 +106,24 @@ attrs="{'invisible': [('journal_refund_move_id', '=', False)]}"> <field name="journal_refund_move_id" string="Journal Refund" widget="statinfo"/> </button> - <button name="action_open_tukar_guling" + + <button name="action_open_tukar_guling" type="object" class="oe_stat_button" icon="fa-refresh" - attrs="{'invisible': ['|', ('refund_type', 'not in', ['retur', 'retur_half']), ('tukar_guling_count', '=', 0)]}"> - <field name="tukar_guling_count" string="Pengajuan Return SO" widget="statinfo"/> + attrs="{'invisible': ['|', ('tukar_guling_count','=', 0), ('has_picking','=',True)]}"> + <div class="o_stat_info"> + <field name="tukar_guling_count" widget="statinfo"/> + <span class="o_stat_text">Pengajuan Return SO</span> + </div> + </button> + + <button name="action_view_picking" + type="object" + class="oe_stat_button" + icon="fa-truck" + attrs="{'invisible': [('has_picking','=',False)]}"> + <field name="picking_ids" widget="statinfo" string="Delivery"/> </button> </div> <widget name="web_ribbon" @@ -128,21 +145,26 @@ <field name="journal_refund_state" invisible="1"/> <field name="partner_id" attrs="{'readonly': [('is_locked', '=', True)]}"/> - <field name="sale_order_ids" widget="many2many_tags" attrs="{'readonly': [('is_locked', '=', True)]}"/> - <field name="invoice_ids" widget="many2many_tags" readonly="1"/> - <field name="invoice_names" widget="html" readonly="1"/> - <field name="so_names" widget="html" readonly="1"/> - <field name="advance_move_names" widget="html" readonly="1"/> + <field name="sale_order_ids" widget="many2many_tags" attrs="{'readonly': [('is_locked', '=', True)], 'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="invoice_ids" widget="many2many_tags" readonly="1" attrs="{'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="tukar_guling_count" invisible="1"/> + <field name="invoice_names" widget="html" readonly="1" attrs="{'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="so_names" widget="html" readonly="1" attrs="{'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="advance_move_names" widget="html" readonly="1" attrs="{'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="transfer_move_id" + attrs="{'invisible': [('refund_type', '!=', 'salah_transfer')], + 'required': [('refund_type', '=', 'salah_transfer')]}"/> <field name="refund_type" attrs="{'readonly': [('is_locked', '=', True)]}"/> <field name="note_refund" attrs="{'readonly': [('is_locked', '=', True)]}"/> </group> <group> <field name="uang_masuk" attrs="{'readonly': [('refund_type', '!=', 'salah_transfer')]}"/> - <field name="total_invoice" readonly="1"/> - <field name="ongkir" attrs="{'readonly': [('is_locked', '=', True)]}"/> + <field name="total_invoice" readonly="1" attrs="{'invisible': [('refund_type', '=', 'salah_transfer')]}"/> + <field name="ongkir" attrs="{'readonly': [('is_locked', '=', True)], 'invisible': [('refund_type', '=', 'salah_transfer')]}"/> <field name="amount_refund" attrs="{'readonly': [('is_locked', '=', True)]}"/> <field name="amount_refund_text" readonly="1"/> <field name="sale_order_count" invisible="1"/> + <field name="has_picking" invisible="1"/> <field name="tukar_guling_ids" invisible="1"/> <field name="remaining_refundable" readonly="1" attrs="{'invisible': [('sale_order_count', '>', 1)]}"/> <field name="uang_masuk_type" required="1" attrs="{'readonly': [('is_locked', '=', True)]}"/> @@ -157,9 +179,17 @@ <page string="Produk Line"> <field name="line_ids" attrs="{'readonly': [('is_locked', '=', True)]}"> <tree editable="bottom" create="0" delete="1"> - <field name="ref_id"/> + <field name="from_name"/> + <field name="prod_id" invisible="1"/> + <field name="ref_id" invisible="1"/> <field name="product_id"/> <field name="quantity"/> + <field name="price_unit"/> + <field name="discount"/> + <field name="subtotal"/> + <field name="tax" widget="many2many_tags"/> + <field name="tax_amt" widget="monetary" options="{'currency_field': 'currency_id'}"/> + <field name="total" widget="monetary" options="{'currency_field': 'currency_id'}" sum="Grand Total"/> <field name="reason"/> </tree> </field> @@ -176,6 +206,7 @@ <field name="bank" attrs="{'readonly': [('is_locked', '=', True)]}"/> <field name="account_name" attrs="{'readonly': [('is_locked', '=', True)]}"/> <field name="account_no" attrs="{'readonly': [('is_locked', '=', True)]}"/> + <field name="kcp" attrs="{'readonly': [('is_locked', '=', True)]}"/> </group> </group> </page> @@ -230,11 +261,34 @@ <field name="reason_reject"/> </group> </page> + + <page string="Return Line" attrs="{'invisible': ['|', ('tukar_guling_count','=', 0), ('has_picking', '=', False)]}"> + <group> + <field name="tukar_guling_ids" readonly="1" nolabel="1"> + <tree> + <field name="name"/> + <field name="partner_id" string="Customer"/> + <field name="origin" string="SO Number"/> + <field name="operations" string="Operations"/> + <field name="return_type" string="Return Type"/> + <field name="state" widget="badge" + decoration-info="state in ('draft', 'approval_sales', 'approval_finance','approval_logistic')" + decoration-warning="state == 'approved'" + decoration-success="state == 'done'" + decoration-muted="state == 'cancel'" + /> + <field name="ba_num" string="Nomor BA"/> + <field name="date"/> + </tree> + </field> + </group> + </page> </notebook> </sheet> <div class="oe_chatter"> <field name="message_follower_ids" widget="mail_followers"/> <field name="message_ids" widget="mail_thread"/> + <field name="activity_ids" widget="mail_activity"/> </div> </form> </field> |
