summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indoteknik_api/controllers/api_v1/user.py4
-rwxr-xr-xindoteknik_custom/__manifest__.py3
-rw-r--r--indoteknik_custom/models/account_move.py33
-rw-r--r--indoteknik_custom/models/approval_payment_term.py10
-rw-r--r--indoteknik_custom/models/dunning_run.py3
-rwxr-xr-xindoteknik_custom/models/purchase_order.py2
-rw-r--r--indoteknik_custom/models/purchase_order_sales_match.py2
-rw-r--r--indoteknik_custom/models/refund_sale_order.py707
-rw-r--r--indoteknik_custom/models/res_partner.py11
-rwxr-xr-xindoteknik_custom/models/sale_order.py242
-rw-r--r--indoteknik_custom/models/sale_order_line.py6
-rw-r--r--indoteknik_custom/models/stock_move.py4
-rw-r--r--indoteknik_custom/models/stock_picking_return.py7
-rw-r--r--indoteknik_custom/models/tukar_guling.py72
-rw-r--r--indoteknik_custom/models/tukar_guling_po.py4
-rw-r--r--indoteknik_custom/report/purchase_report.xml149
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv7
-rw-r--r--indoteknik_custom/views/account_move.xml7
-rw-r--r--indoteknik_custom/views/approval_payment_term.xml2
-rw-r--r--indoteknik_custom/views/dunning_run.xml7
-rw-r--r--indoteknik_custom/views/ir_sequence.xml2
-rw-r--r--indoteknik_custom/views/refund_sale_order.xml309
-rw-r--r--indoteknik_custom/views/res_partner.xml13
-rwxr-xr-xindoteknik_custom/views/sale_order.xml31
-rw-r--r--indoteknik_custom/views/stock_move_line.xml9
-rw-r--r--indoteknik_custom/views/stock_picking.xml105
26 files changed, 1414 insertions, 337 deletions
diff --git a/indoteknik_api/controllers/api_v1/user.py b/indoteknik_api/controllers/api_v1/user.py
index dde30fec..3511bc52 100644
--- a/indoteknik_api/controllers/api_v1/user.py
+++ b/indoteknik_api/controllers/api_v1/user.py
@@ -89,7 +89,9 @@ class User(controller.Controller):
'name': name,
'login': email,
'oauth_provider_id': request.env.ref('auth_oauth.provider_google').id,
- 'sel_groups_1_9_10': 9
+ 'sel_groups_1_9_10': 9,
+ 'active': True,
+
}
user = request.env['res.users'].create(user_data)
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 360303b8..09a3aa6f 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -166,16 +166,19 @@
'report/report_invoice.xml',
'report/report_picking.xml',
'report/report_sale_order.xml',
+ 'report/purchase_report.xml',
'views/vendor_sla.xml',
'views/coretax_faktur.xml',
'views/public_holiday.xml',
'views/stock_inventory.xml',
'views/sale_order_delay.xml',
+ 'views/refund_sale_order.xml',
'views/tukar_guling.xml',
# 'views/tukar_guling_return_views.xml'
'views/tukar_guling_po.xml',
# 'views/refund_sale_order.xml',
'views/update_date_planned_po_wizard_view.xml',
+ # 'views/reimburse.xml',
],
'demo': [],
'css': [],
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index c44cad78..70cd07e4 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -105,6 +105,34 @@ class AccountMove(models.Model):
tracking=True
)
+ # def _check_and_lock_cbd(self):
+ # cbd_term = self.env['account.payment.term'].browse(26)
+ # today = date.today()
+
+ # # Cari semua invoice overdue
+ # overdue_invoices = self.search([
+ # ('move_type', '=', 'out_invoice'),
+ # ('state', '=', 'posted'),
+ # ('payment_state', 'not in', ['paid', 'in_payment', 'reversed']),
+ # ('invoice_date_due', '!=', False),
+ # ('invoice_date_due', '<=', today - timedelta(days=30)),
+ # ], limit=3)
+
+ # _logger.info(f"Found {len(overdue_invoices)} overdue invoices for CBD lock check.")
+ # _logger.info(f"Overdue Invoices: {overdue_invoices.mapped('name')}")
+
+ # # Ambil partner unik dari invoice
+ # partners_to_lock = overdue_invoices.mapped('partner_id').filtered(lambda p: not p.is_cbd_locked)
+ # _logger.info(f"Partners to lock: {partners_to_lock.mapped('name')}")
+
+ # # Lock hanya partner yang belum locked
+ # if partners_to_lock:
+ # partners_to_lock.write({
+ # 'is_cbd_locked': True,
+ # 'property_payment_term_id': cbd_term.id,
+ # })
+
+
def compute_partial_payment(self):
for move in self:
if move.amount_total_signed > 0 and move.amount_residual_signed > 0 and move.payment_state == 'partial':
@@ -179,9 +207,8 @@ class AccountMove(models.Model):
('state', '=', 'posted'),
('payment_state', 'not in', ['paid', 'in_payment', 'reversed']),
('invoice_date_due', 'in', target_dates),
- ('date_terima_tukar_faktur', '!=', False),
- ('partner_id', 'in' , [94603])
- ], limit=5)
+ ('date_terima_tukar_faktur', '!=', False)
+ ])
_logger.info(f"Invoices: {invoices}")
invoices = invoices.filtered(
diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py
index 8618856a..449bd90b 100644
--- a/indoteknik_custom/models/approval_payment_term.py
+++ b/indoteknik_custom/models/approval_payment_term.py
@@ -69,8 +69,8 @@ class ApprovalPaymentTerm(models.Model):
return res
def _track_changes_for_user_688(self, vals, old_values_dict):
- if self.env.user.id != 688:
- return
+ # if self.env.user.id != 688:
+ # return
tracked_fields = {"blocking_stage", "warning_stage", "property_payment_term_id"}
@@ -106,7 +106,8 @@ class ApprovalPaymentTerm(models.Model):
if changes:
timestamp = fields.Datetime.now().strftime('%Y-%m-%d %H:%M:%S')
- rec.change_log_688 = f"{timestamp} - Perubahan oleh Widya:\n" + "\n".join(changes)
+ user = self.env.user
+ rec.change_log_688 = f"{timestamp} - Perubahan oleh {user.name}:\n" + "\n".join(changes)
@staticmethod
@@ -171,7 +172,8 @@ class ApprovalPaymentTerm(models.Model):
'blocking_stage': self.blocking_stage,
'warning_stage': self.warning_stage,
'active_limit': self.active_limit,
- 'property_payment_term_id': self.property_payment_term_id.id
+ 'property_payment_term_id': self.property_payment_term_id.id,
+ 'is_cbd_locked': False,
})
self.approve_date = datetime.utcnow()
self.state = 'approved'
diff --git a/indoteknik_custom/models/dunning_run.py b/indoteknik_custom/models/dunning_run.py
index 5a6aebac..9feea1d1 100644
--- a/indoteknik_custom/models/dunning_run.py
+++ b/indoteknik_custom/models/dunning_run.py
@@ -1,6 +1,6 @@
from odoo import models, api, fields
from odoo.exceptions import AccessError, UserError, ValidationError
-from datetime import timedelta
+from datetime import timedelta, date
import logging
@@ -149,4 +149,5 @@ class DunningRunLine(models.Model):
total_amt = fields.Float(string='Total Amount')
open_amt = fields.Float(string='Open Amount')
due_date = fields.Date(string='Due Date')
+ payment_term = fields.Many2one('account.payment.term', related='invoice_id.invoice_payment_term_id', string='Payment Term')
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 50913a80..18811b85 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -66,7 +66,7 @@ class PurchaseOrder(models.Model):
sale_order = fields.Char(string='Sale Order')
matches_so = fields.Many2many('sale.order', string='Matches SO', compute='_compute_matches_so')
is_create_uangmuka = fields.Boolean(string='Uang Muka?')
- move_id = fields.Many2one('account.move', string='Journal Entries Uang Muka', domain=[('move_type', '=', 'entry')])
+ move_id = fields.Many2one('account.move', string='Journal Entries Uang Muka', domain=[('move_type', '=', 'entry')], copy=False)
logbook_bill_id = fields.Many2one('report.logbook.bill', string='Logbook Bill')
status_printed = fields.Selection([
('not_printed', 'Belum Print'),
diff --git a/indoteknik_custom/models/purchase_order_sales_match.py b/indoteknik_custom/models/purchase_order_sales_match.py
index b18864f3..084b93f7 100644
--- a/indoteknik_custom/models/purchase_order_sales_match.py
+++ b/indoteknik_custom/models/purchase_order_sales_match.py
@@ -39,7 +39,7 @@ class PurchaseOrderSalesMatch(models.Model):
('sale_line_id', '=', rec.sale_line_id.id),
])
if stock_move:
- rec.bu_pick = stock_move.picking_id.id
+ rec.bu_pick = stock_move[0].picking_id.id
else:
rec.bu_pick = None
diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py
index 11bfd07f..f511ed5d 100644
--- a/indoteknik_custom/models/refund_sale_order.py
+++ b/indoteknik_custom/models/refund_sale_order.py
@@ -10,20 +10,24 @@ 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)
note_refund = fields.Text(string='Note Refund')
sale_order_ids = fields.Many2many('sale.order', string='Sales Order Numbers')
uang_masuk = fields.Float(string='Uang Masuk', required=True)
- total_invoice = fields.Float(string='Total Invoice')
+ total_invoice = fields.Float(string='Total Order')
ongkir = fields.Float(string='Ongkir', required=True, default=0.0)
amount_refund = fields.Float(string='Total Refund', required=True)
amount_refund_text = fields.Char(string='Total Refund Text', compute='_compute_refund_text')
user_ids = fields.Many2many('res.users', string='Salespersons', compute='_compute_user_ids', domain=[('active', 'in', [True, False])])
create_uid = fields.Many2one('res.users', string='Created By', readonly=True)
created_date = fields.Date(string='Tanggal Request Refund', readonly=True)
+ sale_order_count = fields.Integer(
+ string="Sale Order Count",
+ compute="_compute_sale_order_count",
+ )
status = fields.Selection([
('draft', 'Draft'),
('pengajuan1', 'Approval Sales Manager'),
@@ -45,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")
@@ -55,9 +60,36 @@ class RefundSaleOrder(models.Model):
('uang', 'Refund Lebih Bayar'),
('retur_half', 'Refund Retur Sebagian'),
('retur', 'Refund Retur Full'),
- ('lainnya', 'Lainnya')
+ ('salah_transfer', 'Salah Transfer')
], string='Refund Type', required=True)
+ tukar_guling_ids = fields.One2many(
+ '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",
+ copy=False,
+ help="Pilih transaksi salah transfer dari jurnal Uang Muka (journal_id=11) yang tidak terkait SO."
+ )
+
+ tukar_guling_count = fields.Integer(
+ string="Tukar Guling Count",
+ 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')
@@ -89,7 +121,7 @@ class RefundSaleOrder(models.Model):
bukti_refund_type = fields.Selection([
('pdf', 'PDF'),
('image', 'Image'),
- ], string="Attachment Type", default='image')
+ ], string="Attachment Type")
bukti_uang_masuk_image = fields.Binary(string="Upload Bukti Uang Masuk")
bukti_transfer_refund_image = fields.Binary(string="Upload Bukti Transfer Refund")
bukti_uang_masuk_pdf = fields.Binary(string="Upload Bukti Uang Masuk")
@@ -107,28 +139,80 @@ class RefundSaleOrder(models.Model):
is_locked = fields.Boolean(string="Locked", compute="_compute_is_locked")
sale_order_names_jasper = fields.Char(string='Sales Order List', compute='_compute_order_invoice_names')
invoice_names_jasper = fields.Char(string='Invoice List', compute='_compute_order_invoice_names')
-
-
-
- @api.depends('refund_type')
- def _compute_refund_type_display(self):
- for rec in self:
- rec.refund_type_display = dict(self.fields_get(allfields=['refund_type'])['refund_type']['selection']).get(rec.refund_type, '')
+ so_order_line_ids = fields.Many2many(
+ "sale.order.line", string="SO Order Lines", compute="_compute_so_order_lines", store=False
+ )
+ currency_id = fields.Many2one(
+ "res.currency", string="Currency",
+ default=lambda self: self.env.company.currency_id, required=True
+ )
+
+ amount_untaxed = fields.Monetary(
+ string="Untaxed Amount", compute="_compute_amount_from_so",
+ )
+ amount_tax = fields.Monetary(
+ string="Taxes", compute="_compute_amount_from_so",
+ )
+ amount_total = fields.Monetary(
+ string="Total", compute="_compute_amount_from_so",
+ )
+ total_margin = fields.Monetary(
+ string="Total Margin", compute="_compute_amount_from_so",
+ )
+ grand_total = fields.Monetary(
+ string="Grand Total", compute="_compute_amount_from_so",
+ )
+ delivery_amt = fields.Monetary(
+ string="Delivery Amount", help="Ongkos kirim yang Dibayarkan Customer", default=0.0, compute="_compute_amount_from_so",
+ )
+ remaining_refundable = fields.Float(
+ string="Sisa Uang Masuk",
+ 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):
allowed_user_ids = [23, 19, 688, 7]
if not (
self.env.user.has_group('indoteknik_custom.group_role_sales') or
self.env.user.has_group('indoteknik_custom.group_role_fat') or
- self.env.user.id not in allowed_user_ids
+ self.env.user.id in allowed_user_ids
):
- raise UserError("❌ Hanya user Sales dan Finance yang boleh membuat refund.")
+ raise UserError("❌ Hanya Sales dan Finance yang boleh membuat refund.")
if vals.get('name', 'New') == 'New':
vals['name'] = self.env['ir.sequence'].next_by_code('refund.sale.order') or 'New'
+
vals['created_date'] = fields.Date.context_today(self)
vals['create_uid'] = self.env.user.id
@@ -138,6 +222,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(
@@ -150,40 +237,79 @@ 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']:
- raise UserError("Refund type Hanya Bisa Lebih Bayar, Barang Kosong Sebagian, atau Retur Sebagian jika ada invoice")
+ 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', 'barang_kosong', 'retur_half']:
- raise UserError("Refund type Lebih Bayar, Barang Kosong Sebagian, atau Retur Sebagian Hanya Bisa dipilih Jika Ada Invoice")
+ 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:
+ sale_orders = self.env['sale.order'].browse(so_ids)
+
+ if refund_type == 'barang_kosong':
+ zero_delivery_lines = sale_orders.mapped('order_line').filtered(
+ lambda l: l.qty_delivered == 0 and l.product_uom_qty > 0
+ )
+ if not zero_delivery_lines:
+ raise UserError("❌ Tidak ada barang kosong di SO yang terpilih.")
+
+ 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
+ )
+ if not partial_delivery_lines:
+ raise UserError("❌ Tidak ada barang yang tidak Terkirim/Kosong di SO 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'.")
-
- refund = refund_type in ['retur', 'retur_half']
- if refund and so_ids:
- so = self.env['sale.order'].browse(so_ids)
- pickings = self.env['stock.picking'].search([
- ('state', '=', 'done'),
- ('picking_type_id', '=', 73),
- ('sale_id', 'in', so_ids)
+
+ 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 == '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'),
])
- if not pickings:
- raise ValidationError(f"SO {', '.join(so.mapped('name'))} tidak melakukan retur barang.")
+ 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
+
+ existing_refunds = self.env['refund.sale.order'].search([
+ ('sale_order_ids', 'in', so_ids)
+ ], order='id desc', limit=1)
- if refund_type == 'retur_half' and not invoice_ids:
- raise ValidationError(f"SO {', '.join(so.mapped('name'))} belum memiliki invoice untuk Retur Sebagian.")
+ if existing_refunds:
+ sisa_uang_masuk = existing_refunds.remaining_refundable
+ else:
+ sisa_uang_masuk = total_pembayaran
- total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total')) if invoice_ids else 0.0
- uang_masuk = vals.get('uang_masuk', 0.0)
- ongkir = vals.get('ongkir', 0.0)
- pengurangan = total_invoice + ongkir
+ if sisa_uang_masuk < 0:
+ raise UserError("❌ Tidak ada sisa transaksi untuk di-refund.")
- if uang_masuk > pengurangan:
- vals['amount_refund'] = uang_masuk - pengurangan
- else:
- raise UserError("Uang masuk harus lebih besar dari total invoice + ongkir untuk melakukan 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:
+ raise ValidationError('Total Refund harus lebih dari 0 jika ingin mengajukan refund')
+
+ if so_ids and len(so_ids) > 1:
+ existing_refund = self.search([('sale_order_ids', 'in', so_ids)], limit=1)
+ 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 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)
@@ -212,6 +338,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)
@@ -229,8 +358,13 @@ class RefundSaleOrder(models.Model):
refund_type = vals.get('refund_type', rec.refund_type)
- 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 in ['barang_kosong', 'barang_kosong_sebagian'] and sale_orders:
+ 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 != '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)
@@ -245,44 +379,53 @@ class RefundSaleOrder(models.Model):
else:
invoice_ids = rec.invoice_ids.ids
- if invoice_ids and vals.get('refund_type', rec.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 Sebagian jika ada invoice")
+ 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', 'barang_kosong', 'retur_half']:
+ 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 refund_type in ['retur', 'retur_half'] and so_ids:
- so = self.env['sale.order'].browse(so_ids)
- pickings = self.env['stock.picking'].search([
- ('state', '=', 'done'),
- ('picking_type_id', '=', 73),
- ('sale_id', 'in', so_ids)
- ])
+ 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'))
+ vals['total_invoice'] = total_invoice
+ uang_masuk = rec.uang_masuk
- if not pickings:
- raise ValidationError(f"SO {', '.join(so.mapped('name'))} tidak melakukan retur barang.")
+ amount_refund = vals.get('amount_refund', rec.amount_refund)
- if refund_type == 'retur_half' and not invoice_ids:
- raise ValidationError(f"SO {', '.join(so.mapped('name'))} belum memiliki invoice untuk retur sebagian.")
+ if amount_refund <= 0:
+ raise ValidationError("Total Refund harus lebih dari 0.")
- if any(field in vals for field in ['uang_masuk', 'invoice_ids', 'ongkir', 'sale_order_ids']):
- total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total'))
- uang_masuk = vals.get('uang_masuk', rec.uang_masuk)
- ongkir = vals.get('ongkir', rec.ongkir)
+ existing_refunds = self.search([
+ ('sale_order_ids', 'in', so_ids),
+ ('id', '!=', rec.id)
+ ])
+ total_refunded = sum(existing_refunds.mapped('amount_refund'))
+ if existing_refunds:
+ remaining = uang_masuk - total_refunded
+ else:
+ remaining = uang_masuk - amount_refund
- if uang_masuk <= (total_invoice + ongkir):
- raise UserError("Uang masuk harus lebih besar dari total invoice + ongkir")
- vals['amount_refund'] = uang_masuk - (total_invoice + ongkir)
+ if remaining < 0:
+ raise ValidationError("Semua dana sudah dikembalikan, tidak bisa mengajukan refund")
- if vals.get('status') == 'refund' and not vals.get('refund_date'):
- vals['refund_date'] = fields.Date.context_today(self)
+ vals['remaining_refundable'] = remaining
return super().write(vals)
+
+ @api.onchange('amount_refund')
+ def _onchange_refund_fields(self):
+ for rec in self:
+ refund_input = rec.amount_refund or 0.0
+ rec.remaining_refundable = (rec.uang_masuk or 0.0) - refund_input
- @api.depends('status_payment')
+ @api.depends('status_payment', 'status')
def _compute_is_locked(self):
for rec in self:
- rec.is_locked = rec.status_payment in ['done', 'reject']
+ rec.is_locked = rec.status_payment in ['done', 'reject'] or rec.status in ['pengajuan3', 'refund', 'reject']
@api.depends('sale_order_ids.name', 'invoice_ids.name')
def _compute_order_invoice_names(self):
@@ -319,13 +462,28 @@ class RefundSaleOrder(models.Model):
all_invoices = self.env['account.move']
total_invoice = 0.0
+ so_ids = self.sale_order_ids.ids
+ amount_refund_before = 0.0
for so in self.sale_order_ids:
self.ongkir += so.delivery_amt or 0.0
valid_invoices = so.invoice_ids.filtered(
lambda inv: inv.move_type in ['out_invoice', 'out_refund'] and inv.state != 'cancel'
)
all_invoices |= valid_invoices
- total_invoice += sum(valid_invoices.mapped('amount_total'))
+ total_invoice += sum(valid_invoices.mapped('amount_total_signed'))
+ refunds = self.env['refund.sale.order'].search([
+ ('sale_order_ids', 'in', so_ids)
+ ])
+ amount_refund_before += sum(refunds.mapped('amount_refund')) if refunds else 0.0
+
+ 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) - amount_refund_before
self.invoice_ids = all_invoices
@@ -341,6 +499,37 @@ class RefundSaleOrder(models.Model):
if self.sale_order_ids:
self.partner_id = self.sale_order_ids[0].partner_id
+ @api.constrains('sale_order_ids')
+ def _check_sale_orders_payment(self):
+ """ Validasi SO harus punya uang masuk (Journal Uang Muka / Midtrans) """
+ for rec in self:
+ invalid_orders = []
+ total_uang_masuk = 0.0
+
+ for so in rec.sale_order_ids:
+ # cari journal uang muka
+ moves = self.env['account.move'].search([
+ ('sale_id', '=', so.id),
+ ('journal_id', '=', 11), # Journal Uang Muka
+ ('state', '=', 'posted'),
+ ])
+
+ if not moves and so.payment_status != 'settlement':
+ invalid_orders.append(so.name)
+
+ if moves:
+ total_uang_muka = sum(moves.mapped('amount_total_signed')) or 0.0
+ total_uang_masuk += total_uang_muka
+ else:
+ # fallback Midtrans gross_amount
+ total_uang_masuk += so.gross_amount or 0.0
+
+ if invalid_orders:
+ raise ValidationError(
+ f"Tidak dapat membuat refund untuk SO {', '.join(invalid_orders)} "
+ "karena tidak memiliki Record Uang Masuk (Journal Uang Muka/Midtrans).\n"
+ "Pastikan semua SO yang dipilih sudah memiliki Record pembayaran yang valid."
+ )
@api.onchange('refund_type')
def _onchange_refund_type(self):
@@ -353,8 +542,15 @@ class RefundSaleOrder(models.Model):
line_vals.append((0, 0, {
'product_id': line.product_id.id,
'quantity': line.product_uom_qty,
+ '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
@@ -362,20 +558,78 @@ class RefundSaleOrder(models.Model):
line_vals = []
StockPicking = self.env['stock.picking']
for so in self.sale_order_ids:
- pickings = StockPicking.search([
+ # BU/SRT
+ pickings_srt = StockPicking.search([
('state', '=', 'done'),
('picking_type_id', '=', 73),
('sale_id', 'in', so.ids)
])
-
- for picking in pickings:
- for move in picking.move_lines:
- line_vals.append((0, 0, {
- 'product_id': move.product_id.id,
- 'quantity': move.product_uom_qty,
- 'reason': '',
- }))
- self.line_ids = line_vals
+ # BU/ORT
+ pickings_ort = StockPicking.search([
+ ('state', '=', 'done'),
+ ('picking_type_id', '=', 74),
+ ('sale_id', 'in', so.ids)
+ ])
+ if not pickings_ort and not pickings_srt:
+ # BU/OUT
+ product_out = StockPicking.search([
+ ('state', '=', 'done'),
+ ('picking_type_id', '=', 29),
+ ('sale_id', 'in', so.ids)
+ ])
+ for picking in product_out:
+ for move in picking.move_lines:
+ 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:
+ 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:
+ 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')
@@ -400,10 +654,10 @@ class RefundSaleOrder(models.Model):
record.amount_refund_text = ''
def unlink(self):
- not_draft = self.filtered(lambda r: r.status != 'draft')
- if not_draft:
- names = ', '.join(not_draft.mapped('name'))
- raise UserError(f"Refund hanya bisa dihapus jika statusnya masih draft.\nTidak bisa hapus: {names}")
+ incantdelete = self.filtered(lambda r: r.status in ['refund', 'reject'])
+ if incantdelete:
+ names = ', '.join(incantdelete.mapped('name'))
+ raise UserError(f"Refund tidak dapat di hapus jika sudah Confirm/Cancel.\nTidak bisa hapus: {names}")
return super().unlink()
@api.depends('invoice_ids')
@@ -433,30 +687,6 @@ class RefundSaleOrder(models.Model):
pengurangan = rec.total_invoice + rec.ongkir
refund = rec.uang_masuk - pengurangan
rec.amount_refund = refund if refund > 0 else 0.0
-
- @api.model
- def default_get(self, fields_list):
- res = super().default_get(fields_list)
- sale_order_id = self.env.context.get('default_sale_order_id')
- if sale_order_id:
- so = self.env['sale.order'].browse(sale_order_id)
- res['sale_order_ids'] = [(6, 0, [so.id])]
- invoice_ids = so.invoice_ids.filtered(
- lambda inv: inv.move_type in ['out_invoice', 'out_refund'] and inv.state != 'cancel'
- ).ids
- res['invoice_ids'] = [(6, 0, invoice_ids)]
- res['uang_masuk'] = 0.0
- res['ongkir'] = so.delivery_amt or 0.0
- line_vals = []
- for line in so.order_line:
- line_vals.append((0, 0, {
- 'product_id': line.product_id.id,
- 'quantity': line.product_uom_qty,
- 'reason': '',
- }))
- res['line_ids'] = line_vals
- res['refund_type'] = 'uang' if invoice_ids else False
- return res
@api.onchange('invoice_ids')
def _onchange_invoice_ids(self):
@@ -464,10 +694,26 @@ class RefundSaleOrder(models.Model):
if self.refund_type not in ['uang', 'barang_kosong']:
self.refund_type = False
- self.total_invoice = sum(self.invoice_ids.mapped('amount_total'))
+ self.total_invoice = sum(self.invoice_ids.mapped('amount_total_signed'))
def action_ask_approval(self):
for rec in self:
+ if rec.refund_type in ['retur', 'retur_half']:
+ so = rec.sale_order_ids
+ if so:
+ retur_done = self.env['stock.picking'].search_count([
+ ('sale_id', '=', so.id),
+ ('picking_type_id', 'in', [73, 74]),
+ ('state', '=', 'done')
+ ])
+ if retur_done == 0:
+ 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'
@@ -481,6 +727,19 @@ class RefundSaleOrder(models.Model):
now = datetime.now(jakarta_tz).replace(tzinfo=None)
for rec in self:
+ if rec.refund_type in ['retur', 'retur_half']:
+ so = rec.sale_order_ids
+ if so:
+ retur_done = self.env['stock.picking'].search_count([
+ ('sale_id', '=', so.id),
+ ('picking_type_id', 'in', [73, 74]),
+ ('state', '=', 'done')
+ ])
+ if retur_done == 0:
+ raise ValidationError(
+ f"⚠️ SO {so.name} memiliki refund tipe Retur. Selesaikan retur untuk melanjutkan refund"
+ )
+
user_name = self.env.user.name
if not rec.status or rec.status == 'draft':
@@ -512,7 +771,7 @@ class RefundSaleOrder(models.Model):
is_fat = self.env.user.has_group('indoteknik_custom.group_role_fat')
allowed_user_ids = [19, 688, 7]
for rec in self:
- if self.user.id not in allowed_user_ids and not is_fat:
+ if self.env.uid not in allowed_user_ids and not is_fat:
raise UserError("❌ Hanya user yang bersangkutan atau Finance (FAT) yang bisa melakukan penolakan.")
if rec.status not in ['refund', 'reject']:
rec.status = 'reject'
@@ -528,7 +787,7 @@ class RefundSaleOrder(models.Model):
is_fat = self.env.user.has_group('indoteknik_custom.group_role_fat')
for rec in self:
if not is_fat:
- raise UserError("Hanya Finance yang dapat mengkonfirmasi refund.")
+ raise UserError("Hanya Finance yang dapat mengkonfirmasi pembayaran refund.")
if rec.status_payment == 'pending':
rec.status_payment = 'done'
rec.refund_date = fields.Date.context_today(self)
@@ -565,15 +824,26 @@ class RefundSaleOrder(models.Model):
# Ambil label refund type
refund_type_label = dict(
self.fields_get(allfields=['refund_type'])['refund_type']['selection']
- ).get(refund.refund_type, '').replace("Refund ", "").upper()
-
+ ).get(refund.refund_type, '')
+
+ # Normalisasi
+ refund_type_label = refund_type_label.upper()
+
+ if refund.refund_type in ['barang_kosong', 'barang_kosong_sebagian']:
+ refund_type_label = "REFUND BARANG KOSONG"
+ elif refund.refund_type in ['retur_half', 'retur']:
+ refund_type_label = "REFUND RETUR BARANG"
+ elif refund.refund_type == 'uang':
+ refund_type_label = "REFUND LEBIH BAYAR"
+ elif refund.refund_type == 'salah_transfer':
+ refund_type_label = "REFUND SALAH TRANSFER"
if not partner:
raise UserError("❌ Partner tidak ditemukan.")
# Ref format
- ref_text = f"REFUND {refund_type_label} {refund.name or ''} {partner.display_name}".upper()
+ ref_text = f"{refund_type_label} {refund.name or ''} {partner.display_name}".upper()
# Buat Account Move (Journal Entry)
account_move = self.env['account.move'].create({
@@ -586,8 +856,8 @@ class RefundSaleOrder(models.Model):
})
amount = refund.amount_refund
-
- second_account_id = 450 if has_invoice else 668
+ # 450 Penerimaan Belum Teridentifikasi, 668 Penerimaan Belum Alokasi
+ second_account_id = 450 if refund.refund_type not in ['barang_kosong', 'barang_kosong_sebagian'] else 668
debit_line = {
'move_id': account_move.id,
@@ -623,12 +893,20 @@ class RefundSaleOrder(models.Model):
def _compute_journal_refund_move_id(self):
for rec in self:
move = self.env['account.move'].search([
- ('refund_id', '=', rec.id)
+ ('refund_id', '=', rec.id),
+ ('state', '!=', 'cancel')
], limit=1)
rec.journal_refund_move_id = move
def action_open_journal_refund(self):
self.ensure_one()
+
+ is_fat = self.env.user.has_group('indoteknik_custom.group_role_fat')
+ allowed_user_ids = [19, 688, 7]
+
+ if not is_fat and self.env.user.id not in allowed_user_ids:
+ raise UserError(_('Anda tidak memiliki akses untuk membuka Journal Refund.'))
+
if self.journal_refund_move_id:
return {
'name': _('Journal Refund'),
@@ -640,14 +918,211 @@ class RefundSaleOrder(models.Model):
}
+ @api.depends(
+ "sale_order_ids",
+ "sale_order_ids.order_line.price_subtotal",
+ "sale_order_ids.order_line.price_tax",
+ "sale_order_ids.order_line.price_total",
+ "sale_order_ids.order_line.purchase_price",
+ "sale_order_ids.order_line.product_uom_qty",
+ "sale_order_ids.delivery_amt",
+ "sale_order_ids.shipping_cost_covered",
+ )
+ def _compute_amount_from_so(self):
+ for rec in self:
+ untaxed = tax = total_margin = delivery = 0.0
+ for so in rec.sale_order_ids:
+ if so.shipping_cost_covered == 'customer':
+ delivery += so.delivery_amt or 0.0
+ for line in so.order_line:
+ untaxed += line.price_subtotal
+ tax += line.price_tax
+ cost = line.purchase_price * line.product_uom_qty
+ margin = line.price_subtotal - cost
+ total_margin += margin
+ rec.amount_untaxed = untaxed
+ rec.amount_tax = tax
+ rec.amount_total = untaxed + tax
+ rec.total_margin = total_margin
+ rec.delivery_amt = delivery
+ rec.grand_total = rec.amount_total + rec.delivery_amt
+
+
+ @api.depends("sale_order_ids", "sale_order_ids.order_line")
+ def _compute_so_order_lines(self):
+ for rec in self:
+ rec.so_order_line_ids = rec.sale_order_ids.mapped("order_line")
+
+
+
+ @api.depends('refund_type')
+ def _compute_refund_type_display(self):
+ for rec in self:
+ rec.refund_type_display = dict(self.fields_get(allfields=['refund_type'])['refund_type']['selection']).get(rec.refund_type, '')
+
+
+ def _compute_sale_order_count(self):
+ for rec in self:
+ rec.sale_order_count = len(rec.sale_order_ids)
+
+ def _compute_show_return_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_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']:
+ raise UserError("Refund Type harus Retur Full atau Retur Sebagian untuk membuat Tukar Guling.")
+
+ tg_records = []
+ for picking in refund.line_ids.mapped('ref_id'):
+ if not picking:
+ continue
+
+ lines = refund.line_ids.filtered(lambda l: l.ref_id.id == picking.id)
+ line_vals = []
+ koli_lines = []
+ for r_line in lines:
+ qty_done = 0.0
+ move_line = r_line.ref_id.move_line_ids_without_package.filtered(
+ lambda ml: ml.product_id.id == r_line.product_id.id
+ )
+ if move_line:
+ qty_done = sum(move_line.mapped('qty_done'))
+ line_vals.append((0, 0, {
+ 'product_id': r_line.product_id.id,
+ 'product_uom_qty': r_line.quantity,
+ 'name':r_line.product_id.name,
+ 'product_uom':r_line.product_id.uom_id.id
+ }))
+
+ if r_line.ref_id.konfirm_koli_lines.pick_id:
+ koli_lines.append((0, 0,{
+ 'pick_id': r_line.ref_id.konfirm_koli_lines.pick_id.id,
+ 'product_id': r_line.product_id.id,
+ 'qty_done': qty_done,
+ 'qty_return': r_line.quantity,
+ }))
+
+ tg = self.env['tukar.guling'].create({
+ 'partner_id': refund.partner_id.id,
+ 'origin': ','.join(refund.sale_order_ids.mapped('name')),
+ 'origin_so': refund.sale_order_ids.id,
+ 'operations': picking.id,
+ 'return_type': 'revisi_so',
+ 'invoice_id': [(6, 0, refund.invoice_ids.ids)],
+ 'refund_id': refund.id,
+ 'line_ids': line_vals,
+ 'mapping_koli_ids': koli_lines
+ })
+ tg_records.append(tg.id)
+
+ return {
+ 'type': 'ir.actions.act_window',
+ 'name': 'Pengajuan Retur SO',
+ 'res_model': 'tukar.guling',
+ 'view_mode': 'tree,form',
+ 'domain': [('id', 'in', tg_records)],
+ }
+
+ def _compute_tukar_guling_count(self):
+ for rec in self:
+ rec.tukar_guling_count = len(rec.tukar_guling_ids)
+
+
+ def action_open_tukar_guling(self):
+ self.ensure_one()
+ return {
+ 'name': 'Pengajuan Return SO',
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'tree,form',
+ 'res_model': 'tukar.guling',
+ 'domain': [('id', 'in', self.tukar_guling_ids.ids)],
+ 'context': dict(self.env.context, default_refund_id=self.id),
+ }
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='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/res_partner.py b/indoteknik_custom/models/res_partner.py
index 148a3fd0..36570e8f 100644
--- a/indoteknik_custom/models/res_partner.py
+++ b/indoteknik_custom/models/res_partner.py
@@ -1,6 +1,6 @@
from odoo import models, fields, api
from odoo.exceptions import UserError, ValidationError
-from datetime import datetime
+from datetime import datetime, timedelta
from odoo.http import request
import re
import requests
@@ -181,10 +181,8 @@ class ResPartner(models.Model):
payment_difficulty = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', tracking=3)
payment_history_url = fields.Text(string='Payment History URL')
- # no compute
- # payment_diff = fields.Selection([('bermasalah', 'Bermasalah'),('sulit', 'Sulit'),('agak_sulit', 'Agak Sulit'),('normal', 'Normal')], string='Payment Difficulty', tracking=3)
-
- # tidak terpakai
+ is_cbd_locked = fields.Boolean("Locked to CBD?", default=False, tracking=True, help="Jika dicentang, maka partner ini terkunci pada payment term CBD karena memiliki invoice yang sudah jatuh tempo lebih dari 30 hari.")
+
@api.model
def _default_payment_term(self):
@@ -193,9 +191,10 @@ class ResPartner(models.Model):
property_payment_term_id = fields.Many2one(
'account.payment.term',
string='Payment Terms',
- default=_default_payment_term
+ default=_default_payment_term, tracking=3
)
+
@api.depends("street", "street2", "city", "state_id", "country_id", "blok", "nomor", "rt", "rw", "kelurahan_id",
"kecamatan_id")
def _alamat_lengkap_text(self):
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 77fee068..e7dd582d 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -234,9 +234,9 @@ class SaleOrder(models.Model):
customer_type = fields.Selection([
('pkp', 'PKP'),
('nonpkp', 'Non PKP')
- ], required=True, compute='_compute_partner_field')
- sppkp = fields.Char(string="SPPKP", required=True, tracking=True, compute='_compute_partner_field')
- npwp = fields.Char(string="NPWP", required=True, tracking=True, compute='_compute_partner_field')
+ ], related="partner_id.customer_type", string="Customer Type", readonly=True)
+ sppkp = fields.Char(string="SPPKP", related="partner_id.sppkp")
+ npwp = fields.Char(string="NPWP", related="partner_id.npwp")
purchase_total = fields.Monetary(string='Purchase Total', compute='_compute_purchase_total')
voucher_id = fields.Many2one(comodel_name='voucher', string='Voucher', copy=False)
applied_voucher_id = fields.Many2one(comodel_name='voucher', string='Applied Voucher', copy=False)
@@ -358,7 +358,6 @@ class SaleOrder(models.Model):
help="Tanggal pertama kali barang berhasil di-reservasi pada DO (BU/PICK/) yang berstatus Siap Dikirim."
)
refund_ids = fields.Many2many('refund.sale.order', compute='_compute_refund_ids', string='Refunds')
- has_refund = fields.Boolean(string='Has Refund', compute='_compute_has_refund')
refund_count = fields.Integer(string='Refund Count', compute='_compute_refund_count')
advance_payment_move_id = fields.Many2one(
'account.move',
@@ -394,6 +393,26 @@ class SaleOrder(models.Model):
('paid', 'Full Paid'),
('no_invoice', 'No Invoice'),
], string="Payment Status Invoice", compute="_compute_payment_state_custom", store=False)
+ partner_is_cbd_locked = fields.Boolean(
+ string="Partner Locked CBD",
+ compute="_compute_partner_is_cbd_locked"
+ )
+
+ @api.depends('partner_id.is_cbd_locked')
+ def _compute_partner_is_cbd_locked(self):
+ for order in self:
+ order.partner_is_cbd_locked = order.partner_id.is_cbd_locked
+
+
+ @api.constrains('payment_term_id', 'partner_id', 'state')
+ def _check_cbd_lock_sale_order(self):
+ # cbd_term = self.env['account.payment.term'].browse(26)
+ for rec in self:
+ if rec.state == 'draft' and rec.partner_id.is_cbd_locked:
+ # if rec.payment_term_id and rec.payment_term_id != cbd_term:
+ raise ValidationError(
+ "Customer ini terkunci ke CBD, hanya boleh pakai Payment Term CBD."
+ )
@api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual')
def _compute_payment_state_custom(self):
@@ -2030,22 +2049,22 @@ class SaleOrder(models.Model):
# return [('id', 'not in', order_ids)]
# return ['&', ('order_line.invoice_lines.move_id.move_type', 'in', ('out_invoice', 'out_refund')), ('order_line.invoice_lines.move_id', operator, value)]
- @api.depends('partner_id')
- def _compute_partner_field(self):
- for order in self:
- partner = order.partner_id.parent_id or order.partner_id
- order.npwp = partner.npwp
- order.sppkp = partner.sppkp
- order.customer_type = partner.customer_type
+ # @api.depends('partner_id')
+ # def _compute_partner_field(self):
+ # for order in self:
+ # partner = order.partner_id.parent_id or order.partner_id
+ # order.npwp = partner.npwp
+ # order.sppkp = partner.sppkp
+ # order.customer_type = partner.customer_type
@api.onchange('partner_id')
def onchange_partner_contact(self):
parent_id = self.partner_id.parent_id
parent_id = parent_id if parent_id else self.partner_id
- self.npwp = parent_id.npwp
- self.sppkp = parent_id.sppkp
- self.customer_type = parent_id.customer_type
+ # self.npwp = parent_id.npwp
+ # self.sppkp = parent_id.sppkp
+ # self.customer_type = parent_id.customer_type
self.email = parent_id.email
self.pareto_status = parent_id.pareto_status
self.user_id = parent_id.user_id
@@ -2116,15 +2135,21 @@ class SaleOrder(models.Model):
if self.payment_term_id.id == 31 and self.total_percent_margin < 25:
raise UserError("Jika ingin menggunakan Tempo 90 Hari maka margin harus di atas 25%")
- if self.warehouse_id.id != 8 and self.warehouse_id.id != 10: # GD Bandengan
- raise UserError('Gudang harus Bandengan')
+ if self.warehouse_id.id != 8 and self.warehouse_id.id != 10 and self.warehouse_id.id != 12: # GD Bandengan / Pameran
+ raise UserError('Gudang harus Bandengan atau Pameran')
if self.state not in ['draft', 'sent']:
raise UserError("Status harus draft atau sent")
- self._validate_npwp()
-
def _validate_npwp(self):
+ if not self.npwp:
+ raise UserError("NPWP partner kosong, silahkan isi terlebih dahulu npwp nya di contact partner")
+
+ if not self.customer_type:
+ raise UserError("Customer Type partner kosong, silahkan isi terlebih dahulu Customer Type nya di contact partner")
+
+ if not self.sppkp:
+ raise UserError("SPPKP partner kosong, silahkan isi terlebih dahulu SPPKP nya di contact partner")
num_digits = sum(c.isdigit() for c in self.npwp)
if num_digits < 10:
@@ -2138,6 +2163,7 @@ class SaleOrder(models.Model):
self._validate_order()
for order in self:
+ order._validate_npwp()
order._validate_uniform_taxes()
order.order_line.validate_line()
@@ -2192,9 +2218,8 @@ class SaleOrder(models.Model):
if self.validate_different_vendor() and not self.vendor_approval:
return self._create_notification_action('Notification', 'Terdapat Vendor yang berbeda dengan MD Vendor')
self.check_due()
-
- self._validate_order()
for order in self:
+ order._validate_npwp()
order._validate_delivery_amt()
order._validate_uniform_taxes()
order.order_line.validate_line()
@@ -2467,6 +2492,7 @@ class SaleOrder(models.Model):
order.check_data_real_delivery_address()
order.sale_order_check_approve()
order._validate_order()
+ order._validate_npwp()
order.order_line.validate_line()
main_parent = order.partner_id.get_main_parent()
@@ -2652,23 +2678,17 @@ class SaleOrder(models.Model):
def _set_sppkp_npwp_contact(self):
partner = self.partner_id.parent_id or self.partner_id
- if not partner.sppkp:
- partner.sppkp = self.sppkp
- if not partner.npwp:
- partner.npwp = self.npwp
+ # if not partner.sppkp:
+ # partner.sppkp = self.sppkp
+ # if not partner.npwp:
+ # partner.npwp = self.npwp
if not partner.email:
partner.email = self.email
- if not partner.customer_type:
- partner.customer_type = self.customer_type
+ # if not partner.customer_type:
+ # partner.customer_type = self.customer_type
if not partner.user_id:
partner.user_id = self.user_id.id
- # if not partner.sppkp or not partner.npwp or not partner.email or partner.customer_type:
- # partner.customer_type = self.customer_type
- # partner.npwp = self.npwp
- # partner.sppkp = self.sppkp
- # partner.email = self.email
-
def _compute_total_margin(self):
for order in self:
total_margin = sum(line.item_margin for line in order.order_line if line.product_id)
@@ -3110,52 +3130,6 @@ class SaleOrder(models.Model):
# order._update_partner_details()
return order
- # def write(self, vals):
- # Call the super method to handle the write operation
- # res = super(SaleOrder, self).write(vals)
- # self._compute_etrts_date()
- # Check if the update is coming from a save operation
- # if any(field in vals for field in ['sppkp', 'npwp', 'email', 'customer_type']):
- # self._update_partner_details()
-
- # return res
-
- def _update_partner_details(self):
- for order in self:
- partner = order.partner_id.parent_id or order.partner_id
- if partner:
- # Update partner details
- partner.sppkp = order.sppkp
- partner.npwp = order.npwp
- partner.email = order.email
- partner.customer_type = order.customer_type
-
- # Save changes to the partner record
- partner.write({
- 'sppkp': partner.sppkp,
- 'npwp': partner.npwp,
- 'email': partner.email,
- 'customer_type': partner.customer_type,
- })
-
- # def write(self, vals):
- # for order in self:
- # if order.state in ['sale', 'cancel']:
- # if 'order_line' in vals:
- # new_lines = vals.get('order_line', [])
- # for command in new_lines:
- # if command[0] == 0: # A new line is being added
- # raise UserError(
- # "SO tidak dapat ditambahkan produk baru karena SO sudah menjadi sale order.")
- #
- # res = super(SaleOrder, self).write(vals)
- # # self._check_total_margin_excl_third_party()
- # if any(fields in vals for fields in ['delivery_amt', 'carrier_id', 'shipping_cost_covered']):
- # self._validate_delivery_amt()
- # if any(field in vals for field in ["order_line", "client_order_ref"]):
- # self._calculate_etrts_date()
- # return res
-
# @api.depends('commitment_date')
def _compute_ready_to_ship_status_detail(self):
def is_empty(val):
@@ -3284,20 +3258,58 @@ 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),
+ ('state', '=', 'posted'),
+ ])
+
+ # Default 0
+ total_uang_muka = 0.0
+
+ has_moves = bool(moves)
+ has_settlement = self.payment_status == 'settlement'
+
+ if has_moves and has_settlement:
+ total_uang_muka = sum(moves.mapped('amount_total_signed')) + self.gross_amount
+ elif has_moves:
+ total_uang_muka = sum(moves.mapped('amount_total_signed'))
+ elif has_settlement:
+ total_uang_muka = self.gross_amount
+ else:
+ raise UserError(
+ "Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk "
+ "(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'))
+ sisa_uang_muka = total_uang_muka - total_refunded
+
+ if sisa_uang_muka <= 0:
+ raise UserError("❌ Tidak ada sisa transaksi untuk di-refund. Semua dana sudah dikembalikan.")
return {
'name': 'Refund Sale Order',
'type': 'ir.actions.act_window',
'res_model': 'refund.sale.order',
'view_mode': 'form',
+ 'target':'new',
'target': 'current',
'context': {
'default_sale_order_ids': [(6, 0, [self.id])],
'default_invoice_ids': [(6, 0, invoice_ids.ids)],
- 'default_uang_masuk': sum(invoice_ids.mapped('amount_total')) + (self.delivery_amt or 0.0) + 1000,
+ 'default_uang_masuk': sisa_uang_muka,
'default_ongkir': self.delivery_amt or 0.0,
- 'default_bank': '', # bisa isi default bank kalau mau
+ 'default_bank': '',
'default_account_name': '',
'default_account_no': '',
'default_refund_type': '',
@@ -3308,6 +3320,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.")
@@ -3316,14 +3352,43 @@ class SaleOrder(models.Model):
if len(invoice_status_set) > 1:
raise UserError("Tidak dapat membuat refund untuk SO dengan status invoice berbeda. Harus memiliki status invoice yang sama.")
- already_refunded = self.filtered(lambda so: so.has_refund)
- if already_refunded:
- so_names = ', '.join(already_refunded.mapped('name'))
- raise UserError(f"❌ Tidak bisa refund ulang. {so_names} sudah melakukan refund.")
+ refunded_orders = self.filtered(lambda so: self.env['refund.sale.order'].search([('sale_order_ids', 'in', so.id)], limit=1))
+ if refunded_orders:
+ raise ValidationError(
+ f"SO {', '.join(refunded_orders.mapped('name'))} sudah pernah di-refund dan tidak bisa ikut dalam refund Multi SO."
+ )
+
+ total_uang_masuk = 0.0
+ invalid_orders=[]
+ for order in self:
+ moves = self.env['account.move'].search([
+ ('sale_id', '=', order.id),
+ ('journal_id', '=', 11),
+ ('state', '=', 'posted'),
+ ])
+
+ total_uang_muka = 0.0
+
+ if moves and order.payment_status == 'settlement':
+ total_uang_muka = order.gross_amount + sum(moves.mapped('amount_total_signed')) or 0.0
+ elif moves:
+ total_uang_muka = sum(moves.mapped('amount_total_signed')) or 0.0
+ elif order.payment_status == 'settlement':
+ total_uang_muka = order.gross_amount
+ else:
+ invalid_orders.append(order.name)
+
+ total_uang_masuk += total_uang_muka
+
+ if invalid_orders:
+ raise ValidationError(
+ f"Tidak dapat membuat refund untuk SO {', '.join(invalid_orders)} karena tidak memiliki Record Uang Masuk (Journal Uang Muka/Midtrans).\n"
+ "Pastikan semua SO yang dipilih sudah memiliki Record pembayaran yang valid."
+ )
+
invoice_ids = self.mapped('invoice_ids').filtered(lambda inv: inv.state != 'cancel')
delivery_total = sum(self.mapped('delivery_amt'))
- total_invoice = sum(invoice_ids.mapped('amount_total'))
return {
'type': 'ir.actions.act_window',
@@ -3334,7 +3399,7 @@ class SaleOrder(models.Model):
'context': {
'default_sale_order_ids': [(6, 0, self.ids)],
'default_invoice_ids': [(6, 0, invoice_ids.ids)],
- 'default_uang_masuk': total_invoice + delivery_total + 1000,
+ 'default_uang_masuk': total_uang_masuk,
'default_ongkir': delivery_total,
'default_bank': '',
'default_account_name': '',
@@ -3343,11 +3408,6 @@ class SaleOrder(models.Model):
}
}
- @api.depends('refund_ids')
- def _compute_has_refund(self):
- for so in self:
- so.has_refund = bool(so.refund_ids)
-
def action_view_related_refunds(self):
self.ensure_one()
return {
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index 47a24264..1f2ea1fb 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -71,23 +71,17 @@ class SaleOrderLine(models.Model):
if order_qty > 0:
for move in line.move_ids:
- # --- CASE 1: Move belum selesai ---
if move.state not in ('done', 'cancel'):
reserved_qty += move.reserved_availability or 0.0
continue
- # --- CASE 2: Move sudah done ---
if move.location_dest_id.usage == 'customer':
- # Barang dikirim ke customer
delivered_qty += move.quantity_done or 0.0
elif move.location_id.usage == 'customer':
- # Barang balik dari customer (retur)
delivered_qty -= move.quantity_done or 0.0
- # Clamp supaya delivered gak minus
delivered_qty = max(delivered_qty, 0)
- # Hitung persen
line.reserved_percent = min((reserved_qty / order_qty) * 100, 100) if order_qty else 0
line.delivered_percent = min((delivered_qty / order_qty) * 100, 100) if order_qty else 0
line.unreserved_percent = max(100 - line.reserved_percent - line.delivered_percent, 0)
diff --git a/indoteknik_custom/models/stock_move.py b/indoteknik_custom/models/stock_move.py
index 90ab30a4..d6505a86 100644
--- a/indoteknik_custom/models/stock_move.py
+++ b/indoteknik_custom/models/stock_move.py
@@ -1,6 +1,9 @@
from odoo import fields, models, api
from odoo.tools.misc import format_date, OrderedSet
from odoo.exceptions import UserError
+import logging
+
+_logger = logging.getLogger(__name__)
class StockMove(models.Model):
_inherit = 'stock.move'
@@ -15,6 +18,7 @@ class StockMove(models.Model):
barcode = fields.Char(string='Barcode', related='product_id.barcode')
vendor_id = fields.Many2one('res.partner' ,string='Vendor')
hold_outgoingg = fields.Boolean('Hold Outgoing', default=False)
+ product_image = fields.Binary(related="product_id.image_128", string="Product Image", readonly=True)
# @api.model_create_multi
# def create(self, vals_list):
diff --git a/indoteknik_custom/models/stock_picking_return.py b/indoteknik_custom/models/stock_picking_return.py
index 1fc8d088..88acf83c 100644
--- a/indoteknik_custom/models/stock_picking_return.py
+++ b/indoteknik_custom/models/stock_picking_return.py
@@ -110,7 +110,7 @@ class ReturnPicking(models.TransientModel):
if mapping_koli_vals:
context['default_mapping_koli_ids'] = mapping_koli_vals
- if picking.purchase_id or 'PO' in picking.origin:
+ if picking.purchase_id or 'PO' in (picking.origin or ''):
_logger.info("Redirect ke Tukar Guling PO via purchase_id / origin")
return {
'name': _('Tukar Guling PO'),
@@ -120,7 +120,7 @@ class ReturnPicking(models.TransientModel):
'target': 'current',
'context': context,
}
- else:
+ if picking.sale_id or 'SO' in (picking.origin or ''):
_logger.info("This picking is NOT from a PO, fallback to SO.")
return {
'name': _('Tukar Guling SO'),
@@ -130,6 +130,9 @@ class ReturnPicking(models.TransientModel):
'target': 'current',
'context': context,
}
+ else:
+ _logger.info("Bukan SO/PO → retur standar (create_returns)")
+ return super(ReturnPicking, self).create_returns()
class ReturnPickingLine(models.TransientModel):
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 699ee670..d718ba0f 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -27,6 +27,10 @@ class TukarGuling(models.Model):
if_so = fields.Boolean('Is SO', default=True)
if_po = fields.Boolean('Is PO', default=False)
real_shipping_id = fields.Many2one('res.partner', string='Shipping Address')
+ refund_id = fields.Many2one(
+ 'refund.sale.order',
+ string="Refund Ref"
+ )
picking_ids = fields.One2many(
'stock.picking',
'tukar_guling_id',
@@ -92,41 +96,45 @@ 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),
- ]
-
- 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:
raise UserError("Kalau sudah ada invoice Return Invoice Option harus diisi!")
for rec in self:
- if rec.val_inv_opt == 'cancel_invoice' and self.is_has_invoice == True:
+ if rec.val_inv_opt == 'cancel_invoice' and self.is_has_invoice == True and rec.invoice_id.state != 'cancel':
raise UserError("Tidak bisa mengubah Return karena sudah ada invoice dan belum di cancel.")
elif rec.val_inv_opt == 'tanpa_cancel' and self.is_has_invoice == True:
continue
@@ -435,9 +443,9 @@ class TukarGuling(models.Model):
# if self.state == 'done':
# raise UserError ("Tidak Boleh delete ketika sudahh done")
for record in self:
- if record.state == 'approved' or record.state == 'done':
+ if record.state in [ 'approved', 'done', 'approval_logistic', 'approval_finance', 'approval_sales']:
raise UserError(
- "Tidak bisa hapus pengajuan jika sudah Approved, set ke draft terlebih dahulu jika ingin menghapus")
+ "Tidak bisa hapus pengajuan jika sudah Proses Approval, set ke draft terlebih dahulu atau cancel jika ingin menghapus")
ongoing_bu = self.picking_ids.filtered(lambda p: p.state != 'approved')
for picking in ongoing_bu:
picking.action_cancel()
@@ -702,7 +710,7 @@ class TukarGuling(models.Model):
### ======== SRT dari BU/OUT =========
srt_return_lines = []
- if mapping_koli:
+ if mapping_koli and record.operations.picking_type_id.id == 29:
for prod in mapping_koli.mapped('product_id'):
qty_total = sum(mk.qty_return for mk in mapping_koli.filtered(lambda m: m.product_id == prod))
move = bu_out.move_lines.filtered(lambda m: m.product_id == prod)
@@ -715,7 +723,7 @@ class TukarGuling(models.Model):
}))
_logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}")
- elif not mapping_koli:
+ elif not mapping_koli and record.operations.picking_type_id.id == 29:
for line in record.line_ids:
move = bu_out.move_lines.filtered(lambda m: m.product_id == line.product_id)
if not move:
@@ -1002,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/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py
index 94771f37..f2f37606 100644
--- a/indoteknik_custom/models/tukar_guling_po.py
+++ b/indoteknik_custom/models/tukar_guling_po.py
@@ -381,8 +381,8 @@ class TukarGulingPO(models.Model):
def unlink(self):
for record in self:
- if record.state == 'done' or record.state == 'approved':
- raise UserError("Tidak bisa hapus pengajuan jika sudah done, set ke draft terlebih dahulu")
+ if record.state in [ 'approved', 'done', 'approval_logistic', 'approval_finance', 'approval_purchase']:
+ raise UserError("Tidak bisa hapus pengajuan jika sudah proses approval atau done, set ke draft atau cancel terlebih dahulu")
ongoing_bu = self.po_picking_ids.filtered(lambda p: p.state != 'done')
for picking in ongoing_bu:
picking.action_cancel()
diff --git a/indoteknik_custom/report/purchase_report.xml b/indoteknik_custom/report/purchase_report.xml
new file mode 100644
index 00000000..168428a6
--- /dev/null
+++ b/indoteknik_custom/report/purchase_report.xml
@@ -0,0 +1,149 @@
+<?xml version="1.0" encoding="utf-8"?>
+<odoo>
+ <data>
+ <!-- Report Action -->
+ <record id="action_report_purchaseorder_website" model="ir.actions.report">
+ <field name="name">Purchase Order (Website)</field>
+ <field name="model">purchase.order</field>
+ <field name="report_type">qweb-pdf</field>
+ <field name="report_name">indoteknik_custom.report_purchaseorder_website</field>
+ <field name="report_file">indoteknik_custom.report_purchaseorder_website</field>
+ <field name="print_report_name">
+ ('PO - %s - %s' % (object.partner_id.name, object.name))
+ </field>
+ <field name="binding_model_id" ref="purchase.model_purchase_order"/>
+ <field name="binding_type">report</field>
+ </record>
+ </data>
+
+ <!-- Wrapper Template -->
+ <template id="report_purchaseorder_website">
+ <t t-call="web.html_container">
+ <t t-foreach="docs" t-as="doc">
+ <t t-call="indoteknik_custom.report_purchaseorder_website_document" t-lang="doc.partner_id.lang"/>
+ </t>
+ </t>
+ </template>
+
+ <template id="report_purchaseorder_website_document">
+ <t t-call="web.html_container">
+ <t t-set="doc" t-value="doc.with_context(lang=doc.partner_id.lang)" />
+
+ <!-- Header -->
+ <div class="header">
+ <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2498521"
+ style="width:100%; display: block;"/>
+ </div>
+
+
+ <!-- PAGE CONTENT -->
+ <div class="article" style="margin: 0 1.5cm 0 1.5cm; ">
+ <!-- TITLE -->
+ <h2 style="text-align:center; margin:0; color:#d32f2f; font-weight:bold;">
+ PURCHASE ORDER
+ </h2>
+ <h4 style="text-align:center; margin:0 0 20px 0;">
+ No. <span t-field="doc.name"/>
+ </h4>
+
+ <!-- TOP INFO -->
+ <table style="width:100%; margin-bottom:16px; font-size:14px;">
+ <tr>
+ <td><strong>Term Of Payment:</strong> <span t-field="doc.payment_term_id.name"/></td>
+ <td><strong>Order Date:</strong> <span t-field="doc.date_order" t-options='{"widget": "date"}'/></td>
+ <td><strong>Responsible:</strong> <span t-field="doc.user_id"/></td>
+ </tr>
+ </table>
+
+ <!-- VENDOR & DELIVERY -->
+ <table style="width:100%; margin-bottom:24px; border-collapse:separate; border-spacing:16px 0;">
+ <tr>
+ <td style="width:50%; border:1px solid #ccc; padding:8px; vertical-align:top;">
+ <strong>Alamat Pengiriman:</strong><br/>
+ PT Indoteknik (Bandengan 1 Depan)<br/>
+ Jl. Bandengan Utara Komp A 8 B<br/>
+ RT. Penjaringan, Kec. Penjaringan, Jakarta (BELAKANG INDOMARET)<br/>
+ JK 14440 - Indonesia
+ </td>
+ <td style="width:50%; border:1px solid #ccc; padding:8px; vertical-align:top;">
+ <strong>Nama Vendor:</strong><br/>
+ <span t-field="doc.partner_id.name"/><br/>
+ <span t-field="doc.partner_id.street"/><br/>
+ <span t-field="doc.partner_id.city"/> - <span t-field="doc.partner_id.zip"/>
+ </td>
+ </tr>
+ </table>
+
+ <!-- ORDER LINES -->
+ <table style="border-collapse:collapse; width:100%; margin-top:16px;">
+ <tbody>
+ <tr style="background:#f2f2f2;">
+ <td style="border:1px solid #ccc;">Description</td>
+ <td style="border:1px solid #ccc; text-align:right;">Quantity</td>
+ <td style="border:1px solid #ccc; text-align:right;">Unit Price</td>
+ <td style="border:1px solid #ccc; text-align:right;">Taxes</td>
+ <td style="border:1px solid #ccc; text-align:right;">Subtotal</td>
+ </tr>
+ </tbody>
+ <tbody>
+ <t t-foreach="doc.order_line" t-as="line">
+ <tr>
+ <td style="border:1px solid #ccc;">
+ <span t-field="line.name"/>
+ </td>
+ <td style="border:1px solid #ccc; text-align:right;">
+ <span t-field="line.product_qty"/> <span t-field="line.product_uom"/>
+ </td>
+ <td style="border:1px solid #ccc; text-align:right;">
+ <span t-field="line.price_unit"/>
+ </td>
+ <td style="border:1px solid #ccc; text-align:right;">
+ <span t-esc="', '.join(map(lambda x: (x.description or x.name), line.taxes_id))"/>
+ </td>
+ <td style="border:1px solid #ccc; text-align:right;">
+ <span t-field="line.price_subtotal"/>
+ </td>
+ </tr>
+ <t t-if="line.product_id.website_description">
+ <tr>
+ <td colspan="5" style="margin-top: 1rem;padding: 1rem; background:#fafafa; border-left:1px solid #ccc; border-right:1px solid #ccc;">
+ <div t-raw="line.product_id.website_description"/>
+ </td>
+ </tr>
+ </t>
+ </t>
+ </tbody>
+ </table>
+
+ <!-- TOTALS -->
+ <table style="margin-top:20px; margin-left:auto; width:40%; font-size:14px;">
+ <tr>
+ <td><strong>Subtotal</strong></td>
+ <td style="text-align:right;"><span t-field="doc.amount_untaxed"/></td>
+ </tr>
+ <tr>
+ <td>Taxes</td>
+ <td style="text-align:right;"><span t-field="doc.amount_tax"/></td>
+ </tr>
+ <tr>
+ <td><strong>Total</strong></td>
+ <td style="text-align:right;"><span t-field="doc.amount_total"/></td>
+ </tr>
+ </table>
+
+ <!-- NOTES -->
+ <div style="margin-top:24px;">
+ <p t-field="doc.notes"/>
+ </div>
+ </div>
+ <!-- STATIC FOOTER -->
+ <div class="footer">
+ <img src="https://erp.indoteknik.com/api/image/ir.attachment/datas/2859765"
+ style="width:100%; display: block;"/>
+ </div>
+
+ </t>
+ </template>
+
+
+</odoo>
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 78b3dc0f..3a320510 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -161,7 +161,6 @@ access_konfirm_koli,access.konfirm.koli,model_konfirm_koli,,1,1,1,1
access_stock_immediate_transfer,access.stock.immediate.transfer,model_stock_immediate_transfer,,1,1,1,1
access_coretax_faktur,access.coretax.faktur,model_coretax_faktur,,1,1,1,1
access_purchase_order_unlock_wizard,access.purchase.order.unlock.wizard,model_purchase_order_unlock_wizard,,1,1,1,1
-access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1
access_sales_order_koli,access.sales.order.koli,model_sales_order_koli,,1,1,1,1
access_stock_backorder_confirmation,access.stock.backorder.confirmation,model_stock_backorder_confirmation,,1,1,1,1
access_warning_modal_wizard,access.warning.modal.wizard,model_warning_modal_wizard,,1,1,1,1
@@ -184,7 +183,8 @@ access_production_purchase_match,access.production.purchase.match,model_producti
access_image_carousel,access.image.carousel,model_image_carousel,,1,1,1,1
access_v_sale_notin_matchpo,access.v.sale.notin.matchpo,model_v_sale_notin_matchpo,,1,1,1,1
access_approval_payment_term,access.approval.payment.term,model_approval_payment_term,,1,1,1,1
-
+access_refund_sale_order,access.refund.sale.order,model_refund_sale_order,base.group_user,1,1,1,1
+access_refund_sale_order_line,access.refund.sale.order.line,model_refund_sale_order_line,base.group_user,1,1,1,1
access_purchasing_job_seen,purchasing.job.seen,model_purchasing_job_seen,,1,1,1,1
access_tukar_guling_all_users,tukar.guling.all.users,model_tukar_guling,base.group_user,1,1,1,1
@@ -194,4 +194,5 @@ access_tukar_guling_line_po_all_users,tukar.guling.line.po.all.users,model_tukar
access_tukar_guling_mapping_koli_all_users,tukar.guling.mapping.koli.all.users,model_tukar_guling_mapping_koli,base.group_user,1,1,1,1
access_purchase_order_update_date_wizard,access.purchase.order.update.date.wizard,model_purchase_order_update_date_wizard,base.group_user,1,1,1,1
access_sync_promise_date_wizard,access.sync.promise.date.wizard,model_sync_promise_date_wizard,base.group_user,1,1,1,1
-access_sync_promise_date_wizard_line,access.sync.promise.date.wizard.line,model_sync_promise_date_wizard_line,base.group_user,1,1,1,1 \ No newline at end of file
+access_sync_promise_date_wizard_line,access.sync.promise.date.wizard.line,model_sync_promise_date_wizard_line,base.group_user,1,1,1,1
+access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1 \ No newline at end of file
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index b399d4c9..c88effd5 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -38,9 +38,10 @@
<!-- <field name="purchase_order_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'in_invoice')]}"/> -->
</field>
<field name="ref" position="after">
- <field name="sale_id" readonly="1" attrs="{'invisible': [('move_type', '!=', 'entry')]}"/>
- <!-- <field name="refund_so_links" readonly="1" widget="html" attrs="{'invisible': ['|', ('move_type', '!=', 'entry'), ('has_refund_so', '=', False)]}"/>
- <field name="has_refund_so" invisible="1"/> -->
+ <field name="sale_id" readonly="1" attrs="{'invisible': ['|', ('move_type', '!=', 'entry'), ('has_refund_so', '=', True)]}"/>
+ <field name="refund_id" readonly="1" attrs="{'invisible': ['|', ('move_type', '!=', 'entry'), ('has_refund_so', '=', False)]}"/>
+ <field name="refund_so_links" readonly="1" widget="html" attrs="{'invisible': ['|', ('move_type', '!=', 'entry'), ('has_refund_so', '=', False)]}"/>
+ <field name="has_refund_so" invisible="1"/>
</field>
<field name="reklas_misc_id" position="after">
<field name="purchase_order_id" context="{'form_view_ref': 'purchase.purchase_order_form'}" options="{'no_create': True}"/>
diff --git a/indoteknik_custom/views/approval_payment_term.xml b/indoteknik_custom/views/approval_payment_term.xml
index 5c130f3f..b0b99689 100644
--- a/indoteknik_custom/views/approval_payment_term.xml
+++ b/indoteknik_custom/views/approval_payment_term.xml
@@ -7,7 +7,7 @@
<tree default_order="create_date desc">
<field name="number"/>
<field name="partner_id"/>
- <field name="parent_id"/>
+ <field name="parent_id" optional="hide"/>
<field name="property_payment_term_id"/>
<field name="create_date" optional="hide"/>
<field name="approve_date" optional="hide"/>
diff --git a/indoteknik_custom/views/dunning_run.xml b/indoteknik_custom/views/dunning_run.xml
index 210f7917..51377f78 100644
--- a/indoteknik_custom/views/dunning_run.xml
+++ b/indoteknik_custom/views/dunning_run.xml
@@ -13,7 +13,7 @@
<field name="resi_tukar_faktur"/>
<field name="date_terima_tukar_faktur"/>
<field name="shipper_faktur_id"/>
- <field name="grand_total"/>
+ <field name="grand_total" sum="Grand Total"/>
<field name="create_uid" optional="hide"/>
</tree>
</field>
@@ -25,13 +25,14 @@
<field name="arch" type="xml">
<tree>
<field name="partner_id"/>
+ <field name="reference"/>
<field name="invoice_id"/>
<field name="date_invoice"/>
- <field name="efaktur_id"/>
- <field name="reference"/>
+ <field name="efaktur_id" optional="hide"/>
<field name="total_amt" sum="Grand Total Amount"/>
<field name="open_amt"/>
<field name="due_date"/>
+ <field name="payment_term"/>
</tree>
</field>
</record>
diff --git a/indoteknik_custom/views/ir_sequence.xml b/indoteknik_custom/views/ir_sequence.xml
index 4915e4c5..94c2cd07 100644
--- a/indoteknik_custom/views/ir_sequence.xml
+++ b/indoteknik_custom/views/ir_sequence.xml
@@ -220,7 +220,7 @@
</record>
<record id="seq_refund_sale_order" model="ir.sequence">
- <field name="name">Refund Sale Order</field>
+ <field name="name">Refund Sales Order</field>
<field name="code">refund.sale.order</field>
<field name="prefix">RC/%(year)s/%(month)s/</field>
<field name="padding">4</field>
diff --git a/indoteknik_custom/views/refund_sale_order.xml b/indoteknik_custom/views/refund_sale_order.xml
new file mode 100644
index 00000000..0c6cd371
--- /dev/null
+++ b/indoteknik_custom/views/refund_sale_order.xml
@@ -0,0 +1,309 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<odoo>
+ <!-- Tree View -->
+ <record id="view_refund_sale_order_tree" model="ir.ui.view">
+ <field name="name">refund.sale.order.tree</field>
+ <field name="model">refund.sale.order</field>
+ <field name="arch" type="xml">
+ <tree string="Refund Sales Orders">
+ <field name="name" readonly="1"/>
+ <field name="created_date" readonly="1"/>
+ <field name="partner_id" readonly="1"/>
+ <field name="sale_order_ids" widget="many2many_tags" readonly="1"/>
+ <field name="uang_masuk" readonly="1"/>
+ <field name="ongkir" readonly="1"/>
+ <field name="total_invoice" readonly="1"/>
+ <field name="amount_refund" readonly="1"/>
+ <field name="remaining_refundable" readonly="1" optional="hide"/>
+ <field name="status"
+ decoration-info="status == 'draft'"
+ decoration-danger="status == 'reject'"
+ decoration-success="status == 'refund'"
+ decoration-warning="status == 'pengajuan1' or status == 'pengajuan2' or status == 'pengajuan3'"
+ widget="badge"
+ readonly="1"/>
+ <field name="status_payment"
+ decoration-info="status_payment == 'pending'"
+ decoration-danger="status_payment == 'reject'"
+ decoration-success="status_payment == 'done'"
+ widget="badge"
+ readonly="1"/>
+ <field name="refund_date" readonly="1"/>
+ <field name="amount_refund_text" readonly="1" optional="hide"/>
+ <field name="invoice_ids" readonly="1" optional="hide"/>
+ <field name="refund_type" readonly="1" optional="hide"/>
+ <field name="user_ids" readonly="1" optional="hide"/>
+ </tree>
+ </field>
+ </record>
+
+ <!-- Form View -->
+ <record id="view_refund_sale_order_form" model="ir.ui.view">
+ <field name="name">refund.sale.order.form</field>
+ <field name="model">refund.sale.order</field>
+ <field name="arch" type="xml">
+ <form string="Refund Sales Order">
+ <header>
+ <button name="action_ask_approval"
+ type="object"
+ string="Ask Approval"
+ attrs="{'invisible': [('status', '!=', 'draft')]}"/>
+
+ <button name="action_approve_flow"
+ type="object"
+ string="Approve"
+ class="oe_highlight"
+ attrs="{'invisible': [('status', 'in', ['refund', 'reject', 'draft'])]}"/>
+ <button name="action_trigger_cancel"
+ type="object"
+ string="Cancel"
+ attrs="{'invisible': ['|', ('status_payment', '!=', 'pending'), ('status', 'in', ['reject', 'refund'])]}" />
+ <button name="action_confirm_refund"
+ type="object"
+ string="Confirm Payment"
+ class="btn-primary"
+ attrs="{'invisible': ['|', ('status', 'not in', ['pengajuan3','refund']), ('status_payment', '!=', 'pending')]}"/>
+ <button name="action_create_journal_refund"
+ string="AP Only"
+ type="object"
+ class="oe_highlight"
+ attrs="{'invisible': ['|', ('journal_refund_state', 'in', ['posted', 'draft']), ('status', 'not in', ['pengajuan3','refund'])]}"/>
+ <button name="action_create_tukar_guling"
+ string="Create Return"
+ type="object"
+ class="oe_highlight"
+ attrs="{'invisible': [('refund_type', 'not in', ['retur_half', 'retur'])]}"/>
+
+ <field name="status"
+ widget="statusbar"
+ statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,reject"
+ attrs="{'invisible': [('status', '!=', 'reject')]}" />
+
+ <field name="status"
+ widget="statusbar"
+ statusbar_visible="draft,pengajuan1,pengajuan2,pengajuan3,refund"
+ attrs="{'invisible': [('status', '=', 'reject')]}" />
+ </header>
+ <xpath expr="//sheet" position="inside">
+ <field name="show_return_alert" invisible="1"/>
+ <div class="alert alert-danger" role="alert"
+ 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">
+ <button name="action_open_journal_refund"
+ type="object"
+ class="oe_stat_button"
+ icon="fa-book"
+ width="250px"
+ 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"
+ type="object"
+ class="oe_stat_button"
+ icon="fa-refresh"
+ 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"
+ title="PAID"
+ bg_color="bg-success"
+ attrs="{'invisible': [('status_payment', '!=', 'done')]}"/>
+
+ <widget name="web_ribbon"
+ title="CANCEL"
+ bg_color="bg-danger"
+ attrs="{'invisible': [('status_payment', '!=', 'reject')]}"/>
+ <h1>
+ <field name="name" readonly="1"/>
+ </h1>
+ <group col="2">
+ <group>
+ <field name="is_locked" invisible="1"/>
+ <field name="status_payment" invisible="1"/>
+ <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)], '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" 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)]}"/>
+ <field name="bukti_uang_masuk_image" widget="image"
+ attrs="{'invisible': [('uang_masuk_type', '=', 'pdf')], 'readonly': [('is_locked', '=', True)]}"/>
+ <field name="bukti_uang_masuk_pdf" widget="pdf_viewer"
+ attrs="{'invisible': [('uang_masuk_type', '=', 'image')], 'readonly': [('is_locked', '=', True)]}"/>
+ </group>
+ </group>
+
+ <notebook>
+ <page string="Produk Line">
+ <field name="line_ids" attrs="{'readonly': [('is_locked', '=', True)]}">
+ <tree editable="bottom" create="0" delete="1">
+ <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>
+ </page>
+
+ <page string="Other Info">
+ <group col="2">
+ <group>
+ <field name="user_ids" widget="many2many_tags" readonly="1"/>
+ <field name="created_date" readonly="1"/>
+ <field name="refund_date" attrs="{'readonly': [('status', 'not in', ['pengajuan3','refund'])]}"/>
+ </group>
+ <group>
+ <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>
+
+ <page string="Finance Note">
+ <group col="2">
+ <group>
+ <field name="finance_note"/>
+ </group>
+ <group>
+ <field name="bukti_refund_type" reqiured="1"/>
+ <field name="bukti_transfer_refund_pdf" widget="pdf_viewer" attrs="{'invisible': [('bukti_refund_type', '=', 'image')]}"/>
+ <field name="bukti_transfer_refund_image" widget="image" attrs="{'invisible': [('bukti_refund_type', '=', 'pdf')]}"/>
+ </group>
+ </group>
+ </page>
+ <page string="Sales Order Lines">
+ <field name="so_order_line_ids" nolabel="1" readonly="1">
+ <tree>
+ <field name="order_id"/>
+ <field name="product_id"/>
+ <field name="purchase_price"/>
+ <field name="product_uom_qty"/>
+ <field name="price_unit"/>
+ <field name="tax_id" widget="many2many_tags"/>
+ <field name="discount"/>
+ <field name="price_subtotal"/>
+ <field name="item_percent_margin"/>
+ <field name="item_percent_margin_before"/>
+ </tree>
+ </field>
+ <group class="oe_subtotal_footer oe_right" colspan="2" name="refund_total">
+ <field name="amount_untaxed" widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ <field name="amount_tax" widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ <div class="oe_subtotal_footer_separator oe_inline o_td_label">
+ <label for="amount_total"/>
+ </div>
+ <field name="amount_total" nolabel="1" class="oe_subtotal_footer_separator"
+ widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ <field name="delivery_amt" widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ <div class="oe_subtotal_footer_separator oe_inline o_td_label">
+ <label for="grand_total"/>
+ </div>
+ <field name="grand_total" nolabel="1" class="oe_subtotal_footer_separator"
+ widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ <field name="total_margin" widget="monetary" options="{'currency_field': 'currency_id'}" readonly="1"/>
+ </group>
+ </page>
+
+ <page string="Cancel Reason" attrs="{'invisible': [('status', '=', 'refund')]}">
+ <group>
+ <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>
+ </record>
+ <!-- Action -->
+ <record id="action_refund_sale_order" model="ir.actions.act_window">
+ <field name="name">Refund Sales Order</field>
+ <field name="res_model">refund.sale.order</field>
+ <field name="view_mode">tree,form</field>
+ </record>
+
+ <!-- Menu -->
+ <menuitem id="menu_refund_sale_order"
+ name="Refund"
+ parent="sale.sale_order_menu"
+ sequence="10"
+ action="action_refund_sale_order"/>
+</odoo>
diff --git a/indoteknik_custom/views/res_partner.xml b/indoteknik_custom/views/res_partner.xml
index ca1a36de..c32151d8 100644
--- a/indoteknik_custom/views/res_partner.xml
+++ b/indoteknik_custom/views/res_partner.xml
@@ -21,6 +21,7 @@
<field name="reference_number"/>
</field>
<field name="property_payment_term_id" position="after">
+ <field name="is_cbd_locked" readonly="1"/>
<field name="user_payment_terms_sales" readonly="1"/>
<field name="date_payment_terms_sales" readonly="1"/>
</field>
@@ -35,9 +36,9 @@
<field name="pareto_status"/>
<field name="digital_invoice_tax"/>
</field>
- <field name="nama_wajib_pajak" position="attributes">
+ <!-- <field name="nama_wajib_pajak" position="attributes">
<attribute name="required">1</attribute>
- </field>
+ </field> -->
<field name="kota_id" position="attributes">
<attribute name="required">0</attribute>
</field>
@@ -47,14 +48,14 @@
<field name="kelurahan_id" position="attributes">
<attribute name="required">0</attribute>
</field>
- <field name="npwp" position="attributes">
+ <!-- <field name="npwp" position="attributes">
<attribute name="required">1</attribute>
</field>
<field name="alamat_lengkap_text" position="attributes">
<attribute name="required">1</attribute>
- </field>
+ </field> -->
<field name="npwp" position="before">
- <field name="customer_type" required="1"/>
+ <field name="customer_type"/>
</field>
<field name="alamat_lengkap_text" position="after">
<field name="nitku" />
@@ -107,7 +108,7 @@
<field name="reminder_invoices"/>
</xpath>
<xpath expr="//field[@name='property_payment_term_id']" position="attributes">
- <attribute name="readonly">0</attribute>
+ <attribute name="readonly">1</attribute>
</xpath>
<xpath expr="//field[@name='property_supplier_payment_term_id']" position="attributes">
<attribute name="readonly">1</attribute>
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index a1a5e0cd..44da3e13 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -35,13 +35,21 @@
string="UangMuka"
type="action" attrs="{'invisible': [('approval_status', '!=', 'approved')]}"/>
</button>
- <!-- <xpath expr="//header" position="inside">
+ <xpath expr="//header" position="inside">
<button name="button_refund"
type="object"
string="Refund"
- class="btn-primary"
- attrs="{'invisible': ['|', ('state', 'not in', ['sale', 'done']), ('has_refund', '=', True)]}" />
- </xpath> -->
+ class="btn-primary" />
+ </xpath>
+ <xpath expr="//sheet" position="before">
+ <field name="partner_is_cbd_locked" invisible="1"/>
+ <div class="alert alert-danger"
+ role="alert"
+ style="height: 40px; margin-bottom:0px;"
+ attrs="{'invisible':['|', ('partner_is_cbd_locked','=',False), ('state', 'not in', ['draft', 'cancel'])]}">
+ <strong>Warning!</strong> Payment Terms Customer terkunci menjadi <b>Cash Before Delivery (C.B.D.)</b> karena ada invoice telah jatuh tempo <b>30 hari</b>. Silakan ajukan <b>Approval Payment Term</b> untuk membuka kunci.
+ </div>
+ </xpath>
<div class="oe_button_box" name="button_box">
<field name="advance_payment_move_ids" invisible="1"/>
<button name="action_open_advance_payment_moves"
@@ -52,13 +60,13 @@
<field name="advance_payment_move_count" widget="statinfo" string="Journals"/>
</button>
- <!-- <button type="object"
+ <button type="object"
name="action_view_related_refunds"
class="oe_stat_button"
icon="fa-refresh"
attrs="{'invisible': [('refund_count', '=', 0)]}">
<field name="refund_count" widget="statinfo" string="Refund"/>
- </button> -->
+ </button>
</div>
<field name="payment_term_id" position="after">
<field name="create_uid" invisible="1"/>
@@ -140,9 +148,9 @@
<field name="pareto_status"/>
</field>
<field name="analytic_account_id" position="after">
- <field name="customer_type" readonly="1"/>
- <field name="npwp" placeholder='99.999.999.9-999.999' readonly="1"/>
- <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}" readonly="1"/>
+ <field name="customer_type"/>
+ <field name="npwp" placeholder='99.999.999.9-999.999'/>
+ <field name="sppkp" attrs="{'required': [('customer_type', '=', 'pkp')]}"/>
<field name="email" required="1"/>
<field name="unreserve_id"/>
<field name="due_id" readonly="1"/>
@@ -177,7 +185,6 @@
<field name="expected_ready_to_ship"/>
<field name="eta_date_start"/>
<field name="eta_date" readonly="1"/>
- <!-- <field name="has_refund" readonly="1"/> -->
</group>
<group string="Return Doc">
<field name="ccm_id" readonly="1"/>
@@ -674,7 +681,7 @@
</record>
</data>
- <!-- <data>
+ <data>
<record id="sale_order_multi_create_refund_ir_actions_server" model="ir.actions.server">
<field name="name">Refund</field>
<field name="model_id" ref="sale.model_sale_order"/>
@@ -682,7 +689,7 @@
<field name="state">code</field>
<field name="code">action = records.open_form_multi_create_refund()</field>
</record>
- </data> -->
+ </data>
<data>
<record id="mail_template_sale_order_notification_to_salesperson" model="mail.template">
diff --git a/indoteknik_custom/views/stock_move_line.xml b/indoteknik_custom/views/stock_move_line.xml
index 757d2522..94c0bf53 100644
--- a/indoteknik_custom/views/stock_move_line.xml
+++ b/indoteknik_custom/views/stock_move_line.xml
@@ -3,18 +3,19 @@
<record id="stock_move_line_form_view_inherited" model="ir.ui.view">
<field name="name">Stock Move Line</field>
<field name="model">stock.move.line</field>
- <field name="inherit_id" ref="stock.view_move_line_form" />
+ <field name="inherit_id" ref="stock.view_move_line_form"/>
+ <field name="priority" eval="100"/>
<field name="arch" type="xml">
- <field name="qty_done" position="after">
+ <xpath expr="(//form//group[.//field[@name='qty_done']])[last()]" position="inside">
<field name="manufacture"/>
- </field>
+ </xpath>
</field>
</record>
<record id="stock_move_line_tree_view_inherited" model="ir.ui.view">
<field name="name">Stock Move Line</field>
<field name="model">stock.move.line</field>
- <field name="inherit_id" ref="stock.view_move_line_tree" />
+ <field name="inherit_id" ref="stock.view_move_line_tree"/>
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="manufacture"/>
diff --git a/indoteknik_custom/views/stock_picking.xml b/indoteknik_custom/views/stock_picking.xml
index b3f0ce9f..fc8be790 100644
--- a/indoteknik_custom/views/stock_picking.xml
+++ b/indoteknik_custom/views/stock_picking.xml
@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<tree position="attributes">
<attribute name="default_order">final_seq asc</attribute>
- <!-- <attribute name="default_order">create_date desc</attribute> -->
+ <!-- <attribute name="default_order">create_date desc</attribute> -->
</tree>
<field name="json_popover" position="after">
<field name="date_done" optional="hide"/>
@@ -20,9 +20,11 @@
<field name="sj_return_date" optional="hide"/>
<field name="date_reserved" optional="hide"/>
<field name="state_reserve" optional="hide"/>
- <field name="state_packing" widget="badge" decoration-success="state_packing == 'packing_done'" decoration-danger="state_packing == 'not_packing'" optional="hide"/>
+ <field name="state_packing" widget="badge" decoration-success="state_packing == 'packing_done'"
+ decoration-danger="state_packing == 'not_packing'" optional="hide"/>
<field name="final_seq"/>
- <field name="state_approve_md" widget="badge" decoration-success="state_approve_md == 'done'" decoration-warning="state_approve_md == 'pending'" optional="hide"/>
+ <field name="state_approve_md" widget="badge" decoration-success="state_approve_md == 'done'"
+ decoration-warning="state_approve_md == 'pending'" optional="hide"/>
<!-- <field name="countdown_hours" optional="hide"/>
<field name="countdown_ready_to_ship" /> -->
</field>
@@ -50,11 +52,11 @@
type="object"
attrs="{'invisible': ['|', ('state', 'in', ['done']), ('approval_receipt_status', '=', 'pengajuan1')]}"
/>
-<!-- <button name="ask_return_approval"-->
-<!-- string="Ask Return/Acc"-->
-<!-- type="object"-->
-<!-- attrs="{'invisible': [('state', 'in', ['draft', 'cancel', 'assigned'])]}"-->
-<!-- />-->
+ <!-- <button name="ask_return_approval"-->
+ <!-- string="Ask Return/Acc"-->
+ <!-- type="object"-->
+ <!-- attrs="{'invisible': [('state', 'in', ['draft', 'cancel', 'assigned'])]}"-->
+ <!-- />-->
<button name="action_create_invoice_from_mr"
string="Create Bill"
type="object"
@@ -64,12 +66,12 @@
string="Biteship"
type="object"
/>
- <!-- <button name="action_sync_biteship_tracking"
- type="object"
- string="Lacak dari Biteship"
- class="btn-primary"
- attrs="{'invisible': [('biteship_id', '=', False)]}"
- /> -->
+ <!-- <button name="action_sync_biteship_tracking"
+ type="object"
+ string="Lacak dari Biteship"
+ class="btn-primary"
+ attrs="{'invisible': [('biteship_id', '=', False)]}"
+ /> -->
<button name="track_envio_shipment"
string="Tracking Envio"
type="object"
@@ -97,6 +99,11 @@
attrs="{'invisible': [('state_approve_md', 'not in', ['waiting'])]}"
/>
</button>
+ <!-- <xpath expr="//field[@name='move_ids_without_package']//tree//field[@name='product_uom']"
+ position="after">
+ <field name="product_image" widget="image"
+ style="height:128px;width:128px;" readonly="1"/>
+ </xpath> -->
<field name="backorder_id" position="after">
<field name="select_shipping_option_so"/>
<field name="shipping_method_so_id"/>
@@ -105,7 +112,8 @@
<field name="count_line_detail"/>
<field name="dokumen_tanda_terima"/>
<field name="dokumen_pengiriman"/>
- <field name="quantity_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)], 'required': [('location_dest_id', '=', 60)]}"/>
+ <field name="quantity_koli"
+ attrs="{'invisible': [('location_dest_id', '!=', 60)], 'required': [('location_dest_id', '=', 60)]}"/>
<field name="total_mapping_koli" attrs="{'invisible': [('location_id', '!=', 60)]}"/>
<field name="total_koli_display" readonly="1" attrs="{'invisible': [('location_id', '!=', 60)]}"/>
<field name="linked_out_picking_id" readonly="1" attrs="{'invisible': [('location_id', '=', 60)]}"/>
@@ -132,8 +140,13 @@
<field name="scheduled_date" position="attributes">
<attribute name="readonly">1</attribute>
</field>
+ <xpath expr="//field[@name='move_ids_without_package']/form/group/field[@name='description_picking']"
+ position="after">
+ <field name="product_image" widget="image" string="Product Image"/>
+ </xpath>
+
<field name="origin" position="after">
-<!-- <field name="show_state_approve_md" invisible="1" optional="hide"/>-->
+ <!-- <field name="show_state_approve_md" invisible="1" optional="hide"/>-->
<field name="state_approve_md" widget="badge"/>
<field name="purchase_id"/>
<field name="sale_order"/>
@@ -141,7 +154,8 @@
<field name="date_doc_kirim" attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/>
<field name="summary_qty_operation"/>
<field name="count_line_operation"/>
- <field name="linked_manual_bu_out" attrs="{'invisible': [('location_id', '=', 60)]}" domain="[('picking_type_code', '=', 'outgoing'),('state', 'not in', ['done','cancel']), ('group_id', '=', group_id)]"/>
+ <field name="linked_manual_bu_out" attrs="{'invisible': [('location_id', '=', 60)]}"
+ domain="[('picking_type_code', '=', 'outgoing'),('state', 'not in', ['done','cancel']), ('group_id', '=', group_id)]"/>
<field name="account_id"
attrs="{
'readonly': [['state', 'in', ['done', 'cancel']]],
@@ -189,29 +203,35 @@
</group>
</group>
</page>
- <page string="Delivery" name="delivery_order" attrs="{'invisible': [('location_dest_id', '=', 60)]}">
+ <page string="Delivery" name="delivery_order"
+ attrs="{'invisible': [('location_dest_id', '=', 60)]}">
<group>
<group>
<field name="notee"/>
<field name="note_logistic"/>
<field name="note_info"/>
- <field name="responsible" />
- <field name="carrier_id" attrs="{'invisible': [('select_shipping_option_so', '=', 'biteship')]}" />
+ <field name="responsible"/>
+ <field name="carrier_id"
+ attrs="{'invisible': [('select_shipping_option_so', '=', 'biteship')]}"/>
<field name="biteship_id" invisible="1"/>
<field name="out_code" attrs="{'invisible': [['out_code', '=', False]]}"/>
<field name="picking_code" attrs="{'invisible': [['picking_code', '=', False]]}"/>
- <field name="picking_code" string="Picking code (akan digenerate ketika sudah di-validate)" attrs="{'invisible': [['picking_code', '!=', False]]}"/>
- <field name="driver_departure_date" attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/>
+ <field name="picking_code"
+ string="Picking code (akan digenerate ketika sudah di-validate)"
+ attrs="{'invisible': [['picking_code', '!=', False]]}"/>
+ <field name="driver_departure_date"
+ attrs="{'readonly':[('invoice_status', '=', 'invoiced')]}"/>
<field name="driver_arrival_date"/>
- <field name="delivery_tracking_no" attrs="{'invisible': [('select_shipping_option_so', '=', 'biteship')]}"/>
+ <field name="delivery_tracking_no"
+ attrs="{'invisible': [('select_shipping_option_so', '=', 'biteship')]}"/>
<field name="driver_id"/>
<field name='sj_return_date'/>
- <field name="sj_documentation" widget="image" />
- <field name="paket_documentation" widget="image" />
+ <field name="sj_documentation" widget="image"/>
+ <field name="paket_documentation" widget="image"/>
</group>
<!-- Biteship Group -->
<group attrs="{'invisible': [('select_shipping_option_so', '!=', 'biteship')]}">
- <field name="delivery_tracking_no" />
+ <field name="delivery_tracking_no"/>
<field name="shipping_method_so_id"/>
<field name="shipping_option_so_id"/>
<field name="biteship_shipping_price" readonly="1"/>
@@ -220,7 +240,8 @@
<field name="biteship_driver_name" readonly="1"/>
<field name="biteship_driver_phone" readonly="1"/>
<field name="biteship_driver_plate_number" readonly="1"/>
- <button name="action_open_biteship_tracking" string="Visit Biteship Tracking" type="object"/>
+ <button name="action_open_biteship_tracking" string="Visit Biteship Tracking"
+ type="object"/>
</group>
<group attrs="{'invisible': [('carrier_id', '!=', 151)]}">
@@ -261,23 +282,27 @@
</group>
</group>
</page>
- <page string="Check Product" name="check_product" attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}">
+ <page string="Check Product" name="check_product"
+ attrs="{'invisible': [('picking_type_code', '=', 'outgoing')]}">
<field name="check_product_lines"/>
</page>
- <page string="Barcode Product" name="barcode_product" attrs="{'invisible': [('picking_type_code', '!=', 'incoming')]}">
+ <page string="Barcode Product" name="barcode_product"
+ attrs="{'invisible': [('picking_type_code', '!=', 'incoming')]}">
<field name="barcode_product_lines"/>
</page>
<page string="Check Koli" name="check_koli" attrs="{'invisible': [('location_dest_id', '!=', 60)]}">
<field name="check_koli_lines"/>
</page>
- <page string="Mapping Koli" name="konfirm_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
+ <page string="Mapping Koli" name="konfirm_koli"
+ attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
<field name="konfirm_koli_lines"/>
</page>
- <page string="Konfirm Koli" name="scan_koli" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
+ <page string="Konfirm Koli" name="scan_koli"
+ attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}">
<field name="scan_koli_lines"/>
</page>
</page>
-
+
</field>
</record>
@@ -287,18 +312,20 @@
<field name="arch" type="xml">
<tree editable="bottom">
<field name="code_koli"/>
- <field name="koli_id" options="{'no_create': True}" domain="[('state', '=', 'not_delivered')]"/>
+ <field name="koli_id" options="{'no_create': True}" domain="[('state', '=', 'not_delivered')]"/>
<field name="scan_koli_progress"/>
</tree>
</field>
</record>
+
<record id="konfirm_koli_tree" model="ir.ui.view">
<field name="name">konfirm.koli.tree</field>
<field name="model">konfirm.koli</field>
<field name="arch" type="xml">
<tree editable="bottom">
- <field name="pick_id" options="{'no_create': True}" required="1" domain="[('picking_type_code', '=', 'internal'), ('group_id', '=', parent.group_id), ('linked_manual_bu_out', '=', parent.id)]"/>
+ <field name="pick_id" options="{'no_create': True}" required="1"
+ domain="[('picking_type_code', '=', 'internal'), ('group_id', '=', parent.group_id), ('linked_manual_bu_out', '=', parent.id)]"/>
</tree>
</field>
</record>
@@ -307,7 +334,7 @@
<field name="name">check.koli.tree</field>
<field name="model">check.koli</field>
<field name="arch" type="xml">
- <tree editable="bottom">
+ <tree editable="bottom">
<field name="koli"/>
<field name="reserved_id"/>
<field name="check_koli_progress"/>
@@ -344,12 +371,14 @@
<field name="model">stock.move.line</field>
<field name="inherit_id" ref="stock.view_stock_move_line_detailed_operation_tree"/>
<field name="arch" type="xml">
- <tree editable="bottom" decoration-muted="(state == 'done' and is_locked == True)" decoration-danger="qty_done&gt;product_uom_qty and state!='done' and parent.picking_type_code != 'incoming'" decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id">
+ <tree editable="bottom" decoration-muted="(state == 'done' and is_locked == True)"
+ decoration-danger="qty_done&gt;product_uom_qty and state!='done' and parent.picking_type_code != 'incoming'"
+ decoration-success="qty_done==product_uom_qty and state!='done' and not result_package_id">
<field name="note" placeholder="Add a note here"/>
</tree>
</field>
</record>
-
+
<record id="view_picking_internal_search_inherit" model="ir.ui.view">
<field name="name">stock.picking.internal.search.inherit</field>
@@ -382,7 +411,7 @@
</form>
</field>
</record>
-
+
<record id="action_warning_modal_wizard" model="ir.actions.act_window">
<field name="name">Peringatan Koli</field>
<field name="res_model">warning.modal.wizard</field>