summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-08-08 15:11:35 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-08-08 15:11:35 +0700
commitda1903796553ca0ee5195a7962521bc09b4b98a8 (patch)
tree0fe68309a748a74e121c9f9c51cb4b047e7cd217
parent7ebcf1675e55dae39f769466e5bf5ef064646f27 (diff)
parent19067dfea850b289b47d70ab36628b796ae87265 (diff)
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into reminder-tempo-v2
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py8
-rw-r--r--indoteknik_api/models/sale_order.py4
-rw-r--r--indoteknik_custom/models/account_move.py8
-rw-r--r--indoteknik_custom/models/account_move_due_extension.py9
-rw-r--r--indoteknik_custom/models/approval_payment_term.py1
-rwxr-xr-xindoteknik_custom/models/sale_order.py50
-rw-r--r--indoteknik_custom/models/stock_picking.py20
-rw-r--r--indoteknik_custom/models/tukar_guling.py197
-rw-r--r--indoteknik_custom/models/tukar_guling_po.py6
-rw-r--r--indoteknik_custom/views/account_move.xml3
-rw-r--r--indoteknik_custom/views/account_move_views.xml6
-rw-r--r--indoteknik_custom/views/approval_payment_term.xml3
12 files changed, 241 insertions, 74 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index 2348a126..6f5a3d44 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -206,8 +206,8 @@ class SaleOrder(controller.Controller):
]
total = len(bu_pickings)
done_pickings = [p for p in bu_pickings if p.state == 'done']
- done_with_driver = [p for p in done_pickings if p.driver_arrival_date]
- done_without_driver = [p for p in done_pickings if not p.driver_arrival_date]
+ done_with_driver = [p for p in done_pickings if p.sj_return_date]
+ done_without_driver = [p for p in done_pickings if not p.sj_return_date]
if status == 'dikemas' and len(done_pickings) == 0:
filtered_orders.append(sale_order)
@@ -424,8 +424,8 @@ class SaleOrder(controller.Controller):
return self.response('Unauthorized')
sale_order = request.env['sale.order'].sudo().search_read([('id', '=', id)], ['name'])
- pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'quotation_so_new')]).render_jasper([id], {})
- # pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'indoteknik_custom.report_saleorder_website')])._render_qweb_pdf([id])
+ # pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'quotation_so_new')]).render_jasper([id], {})
+ pdf, type = request.env['ir.actions.report'].sudo().search([('report_name', '=', 'indoteknik_custom.report_saleorder_website')])._render_qweb_pdf([id])
if pdf and len(sale_order) > 0:
return rest_api.response_attachment({
diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py
index 615dcdcb..0561043b 100644
--- a/indoteknik_api/models/sale_order.py
+++ b/indoteknik_api/models/sale_order.py
@@ -67,8 +67,8 @@ class SaleOrder(models.Model):
# Hitung status masing-masing picking
total = len(bu_pickings)
done_pickings = [p for p in bu_pickings if p.state == 'done']
- done_with_driver = [p for p in done_pickings if p.driver_arrival_date]
- done_without_driver = [p for p in done_pickings if not p.driver_arrival_date]
+ done_with_driver = [p for p in done_pickings if p.sj_return_date]
+ done_without_driver = [p for p in done_pickings if not p.sj_return_date]
if len(done_pickings) == 0:
data['status'] = 'sale'
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index bf7e2d68..12978a2f 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -95,6 +95,14 @@ class AccountMove(models.Model):
)
payment_date = fields.Date(string="Payment Date", compute='_compute_payment_date')
+ partial_payment = fields.Float(string="Partial Payment", compute='compute_partial_payment')
+
+ 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':
+ move.partial_payment = move.amount_total_signed - move.amount_residual_signed
+ else:
+ move.partial_payment = 0
def _compute_payment_date(self):
for move in self:
diff --git a/indoteknik_custom/models/account_move_due_extension.py b/indoteknik_custom/models/account_move_due_extension.py
index 4a3f40e2..d354e3e3 100644
--- a/indoteknik_custom/models/account_move_due_extension.py
+++ b/indoteknik_custom/models/account_move_due_extension.py
@@ -33,6 +33,7 @@ class DueExtension(models.Model):
counter = fields.Integer(string="Counter", compute='_compute_counter')
approve_by = fields.Many2one('res.users', string="Approve By", readonly=True)
date_approve = fields.Datetime(string="Date Approve", readonly=True)
+
def _compute_counter(self):
for due in self:
due.counter = due.partner_id.counter
@@ -102,6 +103,14 @@ class DueExtension(models.Model):
self.date_approve = datetime.utcnow()
template = self.env.ref('indoteknik_custom.mail_template_due_extension_approve')
template.send_mail(self.id, force_send=True)
+ return {
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'sale.order',
+ 'view_mode': 'form',
+ 'res_id': self.order_id.id,
+ 'views': [(False, 'form')],
+ 'target': 'current',
+ }
def generate_due_line(self):
partners = self.partner_id.get_child_ids()
diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py
index 6c857b45..4cf9a4c8 100644
--- a/indoteknik_custom/models/approval_payment_term.py
+++ b/indoteknik_custom/models/approval_payment_term.py
@@ -39,6 +39,7 @@ class ApprovalPaymentTerm(models.Model):
('rejected', 'Rejected')],
default='waiting_approval_sales_manager', tracking=True)
reason_reject = fields.Selection([('reason1', 'Reason 1'), ('reason2', 'Reason 2'), ('reason3', 'Reason 3')], string='Reason Reject', tracking=True)
+ reject_reason = fields.Text('Reject Reason', tracking=True)
sale_order_ids = fields.Many2many(
'sale.order',
string='Sale Orders',
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 94cfdc39..5eb90d83 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -2159,7 +2159,12 @@ class SaleOrder(models.Model):
# if order.validate_partner_invoice_due():
# return self._create_notification_action('Notification',
# 'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
-
+
+ if not order._is_request_to_own_team_leader():
+ return self._create_notification_action(
+ 'Peringatan',
+ 'Hanya bisa konfirmasi SO tim Anda.'
+ )
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
@@ -2169,6 +2174,12 @@ class SaleOrder(models.Model):
self.check_limit_so_to_invoice()
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
+ elif order._requires_approval_team_sales():
+ self.check_product_bom()
+ self.check_credit_limit()
+ self.check_limit_so_to_invoice()
+ order.approval_status = 'approved'
+ return self._create_approval_notification('Team Sales')
raise UserError("Bisa langsung Confirm")
@@ -2385,12 +2396,20 @@ class SaleOrder(models.Model):
return self._create_notification_action('Notification',
'Terdapat invoice yang telah melewati batas waktu, mohon perbarui pada dokumen Due Extension')
+ if not order._is_request_to_own_team_leader():
+ return self._create_notification_action(
+ 'Warning',
+ 'Hanya bisa konfirmasi SO tim Anda.'
+ )
if order._requires_approval_margin_leader():
order.approval_status = 'pengajuan2'
return self._create_approval_notification('Pimpinan')
elif order._requires_approval_margin_manager():
order.approval_status = 'pengajuan1'
return self._create_approval_notification('Sales Manager')
+ elif order._requires_approval_team_sales():
+ order.approval_status = 'approved'
+ return self._create_approval_notification('Team Sales')
order.approval_status = 'approved'
order._set_sppkp_npwp_contact()
@@ -2491,8 +2510,33 @@ class SaleOrder(models.Model):
return self.total_percent_margin <= 15 and not self.env.user.is_leader
def _requires_approval_margin_manager(self):
- return 15 < self.total_percent_margin <= 24 and not self.env.user.is_sales_manager and not self.env.user.id == 375 and not self.env.user.is_leader
- # return self.total_percent_margin >= 15 and not self.env.user.is_leader and not self.env.user.is_sales_manager
+ return 15 < self.total_percent_margin < 18 and not self.env.user.is_sales_manager and not self.env.user.id == 375 and not self.env.user.is_leader
+
+ def _requires_approval_team_sales(self):
+ return (
+ 18 <= self.total_percent_margin <= 24
+ and self.env.user.id not in [11, 9, 375] # Eko, Ade, Putra
+ and not self.env.user.is_sales_manager
+ and not self.env.user.is_leader
+ )
+
+
+ def _is_request_to_own_team_leader(self):
+ user = self.env.user
+
+ # Pengecualian Pak Akbar & Darren
+ if user.is_leader or user.is_sales_manager:
+ return True
+
+ if not self.team_id or not self.team_id.user_id:
+ return True
+
+ salesperson_id = self.user_id.id
+ approver_id = user.id
+ team_leader_id = self.team_id.user_id.id
+
+ return salesperson_id == approver_id or approver_id == team_leader_id
+
def _create_approval_notification(self, approval_role):
title = 'Warning'
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index f2f5f52a..46bb6cee 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1059,17 +1059,21 @@ class StockPicking(models.Model):
return
self.sale_id.date_doc_kirim = self.date_doc_kirim
+ from odoo import fields
+
def action_assign(self):
- res = super(StockPicking, self).action_assign()
- for move in self:
- # if not move.sale_id.hold_outgoing and move.location_id.id != 57 and move.location_dest_id.id != 60:
- # TODO cant skip hold outgoing cause of not singleton method
- current_time = datetime.datetime.utcnow()
- move.real_shipping_id = move.sale_id.real_shipping_id
- move.date_availability = current_time
- # self.check_state_reserve()
+ pickings_to_assign = self.filtered(lambda p: not (p.sale_id and p.sale_id.hold_outgoing))
+
+ res = super(StockPicking, pickings_to_assign).action_assign()
+
+ current_time = datetime.datetime.utcnow()
+ for picking in pickings_to_assign:
+ picking.real_shipping_id = picking.sale_id.real_shipping_id
+ picking.date_availability = current_time
+
return res
+
def ask_approval(self):
if self.env.user.is_accounting:
raise UserError("Bisa langsung Validate")
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 8de5b671..eadb164c 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -2,6 +2,7 @@ from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
import logging
from datetime import datetime
+from collections import defaultdict
_logger = logging.getLogger(__name__)
@@ -479,13 +480,22 @@ class TukarGuling(models.Model):
# raise UserError("BU ini sudah pernah diretur oleh dokumen lain.")
if self.operations.picking_type_id.id == 29:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name)
-
+ # Cek apakah ada BU/PICK di origin
+ origin = self.operations.origin
+ has_bu_pick = self.env['stock.picking'].search_count([
+ ('origin', '=', origin),
+ ('picking_type_id', '=', 30),
+ ('state', '!=', 'cancel')
+ ]) > 0
+
+ if has_bu_pick:
+ for line in self.line_ids:
+ mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ total_qty = sum(l.qty_return for l in mapping_lines)
+ if total_qty != line.product_uom_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ )
# self._check_invoice_on_revisi_so()
self._validate_product_lines()
@@ -531,7 +541,7 @@ class TukarGuling(models.Model):
])
if self.state == 'approved' and done_ort:
self.state = 'done'
- else:
+ elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so' and not has_bu_pick:
raise UserError("Tidak bisa menentukan jenis retur.")
def action_approve(self):
@@ -544,12 +554,22 @@ class TukarGuling(models.Model):
tipe = self.return_type
if self.operations.picking_type_id.id == 29:
- for line in self.line_ids:
- mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
- total_qty = sum(l.qty_return for l in mapping_lines)
- if total_qty != line.product_uom_qty:
- raise UserError(
- _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name)
+ # Cek apakah ada BU/PICK di origin
+ origin = self.operations.origin
+ has_bu_pick = self.env['stock.picking'].search_count([
+ ('origin', '=', origin),
+ ('picking_type_id', '=', 30),
+ ('state', '!=', 'cancel')
+ ]) > 0
+
+ if has_bu_pick:
+ for line in self.line_ids:
+ mapping_lines = self.mapping_koli_ids.filtered(lambda x: x.product_id == line.product_id)
+ total_qty = sum(l.qty_return for l in mapping_lines)
+ if total_qty != line.product_uom_qty:
+ raise UserError(
+ _("Qty di Koli tidak sesuai dengan qty retur untuk produk %s") % line.product_id.display_name
+ )
if operasi == 30 and self.operations.linked_manual_bu_out.state == 'done':
raise UserError("❌ Tidak bisa retur BU/PICK karena BU/OUT sudah done")
@@ -617,6 +637,23 @@ class TukarGuling(models.Model):
def _create_pickings(self):
_logger.info("🛠 Starting _create_pickings()")
+
+ def _force_locations(picking, from_loc, to_loc):
+ picking.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+ for move in picking.move_lines:
+ move.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+ for move_line in move.move_line_ids:
+ move_line.write({
+ 'location_id': from_loc,
+ 'location_dest_id': to_loc,
+ })
+
for record in self:
if not record.operations:
raise UserError("BU/OUT dari field operations tidak ditemukan.")
@@ -639,36 +676,70 @@ class TukarGuling(models.Model):
### ======== SRT dari BU/OUT =========
srt_return_lines = []
- 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)
- if not move:
- raise UserError(f"Move BU/OUT tidak ditemukan untuk produk {prod.display_name}")
- srt_return_lines.append((0, 0, {
- 'product_id': prod.id,
- 'quantity': qty_total,
- 'move_id': move.id,
- }))
- _logger.info(f"📟 SRT line: {prod.display_name} | qty={qty_total}")
+ if mapping_koli:
+ move_per_product = defaultdict(list)
+ for move in bu_out.move_lines:
+ move_per_product[move.product_id.id].append(move)
+
+ mapped_product_ids = set(mapping_koli.mapped('product_id').ids)
+
+ for product_id in mapped_product_ids:
+ qty_ret = sum(
+ line.qty_return for line in mapping_koli.filtered(lambda l: l.product_id.id == product_id))
+ if qty_ret <= 0:
+ continue
+
+ product_moves = move_per_product[product_id]
+ if not product_moves:
+ raise UserError(f"❌ Move BU/OUT tidak ditemukan untuk product ID {product_id}")
+
+ if len(product_moves) > 1:
+ _logger.warning(f"🟠 Detected duplicate moves for product {product_id}, picking {bu_out.name}")
+ chosen_move = product_moves[0]
+ else:
+ chosen_move = product_moves[0]
+
+ srt_return_lines.append((0, 0, {
+ 'product_id': product_id,
+ 'quantity': qty_ret,
+ 'move_id': chosen_move.id,
+ }))
+ _logger.info(f"📟 SRT line: {chosen_move.product_id.display_name} | qty={qty_ret}")
+
+ else:
+ # --- Fallback ke line_ids jika tidak ada mapping_koli ---
+ for line in record.line_ids:
+ move = bu_out.move_lines.filtered(lambda m: m.product_id == line.product_id)
+ if not move:
+ raise UserError(f"❌ Move BU/OUT tidak ditemukan untuk produk {line.product_id.display_name}")
+ srt_return_lines.append((0, 0, {
+ 'product_id': line.product_id.id,
+ 'quantity': line.product_uom_qty,
+ 'move_id': move.id,
+ }))
+ _logger.info(f"📟 SRT line (fallback): {line.product_id.display_name} | qty={line.product_uom_qty}")
srt_picking = None
if srt_return_lines:
+ # Tentukan tujuan lokasi berdasarkan ada/tidaknya mapping_koli
+ dest_location_id = BU_OUTPUT_LOCATION_ID if mapping_koli else BU_STOCK_LOCATION_ID
+
srt_wizard = self.env['stock.return.picking'].with_context({
'active_id': bu_out.id,
'default_location_id': PARTNER_LOCATION_ID,
- 'default_location_dest_id': BU_OUTPUT_LOCATION_ID,
+ 'default_location_dest_id': dest_location_id,
'from_ui': False,
}).create({
'picking_id': bu_out.id,
'location_id': PARTNER_LOCATION_ID,
- 'original_location_id': BU_OUTPUT_LOCATION_ID,
'product_return_moves': srt_return_lines
})
+
srt_vals = srt_wizard.create_returns()
srt_picking = self.env['stock.picking'].browse(srt_vals['res_id'])
+ _force_locations(srt_picking, PARTNER_LOCATION_ID, dest_location_id)
+
srt_picking.write({
- 'location_id': PARTNER_LOCATION_ID,
- 'location_dest_id': BU_OUTPUT_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
@@ -681,40 +752,66 @@ class TukarGuling(models.Model):
### ======== ORT dari BU/PICK =========
ort_pickings = []
is_retur_from_bu_pick = record.operations.picking_type_id.id == 30
- picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped(
- 'pick_id') or line.product_uom_qty
+ picks_to_return = [record.operations] if is_retur_from_bu_pick else mapping_koli.mapped('pick_id')
for pick in picks_to_return:
ort_return_lines = []
+
if is_retur_from_bu_pick:
- # Ambil dari tukar.guling.line
+ # Build map produk -> move list
+ move_map = defaultdict(list)
+ for move in pick.move_lines:
+ move_map[move.product_id.id].append(move)
+
for line in record.line_ids:
- move = pick.move_lines.filtered(lambda m: m.product_id == line.product_id)
- if not move:
+ moves = move_map.get(line.product_id.id)
+ if not moves:
raise UserError(
f"Move tidak ditemukan di BU/PICK {pick.name} untuk {line.product_id.display_name}")
+
+ chosen_move = moves[0]
+ if len(moves) > 1:
+ _logger.warning(
+ f"🟠 Duplicate move detected for {line.product_id.display_name} in {pick.name}. Using the first move only.")
+ pick.message_post(
+ body=f"🟠 Duplicate move ditemukan untuk produk <b>{line.product_id.display_name}</b>. Hanya 1 move yang dipakai.")
+
ort_return_lines.append((0, 0, {
'product_id': line.product_id.id,
'quantity': line.product_uom_qty,
- 'move_id': move.id,
+ 'move_id': chosen_move.id,
}))
_logger.info(
f"📟 ORT (BU/PICK langsung) | {pick.name} | {line.product_id.display_name} | qty={line.product_uom_qty}")
+
else:
- # Ambil dari mapping koli
+ # Mapping koli case
+ move_map = defaultdict(list)
+ for move in pick.move_lines:
+ move_map[move.product_id.id].append(move)
+
for mk in mapping_koli.filtered(lambda m: m.pick_id == pick):
- move = pick.move_lines.filtered(lambda m: m.product_id == mk.product_id)
- if not move:
+ moves = move_map.get(mk.product_id.id)
+ if not moves:
raise UserError(
f"Move tidak ditemukan di BU/PICK {pick.name} untuk {mk.product_id.display_name}")
+
+ chosen_move = moves[0]
+ if len(moves) > 1:
+ _logger.warning(
+ f"🟠 Duplicate move detected for {mk.product_id.display_name} in {pick.name}. Using the first move only.")
+ pick.message_post(
+ body=f"🟠 Duplicate move ditemukan untuk produk <b>{mk.product_id.display_name}</b>. Hanya 1 move yang dipakai.")
+
ort_return_lines.append((0, 0, {
'product_id': mk.product_id.id,
'quantity': mk.qty_return,
- 'move_id': move.id,
+ 'move_id': chosen_move.id,
}))
_logger.info(
f"📟 ORT (mapping koli) | {pick.name} | {mk.product_id.display_name} | qty={mk.qty_return}")
+ # Buat retur jika ada return line
if ort_return_lines:
ort_wizard = self.env['stock.return.picking'].with_context({
'active_id': pick.id,
@@ -724,27 +821,27 @@ class TukarGuling(models.Model):
}).create({
'picking_id': pick.id,
'location_id': BU_OUTPUT_LOCATION_ID,
- 'original_location_id': BU_STOCK_LOCATION_ID,
'product_return_moves': ort_return_lines
})
+
ort_vals = ort_wizard.create_returns()
ort_picking = self.env['stock.picking'].browse(ort_vals['res_id'])
+ _force_locations(ort_picking, BU_OUTPUT_LOCATION_ID, BU_STOCK_LOCATION_ID)
+
ort_picking.write({
- 'location_id': BU_OUTPUT_LOCATION_ID,
- 'location_dest_id': BU_STOCK_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
})
+
created_returns.append(ort_picking)
ort_pickings.append(ort_picking)
_logger.info(f"✅ ORT created: {ort_picking.name}")
record.message_post(
body=f"📦 <b>{ort_picking.name}</b> created by <b>{self.env.user.name}</b> (state: <b>{ort_picking.state}</b>)")
- ### ======== Tukar Guling: BU/OUT dan BU/PICK baru ========
+ ### ======== BU/PICK & BU/OUT Baru dari SRT/ORT ========
if record.return_type == 'tukar_guling':
-
# BU/PICK Baru dari ORT
for ort_p in ort_pickings:
return_lines = []
@@ -770,19 +867,18 @@ class TukarGuling(models.Model):
}).create({
'picking_id': ort_p.id,
'location_id': BU_STOCK_LOCATION_ID,
- 'original_location_id': BU_OUTPUT_LOCATION_ID,
'product_return_moves': return_lines
})
bu_pick_vals = bu_pick_wizard.create_returns()
new_pick = self.env['stock.picking'].browse(bu_pick_vals['res_id'])
+ _force_locations(new_pick, BU_STOCK_LOCATION_ID, BU_OUTPUT_LOCATION_ID)
+
new_pick.write({
- 'location_id': BU_STOCK_LOCATION_ID,
- 'location_dest_id': BU_OUTPUT_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
})
- new_pick.action_assign() # Penting agar bisa trigger check koli
+ new_pick.action_assign()
new_pick.action_confirm()
created_returns.append(new_pick)
_logger.info(f"✅ BU/PICK Baru dari ORT created: {new_pick.name}")
@@ -810,14 +906,13 @@ class TukarGuling(models.Model):
}).create({
'picking_id': srt_picking.id,
'location_id': BU_OUTPUT_LOCATION_ID,
- 'original_location_id': PARTNER_LOCATION_ID,
'product_return_moves': return_lines
})
bu_out_vals = bu_out_wizard.create_returns()
new_out = self.env['stock.picking'].browse(bu_out_vals['res_id'])
+ _force_locations(new_out, BU_OUTPUT_LOCATION_ID, PARTNER_LOCATION_ID)
+
new_out.write({
- 'location_id': BU_OUTPUT_LOCATION_ID,
- 'location_dest_id': PARTNER_LOCATION_ID,
'group_id': bu_out.group_id.id,
'tukar_guling_id': record.id,
'sale_order': record.origin
diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py
index 5d444472..0badc117 100644
--- a/indoteknik_custom/models/tukar_guling_po.py
+++ b/indoteknik_custom/models/tukar_guling_po.py
@@ -430,8 +430,8 @@ class TukarGulingPO(models.Model):
# Cek hak akses berdasarkan state
for rec in self:
if rec.state == 'approval_purchase':
- if not rec.env.user.has_group('indoteknik_custom.group_role_sales'):
- raise UserError("Hanya Sales yang boleh approve tahap ini.")
+ if not rec.env.user.has_group('indoteknik_custom.group_role_purchasing'):
+ raise UserError("Hanya Purchasing yang boleh approve tahap ini.")
rec.state = 'approval_finance'
rec.date_purchase = now
@@ -497,7 +497,7 @@ class TukarGulingPO(models.Model):
user = self.env.user
if not (
- user.has_group('indoteknik_custom.group_role_sales') or
+ user.has_group('indoteknik_custom.group_role_purchasing') or
user.has_group('indoteknik_custom.group_role_fat') or
user.has_group('indoteknik_custom.group_role_logistic')
):
diff --git a/indoteknik_custom/views/account_move.xml b/indoteknik_custom/views/account_move.xml
index 23f2d931..e5d1cf8a 100644
--- a/indoteknik_custom/views/account_move.xml
+++ b/indoteknik_custom/views/account_move.xml
@@ -29,7 +29,8 @@
</field>
<field name="payment_reference" position="after">
<field name="date_completed" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice')]}"/>
- <field name="payment_date" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice')]}"/>
+ <field name="payment_date" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice'), ('payment_date', '=', False)]}"/>
+ <field name="partial_payment" readonly="1" attrs="{'invisible': [('move_type', '!=', 'out_invoice'), ('payment_state', '!=', 'partial')]}"/>
<field name="reklas_id" attrs="{'invisible': [('move_type', '!=', 'out_invoice')]}"/>
</field>
<field name="invoice_date" position="after">
diff --git a/indoteknik_custom/views/account_move_views.xml b/indoteknik_custom/views/account_move_views.xml
index da25636e..0fd7c9cd 100644
--- a/indoteknik_custom/views/account_move_views.xml
+++ b/indoteknik_custom/views/account_move_views.xml
@@ -47,15 +47,20 @@
<button name="approve_new_due"
string="Approve"
type="object"
+ attrs="{'readonly': [('approval_status', 'in', ('approved'))]}"
/>
<button name="due_extension_approval"
string="Ask Approval"
type="object"
+ attrs="{'readonly': [('approval_status', 'in', ('approved'))]}"
/>
<button name="due_extension_cancel"
string="Cancel"
type="object"
+ attrs="{'readonly': [('approval_status', 'in', ('approved'))]}"
/>
+ <field name="approval_status" widget="statusbar"
+ statusbar_visible="pengajuan,approved"/>
</header>
<sheet>
<group>
@@ -67,7 +72,6 @@
<group>
<field name="is_approve" readonly="1"/>
<field name="counter" readonly="1"/>
- <field name="approval_status" readonly="1"/>
<field name="approve_by" readonly="1"/>
<field name="date_approve" readonly="1"/>
</group>
diff --git a/indoteknik_custom/views/approval_payment_term.xml b/indoteknik_custom/views/approval_payment_term.xml
index cc9db914..f7c24737 100644
--- a/indoteknik_custom/views/approval_payment_term.xml
+++ b/indoteknik_custom/views/approval_payment_term.xml
@@ -59,7 +59,8 @@
</group>
<group>
<field name="reason"/>
- <field name="reason_reject" attrs="{'invisible': [('state', '!=', 'rejected')]}"/>
+ <field name="reason_reject" invisible="1"/>
+ <field name="reject_reason" attrs="{'invisible': [('state', '!=', 'rejected')]}"/>
<field name="approve_date" readonly="1"/>
<field name="approve_sales_manager" readonly="1"/>
<field name="approve_finance" readonly="1"/>