summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorunknown <it@fixcomart.co.id>2025-09-17 14:28:21 +0700
committerunknown <it@fixcomart.co.id>2025-09-17 14:28:21 +0700
commit83608402d79a4b90d43a377aefbffbd17ae778c9 (patch)
tree74275a8e3d589825e8a26b220a77d68ce075aa58
parent099357ac4b17def99fe161eac3e04fec58814b06 (diff)
parent811e9dde8a027276b92a8dd594e11237b0b3b8c5 (diff)
(andri) fix merge
-rw-r--r--indoteknik_api/controllers/api_v1/sale_order.py2
-rw-r--r--indoteknik_api/models/sale_order.py2
-rwxr-xr-xindoteknik_custom/__manifest__.py1
-rwxr-xr-xindoteknik_custom/models/__init__.py3
-rw-r--r--indoteknik_custom/models/logbook_sj.py5
-rw-r--r--indoteknik_custom/models/manufacturing.py22
-rwxr-xr-xindoteknik_custom/models/purchase_order.py22
-rwxr-xr-xindoteknik_custom/models/purchase_order_line.py1
-rw-r--r--indoteknik_custom/models/refund_sale_order.py135
-rw-r--r--indoteknik_custom/models/report_logbook_sj.py61
-rwxr-xr-xindoteknik_custom/models/sale_order.py30
-rw-r--r--indoteknik_custom/models/sj_tele.py102
-rw-r--r--indoteknik_custom/models/stock_picking.py50
-rw-r--r--indoteknik_custom/models/tukar_guling.py40
-rw-r--r--indoteknik_custom/models/tukar_guling_po.py24
-rw-r--r--indoteknik_custom/report/purchase_report.xml27
-rwxr-xr-xindoteknik_custom/security/ir.model.access.csv7
-rwxr-xr-xindoteknik_custom/views/purchase_order.xml11
-rw-r--r--indoteknik_custom/views/refund_sale_order.xml2
-rw-r--r--indoteknik_custom/views/report_logbook_sj.xml92
-rwxr-xr-xindoteknik_custom/views/sale_order.xml6
-rw-r--r--indoteknik_custom/views/sj_tele.xml15
-rw-r--r--indoteknik_custom/views/tukar_guling_po.xml2
23 files changed, 496 insertions, 166 deletions
diff --git a/indoteknik_api/controllers/api_v1/sale_order.py b/indoteknik_api/controllers/api_v1/sale_order.py
index accc7531..1a75c830 100644
--- a/indoteknik_api/controllers/api_v1/sale_order.py
+++ b/indoteknik_api/controllers/api_v1/sale_order.py
@@ -198,7 +198,7 @@ class SaleOrder(controller.Controller):
if status in ['dikemas', 'dikirim', 'selesai', 'partial']:
filtered_orders = []
for sale_order in sale_orders:
- bu_pickings = [p for p in sale_order.picking_ids if p.picking_type_id and p.picking_type_id.id == 29]
+ bu_pickings = [p for p in sale_order.picking_ids if p.picking_type_id and p.picking_type_id.id == 29 and p.state != 'cancel']
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.sj_return_date]
diff --git a/indoteknik_api/models/sale_order.py b/indoteknik_api/models/sale_order.py
index c59dead9..23be358a 100644
--- a/indoteknik_api/models/sale_order.py
+++ b/indoteknik_api/models/sale_order.py
@@ -75,7 +75,7 @@ class SaleOrder(models.Model):
if sale_order.state == 'sale':
bu_pickings = [
p for p in sale_order.picking_ids
- if p.picking_type_id and p.picking_type_id.id == 29
+ if p.picking_type_id and p.picking_type_id.id == 29 and p.state != 'cancel'
]
# Hitung status masing-masing picking
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index f2145d0c..806d5706 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -183,6 +183,7 @@
'views/letter_receivable.xml',
'views/letter_receivable_mail_template.xml'
# 'views/reimburse.xml',
+ 'views/sj_tele.xml'
],
'demo': [],
'css': [],
diff --git a/indoteknik_custom/models/__init__.py b/indoteknik_custom/models/__init__.py
index c8910669..6dc61277 100755
--- a/indoteknik_custom/models/__init__.py
+++ b/indoteknik_custom/models/__init__.py
@@ -158,4 +158,5 @@ from . import tukar_guling
from . import tukar_guling_po
from . import update_date_planned_po_wizard
from . import unpaid_invoice_view
-from . import letter_receivable \ No newline at end of file
+from . import letter_receivable
+from . import sj_tele
diff --git a/indoteknik_custom/models/logbook_sj.py b/indoteknik_custom/models/logbook_sj.py
index 75b2622f..0cda9c8b 100644
--- a/indoteknik_custom/models/logbook_sj.py
+++ b/indoteknik_custom/models/logbook_sj.py
@@ -24,6 +24,7 @@ class LogbookSJ(models.TransientModel):
}
report_logbook = self.env['report.logbook.sj'].create([parameters_header])
+ seq=1
for line in logbook_line:
picking = self.env['stock.picking'].search([('picking_code', '=', line.name)], limit=1)
if not picking:
@@ -43,9 +44,11 @@ class LogbookSJ(models.TransientModel):
'tracking_no': stock.delivery_tracking_no,
'partner_id': parent_id,
'report_logbook_sj_id': report_logbook.id,
- 'note': line.note
+ 'note': line.note,
+ 'line_num': seq
}
self.env['report.logbook.sj.line'].create([data])
+ seq += 1
report_logbook_ids.append(report_logbook.id)
line.unlink()
diff --git a/indoteknik_custom/models/manufacturing.py b/indoteknik_custom/models/manufacturing.py
index aea01362..f986fd4f 100644
--- a/indoteknik_custom/models/manufacturing.py
+++ b/indoteknik_custom/models/manufacturing.py
@@ -4,54 +4,56 @@ import logging
_logger = logging.getLogger(__name__)
+
class Manufacturing(models.Model):
_inherit = 'mrp.production'
unbuild_counter = fields.Integer(string='Unbuild Counter', default=0, help='For restrict unbuild more than once')
-
+
def action_confirm(self):
if self._name != 'mrp.production':
return super(Manufacturing, self).action_confirm()
if not self.env.user.is_purchasing_manager:
raise UserError("Hanya bisa di confirm oleh Purchasing Manager")
-
+
# if self.location_src_id.id != 75:
# raise UserError('Component Location hanya bisa di AS/Stock')
# elif self.location_dest_id.id != 75:
# raise UserError('Finished Product Location hanya bisa di AS/Stock')
-
+
result = super(Manufacturing, self).action_confirm()
return result
-
+
def button_mark_done(self):
if self._name != 'mrp.production':
return super(Manufacturing, self).button_mark_done()
# Check product category
if self.product_id.categ_id.name != 'Finish Good':
raise UserError('Tidak bisa di complete karna product category bukan Unit / Finish Good')
-
+
if self.sale_order and self.sale_order.state != 'sale':
raise UserError(
('Tidak bisa Mark as Done.\nSales Order "%s" (Nomor: %s) belum dikonfirmasi.')
% (self.sale_order.partner_id.name, self.sale_order.name)
)
-
+
for line in self.move_raw_ids:
# if line.quantity_done > 0 and line.quantity_done != self.product_uom_qty:
# raise UserError('Qty Consume per Line tidak sama dengan Qty to Produce')
if line.forecast_availability != line.product_uom_qty:
- raise UserError('Qty Reserved belum sesuai dengan yang seharusnya, product: %s' % line.product_id.display_name)
+ raise UserError(
+ 'Qty Reserved belum sesuai dengan yang seharusnya, product: %s' % line.product_id.display_name)
result = super(Manufacturing, self).button_mark_done()
return result
-
+
def button_unbuild(self):
if self._name != 'mrp.production':
return super(Manufacturing, self).button_unbuild()
-
+
if self.unbuild_counter >= 1:
raise UserError('Tidak bisa unbuild lebih dari 1 kali')
-
+
self.unbuild_counter = self.unbuild_counter + 1
result = super(Manufacturing, self).button_unbuild()
diff --git a/indoteknik_custom/models/purchase_order.py b/indoteknik_custom/models/purchase_order.py
index 18811b85..68180235 100755
--- a/indoteknik_custom/models/purchase_order.py
+++ b/indoteknik_custom/models/purchase_order.py
@@ -6,6 +6,7 @@ import logging
from pytz import timezone, utc
import io
import base64
+from odoo.tools import lazy_property
try:
from odoo.tools.misc import xlsxwriter
except ImportError:
@@ -115,6 +116,20 @@ class PurchaseOrder(models.Model):
compute='_compute_complete_bu_in_count'
)
+ show_description = fields.Boolean(
+ string='Show Description',
+ default=True
+ )
+
+ @api.onchange('show_description')
+ def onchange_show_description(self):
+ if self.show_description == True:
+ for line in self.order_line:
+ line.show_description = True
+ else:
+ for line in self.order_line:
+ line.show_description = False
+
def _compute_complete_bu_in_count(self):
for order in self:
if order.state not in ['done', 'cancel']:
@@ -137,7 +152,7 @@ class PurchaseOrder(models.Model):
def _compute_date_planned(self):
""" date_planned = the earliest date_planned across all order lines. """
for order in self:
- order.date_planned = False
+ order.date_planned = order.date_planned
@api.constrains('date_planned')
def constrains_date_planned(self):
@@ -1066,8 +1081,11 @@ class PurchaseOrder(models.Model):
# sticky=True
# )
+ has_bom = self.product_bom_id.id
+ has_manufacturing = self.manufacturing_id.id
+
if not self.from_apo:
- if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader:
+ if not self.matches_so and not self.env.user.is_purchasing_manager and not self.env.user.is_leader and not has_bom and not has_manufacturing:
raise UserError("Tidak ada link dengan SO, harus di confirm oleh Purchasing Manager")
send_email = False
diff --git a/indoteknik_custom/models/purchase_order_line.py b/indoteknik_custom/models/purchase_order_line.py
index 315795d5..a3c3a33b 100755
--- a/indoteknik_custom/models/purchase_order_line.py
+++ b/indoteknik_custom/models/purchase_order_line.py
@@ -50,6 +50,7 @@ class PurchaseOrderLine(models.Model):
cost_service_per_item = fields.Float(string='Biaya Jasa Per Item', compute='_compute_doc_delivery_amt')
contribution_cost_service = fields.Float(string='Contribution Cost Service', compute='_compute_doc_delivery_amt')
ending_price = fields.Float(string='Ending Price', compute='_compute_doc_delivery_amt')
+ show_description = fields.Boolean(string='Show Description', help="Show Description when print po", default=True)
def _compute_doc_delivery_amt(self):
for line in self:
diff --git a/indoteknik_custom/models/refund_sale_order.py b/indoteknik_custom/models/refund_sale_order.py
index f511ed5d..4ee76006 100644
--- a/indoteknik_custom/models/refund_sale_order.py
+++ b/indoteknik_custom/models/refund_sale_order.py
@@ -77,7 +77,7 @@ class RefundSaleOrder(models.Model):
'account.move',
string="Journal Payment",
copy=False,
- help="Pilih transaksi salah transfer dari jurnal Uang Muka (journal_id=11) yang tidak terkait SO."
+ help="Pilih transaksi salah transfer dari jurnal Uang Muka yang tidak terkait SO."
)
tukar_guling_count = fields.Integer(
@@ -113,7 +113,7 @@ class RefundSaleOrder(models.Model):
string='Customer',
required=True
)
- advance_move_names = fields.Html(string="Group Journal SO", compute="_compute_advance_move_names")
+ advance_move_names = fields.Html(string="Group Journal Payment", compute="_compute_advance_move_names")
uang_masuk_type = fields.Selection([
('pdf', 'PDF'),
('image', 'Image'),
@@ -237,6 +237,7 @@ 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 []
+ invoices = self.env['account.move'].browse(invoice_ids)
if invoice_ids and refund_type and refund_type not in ['uang', 'barang_kosong_sebagian', 'barang_kosong', 'retur_half']:
raise UserError("Refund type Hanya Bisa Lebih Bayar, Barang Kosong Sebagian, atau Retur jika ada invoice")
@@ -276,18 +277,46 @@ class RefundSaleOrder(models.Model):
('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
- total_pembayaran = total_uang_muka + total_midtrans
+ piutangbca = self.env['account.move'].search([
+ ('ref', 'in', invoices.mapped('name')),
+ ('journal_id', '=', 4),
+ ('state', '=', 'posted'),
+ ])
+ piutangmdr = self.env['account.move'].search([
+ ('ref', 'in', invoices.mapped('name')),
+ ('journal_id', '=', 7),
+ ('state', '=', 'posted'),
+ ])
+ has_moves = bool(moves)
+ has_piutangmdr = bool(piutangmdr)
+ has_piutangbca = bool(piutangbca)
+ ssos = self.env['sale.order'].browse(so_ids)
+ has_settlement = any(so.payment_status == 'settlement' for so in ssos)
+
+ sisa_uang_masuk = 0.0
+ if has_moves and has_settlement:
+ sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount'))
+ elif has_moves:
+ sisa_uang_masuk = sum(moves.mapped('amount_total_signed'))
+ elif has_settlement:
+ sisa_uang_masuk = sum(ssos.mapped('gross_amount'))
+ elif has_piutangbca:
+ sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed'))
+ elif has_piutangmdr:
+ sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed'))
+ else:
+ raise UserError(
+ "❌ Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk "
+ "(Journal Uang Muka / Payment Invoices / Midtrans Payment)."
+ )
+
existing_refunds = self.env['refund.sale.order'].search([
('sale_order_ids', 'in', so_ids)
], order='id desc', limit=1)
if existing_refunds:
sisa_uang_masuk = existing_refunds.remaining_refundable
- else:
- sisa_uang_masuk = total_pembayaran
if sisa_uang_masuk < 0:
raise UserError("❌ Tidak ada sisa transaksi untuk di-refund.")
@@ -297,6 +326,14 @@ class RefundSaleOrder(models.Model):
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)
+ can_refund = sisa_uang_masuk - total_invoice
+
+ if amount_refund > can_refund or can_refund == 0.0:
+ raise ValidationError(
+ _("Maksimal refund yang bisa dilakukan adalah sebesar %s. "
+ "Silakan sesuaikan jumlah refund.") % (can_refund)
+ )
+
if amount_refund <= 0.00:
raise ValidationError('Total Refund harus lebih dari 0 jika ingin mengajukan refund')
@@ -393,9 +430,16 @@ class RefundSaleOrder(models.Model):
total_invoice = sum(self.env['account.move'].browse(invoice_ids).mapped('amount_total_signed'))
vals['total_invoice'] = total_invoice
uang_masuk = rec.uang_masuk
+ can_refund = uang_masuk - total_invoice
amount_refund = vals.get('amount_refund', rec.amount_refund)
+ if amount_refund > can_refund:
+ raise ValidationError(
+ _("Maksimal refund yang bisa dilakukan adalah sebesar %s. "
+ "Silakan sesuaikan jumlah refund.") % (can_refund)
+ )
+
if amount_refund <= 0:
raise ValidationError("Total Refund harus lebih dari 0.")
@@ -437,15 +481,34 @@ class RefundSaleOrder(models.Model):
def _compute_advance_move_names(self):
for rec in self:
move_links = []
+
+ invoice_ids = rec.sale_order_ids.mapped('invoice_ids')
+
moves = self.env['account.move'].search([
('sale_id', 'in', rec.sale_order_ids.ids),
('journal_id', '=', 11),
- ('state', '=', 'posted')
+ ('state', '=', 'posted'),
+ ])
+
+ piutangbca = self.env['account.move'].search([
+ ('ref', 'in', invoice_ids.mapped('name')),
+ ('journal_id', '=', 4),
+ ('state', '=', 'posted'),
+ ])
+
+ piutangmdr = self.env['account.move'].search([
+ ('ref', 'in', invoice_ids.mapped('name')),
+ ('journal_id', '=', 7),
+ ('state', '=', 'posted'),
])
- for move in moves:
+
+ all_moves = moves | piutangbca | piutangmdr
+
+ for move in all_moves:
url = f"/web#id={move.id}&model=account.move&view_type=form"
name = html_escape(move.name or 'Unnamed')
move_links.append(f'<a href="{url}" target="_blank">{name}</a>')
+
rec.advance_move_names = ', '.join(move_links) if move_links else "-"
@api.depends('sale_order_ids.user_id')
@@ -481,9 +544,35 @@ class RefundSaleOrder(models.Model):
('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
+ piutangbca = self.env['account.move'].search([
+ ('ref', 'in', all_invoices.mapped('name')),
+ ('journal_id', '=', 4),
+ ('state', '=', 'posted'),
+ ])
+ piutangmdr = self.env['account.move'].search([
+ ('ref', 'in', all_invoices.mapped('name')),
+ ('journal_id', '=', 7),
+ ('state', '=', 'posted'),
+ ])
+ has_moves = bool(moves)
+ has_piutangmdr = bool(piutangmdr)
+ has_piutangbca = bool(piutangbca)
+ ssos = self.env['sale.order'].browse(so_ids)
+ has_settlement = any(so.payment_status == 'settlement' for so in ssos)
+
+ sisa_uang_masuk = 0.0
+ if has_moves and has_settlement:
+ sisa_uang_masuk = sum(moves.mapped('amount_total_signed')) + sum(ssos.mapped('gross_amount'))
+ elif has_moves:
+ sisa_uang_masuk = sum(moves.mapped('amount_total_signed'))
+ elif has_settlement:
+ sisa_uang_masuk = sum(ssos.mapped('gross_amount'))
+ elif has_piutangbca:
+ sisa_uang_masuk = sum(piutangbca.mapped('amount_total_signed'))
+ elif has_piutangmdr:
+ sisa_uang_masuk = sum(piutangmdr.mapped('amount_total_signed'))
+
+ self.uang_masuk = sisa_uang_masuk - amount_refund_before
self.invoice_ids = all_invoices
@@ -504,7 +593,6 @@ class RefundSaleOrder(models.Model):
""" 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
@@ -513,21 +601,24 @@ class RefundSaleOrder(models.Model):
('journal_id', '=', 11), # Journal Uang Muka
('state', '=', 'posted'),
])
+ piutangbca = self.env['account.move'].search([
+ ('ref', 'in', rec.invoice_ids.mapped('name')),
+ ('journal_id', '=', 4),
+ ('state', '=', 'posted'),
+ ])
+ piutangmdr = self.env['account.move'].search([
+ ('ref', 'in', rec.invoice_ids.mapped('name')),
+ ('journal_id', '=', 7),
+ ('state', '=', 'posted'),
+ ])
- if not moves and so.payment_status != 'settlement':
+ if not moves and so.payment_status != 'settlement' and not piutangbca and not piutangmdr:
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"
+ "karena tidak memiliki Record Uang Masuk (Journal Uang Muka/Payment Invoice/Midtrans).\n"
"Pastikan semua SO yang dipilih sudah memiliki Record pembayaran yang valid."
)
diff --git a/indoteknik_custom/models/report_logbook_sj.py b/indoteknik_custom/models/report_logbook_sj.py
index 17119c12..3b07ff02 100644
--- a/indoteknik_custom/models/report_logbook_sj.py
+++ b/indoteknik_custom/models/report_logbook_sj.py
@@ -1,7 +1,14 @@
+from operator import index
+
from odoo import models, fields, api
from odoo.exceptions import UserError
from pytz import timezone
from datetime import datetime
+import requests
+import json
+import logging
+
+_logger = logging.getLogger(__name__)
class ReportLogbookSJ(models.Model):
_name = 'report.logbook.sj'
@@ -60,9 +67,28 @@ class ReportLogbookSJ(models.Model):
self.state = 'terima_semua'
else:
raise UserError('Hanya Accounting yang bisa Approve')
-
+
+
+ def write(self, vals):
+ res = super(ReportLogbookSJ, self).write(vals)
+ if 'report_logbook_sj_line' in vals or any(f in vals for f in ()):
+ self._resequence_lines()
+ return res
+
+ def _resequence_lines(self):
+ for rec in self:
+ lines = rec.report_logbook_sj_line.sorted(key=lambda l: (l.line_num or 0, l.id))
+ for idx, line in enumerate(lines, start=1):
+ if line.line_num != idx:
+ line.line_num = idx
+
+ @api.onchange('report_logbook_sj_line')
+ def _onchange_report_logbook_sj_line(self):
+ self._resequence_lines()
+
class ReportLogbookSJLine(models.Model):
_name = 'report.logbook.sj.line'
+ _order = 'sequence, id' # urut default di UI & ORM (drag pakai sequence)
name = fields.Char(string='SJ Number')
driver_id = fields.Many2one(comodel_name='res.users', string='Driver')
@@ -70,10 +96,41 @@ class ReportLogbookSJLine(models.Model):
arrival_date = fields.Char(string='Arrival Date')
carrier_id = fields.Many2one('delivery.carrier', string='Shipping Method')
tracking_no = fields.Char(string='Tracking No')
- logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ') # Corrected model name
+
+ # NOTE: field ini duplikat relasi; pakai salah satu saja.
+ # kamu boleh hapus logbook_sj_id kalau tidak dipakai di tempat lain.
+ logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ')
+
partner_id = fields.Many2one('res.partner', string='Customer')
picking_id = fields.Many2one('stock.picking', string='Picking')
sale_id = fields.Many2one('sale.order', string='Sale Order')
+
report_logbook_sj_id = fields.Many2one('report.logbook.sj', string='Logbook SJ')
not_exist = fields.Boolean(string='Not Exist')
note = fields.Char(string='Note')
+
+ sequence = fields.Integer(string='Sequence', default=0, index=True)
+
+ line_num = fields.Integer(string='No', compute='_compute_line_num', store=False)
+
+ @api.depends(
+ 'report_logbook_sj_id.report_logbook_sj_line',
+ 'report_logbook_sj_id.report_logbook_sj_line.sequence'
+ )
+ def _compute_line_num(self):
+ for parent in self.mapped('report_logbook_sj_id'):
+ lines = parent.report_logbook_sj_line.sorted(key=lambda l: (l.sequence or 0, l.id))
+ for i, l in enumerate(lines, start=1):
+ l.line_num = i
+ for rec in self.filtered(lambda r: not r.report_logbook_sj_id):
+ rec.line_num = 0
+
+ @api.model
+ def create(self, vals):
+ if not vals.get('sequence') and vals.get('report_logbook_sj_id'):
+ last = self.search(
+ [('report_logbook_sj_id', '=', vals['report_logbook_sj_id'])],
+ order='sequence desc, id desc', limit=1
+ )
+ vals['sequence'] = (last.sequence or 0) + 1
+ return super().create(vals)
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 8f49b579..484a9016 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -1674,7 +1674,7 @@ class SaleOrder(models.Model):
rec.expected_ready_to_ship = eta_date
@api.depends("order_line.product_id", "date_order")
- def _compute_etrts_date(self): # Function to calculate Estimated Ready To Ship Date
+ def _compute_etrts_date(self):
self._calculate_etrts_date()
@@ -1844,9 +1844,9 @@ class SaleOrder(models.Model):
def override_allow_create_invoice(self):
if not self.env.user.is_accounting:
raise UserError('Hanya Finance Accounting yang dapat klik tombol ini')
- for term in self.payment_term_id.line_ids:
- if term.days > 0:
- raise UserError('Hanya dapat digunakan pada Cash Before Delivery')
+ # for term in self.payment_term_id.line_ids:
+ # if term.days > 0:
+ # raise UserError('Hanya dapat digunakan pada Cash Before Delivery')
for line in self.order_line:
line.qty_to_invoice = line.product_uom_qty
@@ -3261,16 +3261,31 @@ class SaleOrder(models.Model):
f"❌ SO {self.name} Belum melakukan kirim barang "
f"({', '.join(not_done_pickings.mapped('name'))}). Selesaikan Pengiriman untuk melakukan refund."
)
+
+ invoice_ids = self.invoice_ids.filtered(lambda inv: inv.state != 'cancel')
+
moves = self.env['account.move'].search([
('sale_id', '=', self.id),
('journal_id', '=', 11),
('state', '=', 'posted'),
])
+ piutangbca = self.env['account.move'].search([
+ ('ref', 'in', invoice_ids.mapped('name')),
+ ('journal_id', '=', 4),
+ ('state', '=', 'posted'),
+ ])
+ piutangmdr = self.env['account.move'].search([
+ ('ref', 'in', invoice_ids.mapped('name')),
+ ('journal_id', '=', 7),
+ ('state', '=', 'posted'),
+ ])
# Default 0
total_uang_muka = 0.0
has_moves = bool(moves)
+ has_piutangmdr = bool(piutangmdr)
+ has_piutangbca = bool(piutangbca)
has_settlement = self.payment_status == 'settlement'
if has_moves and has_settlement:
@@ -3279,12 +3294,15 @@ class SaleOrder(models.Model):
total_uang_muka = sum(moves.mapped('amount_total_signed'))
elif has_settlement:
total_uang_muka = self.gross_amount
+ elif has_piutangbca:
+ total_uang_muka = sum(piutangbca.mapped('amount_total_signed'))
+ elif has_piutangmdr:
+ total_uang_muka = sum(piutangmdr.mapped('amount_total_signed'))
else:
raise UserError(
"Tidak bisa melakukan refund karena SO tidak memiliki Record Uang Masuk "
- "(Journal Uang Muka/Midtrans Payment)."
+ "(Journal Uang Muka/Payment Invoices/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
diff --git a/indoteknik_custom/models/sj_tele.py b/indoteknik_custom/models/sj_tele.py
new file mode 100644
index 00000000..d44aa338
--- /dev/null
+++ b/indoteknik_custom/models/sj_tele.py
@@ -0,0 +1,102 @@
+from odoo import models, fields, api
+from odoo.exceptions import UserError
+import requests
+import json
+import logging, subprocess
+import time
+from collections import OrderedDict
+
+_logger = logging.getLogger(__name__)
+
+class SjTele(models.Model):
+ _name = 'sj.tele'
+ _description = 'sj.tele'
+
+ picking_id = fields.Many2one('stock.picking', string='Picking')
+ sale_id = fields.Many2one('sale.order', string='Sales Order')
+ picking_name = fields.Char(string='Picking Name')
+ sale_name = fields.Char(string='Sale Name')
+ create_date = fields.Datetime(string='Create Date')
+ date_doc_kirim = fields.Datetime(string='Tanggal Kirim SJ')
+
+ # @api.model
+ # def run_pentaho_carte(self):
+ # carte = "http://127.0.0.1:8080"
+ # job_kjb = r"C:/Users/Indoteknik/Desktop/tes.kjb"
+ # params = {"job": job_kjb, "level": "Basic", "block": "Y"}
+ # try:
+ # r = requests.get(
+ # f"{carte}/kettle/executeJob/",
+ # params=params,
+ # auth=("cluster", "cluster"),
+ # timeout=900,
+ # )
+ # r.raise_for_status()
+ # # kalau Carte mengembalikan <result>ERROR</result>, anggap gagal
+ # if "<result>ERROR</result>" in r.text:
+ # raise UserError(f"Carte error: {r.text}")
+ # except Exception as e:
+ # _logger.exception("Carte call failed: %s", e)
+ # raise UserError(f"Gagal memanggil Carte: {e}")
+
+ # time.sleep(3)
+
+ # self.env['sj.tele'].sudo().woi()
+
+ # return True
+
+ def woi(self):
+ bot_mqdd = '8203414501:AAHy_XwiUAVrgRM2EJzW7sZx9npRLITZpb8'
+ chat_id_mqdd = '-1003087280519'
+ api_base = f'https://api.telegram.org/bot{bot_mqdd}'
+
+ data = self.search([], order='create_date asc', limit=15)
+
+ if not data:
+ text = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n✅ tidak ada data (semua sudah tercatat)."
+ try:
+ r = requests.post(api_base + "/sendMessage",
+ json={'chat_id': chat_id_mqdd, 'text': text},
+ timeout=20)
+ r.raise_for_status()
+ except Exception as e:
+ _logger.exception("Gagal kirim Telegram (no data): %s", e)
+ return True
+
+
+ lines = []
+ groups = OrderedDict()
+
+ for rec in data:
+ name = rec.picking_name or (rec.picking_id.name if rec.picking_id else '')
+ pid = rec.picking_id.id if rec.picking_id else ''
+ so = rec.sale_id.name or rec.sale_name or ''
+ dttm = (rec.picking_id.date_doc_kirim if (rec.picking_id and rec.picking_id.date_doc_kirim)
+ else getattr(rec, 'date_doc_kirim', None))
+
+ # format header tanggal (string), tanpa konversi Waktu/WIB
+ if dttm:
+ date_header = dttm if isinstance(dttm, str) else fields.Datetime.to_string(dttm)
+ date_header = date_header[:10]
+ else:
+ date_header = '(Tidak ada tanggal kirim SJ)'
+
+ if name:
+ groups.setdefault(date_header, []).append(f"- ({pid}) - {name} - {so}")
+
+ # build output berurutan per tanggal
+ for header_date, items in groups.items():
+ lines.append(header_date)
+ lines.extend(items)
+
+
+ header = "Berikut merupakan nomor BU/OUT yang belum ada di Logbook SJ report:\n"
+ text = header + "\n".join(lines)
+
+ try:
+ r = requests.post(api_base + "/sendMessage",
+ json={'chat_id': chat_id_mqdd, 'text': text})
+ r.raise_for_status()
+ except Exception as e:
+ _logger.exception("Gagal kirim Telegram: %s", e)
+ return True \ No newline at end of file
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index a48e0ed1..78a49ee4 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1743,27 +1743,37 @@ class StockPicking(models.Model):
}
if self.biteship_id:
- histori = self.get_manifest_biteship()
- day_start = order.estimated_arrival_days_start
- day_end = order.estimated_arrival_days
- if sale_order_delay:
- if sale_order_delay.status == 'delayed':
- day_start = day_start + sale_order_delay.days_delayed
- day_end = day_end + sale_order_delay.days_delayed
- elif sale_order_delay.status == 'early':
- day_start = day_start - sale_order_delay.days_delayed
- day_end = day_end - sale_order_delay.days_delayed
-
- eta_start = order.date_order + timedelta(days=day_start)
- eta_end = order.date_order + timedelta(days=day_end)
- formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}"
- response['eta'] = formatted_eta
- response['manifests'] = histori.get("manifests", [])
- response['delivered'] = histori.get("delivered",
- False) or self.sj_return_date != False or self.driver_arrival_date != False
- response['status'] = self._map_status_biteship(histori.get("delivered"))
+ try:
+ histori = self.get_manifest_biteship()
+ day_start = order.estimated_arrival_days_start
+ day_end = order.estimated_arrival_days
+ if sale_order_delay:
+ if sale_order_delay.status == 'delayed':
+ day_start += sale_order_delay.days_delayed
+ day_end += sale_order_delay.days_delayed
+ elif sale_order_delay.status == 'early':
+ day_start -= sale_order_delay.days_delayed
+ day_end -= sale_order_delay.days_delayed
+
+ eta_start = order.date_order + timedelta(days=day_start)
+ eta_end = order.date_order + timedelta(days=day_end)
+ formatted_eta = f"{eta_start.strftime('%d %b')} - {eta_end.strftime('%d %b %Y')}"
+
+ response['eta'] = formatted_eta
+ response['manifests'] = histori.get("manifests", [])
+ response['delivered'] = (
+ histori.get("delivered", False)
+ or self.sj_return_date != False
+ or self.driver_arrival_date != False
+ )
+ response['status'] = self._map_status_biteship(histori.get("delivered"))
- return response
+ return response
+
+ except Exception as e:
+ # Kalau ada error di biteship, log dan fallback ke Odoo
+ _logger.warning("Biteship error pada DO %s: %s", self.name, str(e))
+ # biarkan lanjut ke kondisi di bawah (pakai Odoo waybill_id)
if not self.waybill_id or len(self.waybill_id.manifest_ids) == 0:
response['delivered'] = self.sj_return_date != False or self.driver_arrival_date != False
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index f435dd9f..c683f75a 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -61,7 +61,7 @@ class TukarGuling(models.Model):
notes = fields.Text('Notes')
return_type = fields.Selection(String='Return Type', selection=[
('tukar_guling', 'Tukar Guling'), # -> barang yang sama
- ('revisi_so', 'Revisi SO')], required=True, tracking=3)
+ ('retur_so', 'Retur SO')], required=True, tracking=3, help='Retur SO (ORT-SRT),\n Tukar Guling (ORT-SRT-PICK-OUT)')
state = fields.Selection(string='Status', selection=[
('draft', 'Draft'),
('approval_sales', ' Approval Sales'),
@@ -169,7 +169,7 @@ class TukarGuling(models.Model):
raise UserError("❌ Picking type harus BU/OUT atau BU/PICK")
for rec in self:
if rec.operations and rec.operations.picking_type_id.id == 30:
- rec.return_type = 'revisi_so'
+ rec.return_type = 'retur_so'
if self.operations:
from_return_picking = self.env.context.get('from_return_picking', False) or \
@@ -315,7 +315,7 @@ class TukarGuling(models.Model):
@api.constrains('return_type', 'operations')
def _check_required_bu_fields(self):
for record in self:
- if record.return_type in ['revisi_so', 'tukar_guling'] and not record.operations:
+ if record.return_type in ['retur_so', 'tukar_guling'] and not record.operations:
raise ValidationError("Operations harus diisi")
@api.constrains('line_ids', 'state')
@@ -352,16 +352,16 @@ class TukarGuling(models.Model):
# ('state', '!=', 'cancel')
# ]) > 0
- # def _check_invoice_on_revisi_so(self):
+ # def _check_invoice_on_retur_so(self):
# for record in self:
- # if record.return_type == 'revisi_so' and record.origin:
+ # if record.return_type == 'retur_so' and record.origin:
# invoices = self.env['account.move'].search([
# ('invoice_origin', 'ilike', record.origin),
# ('state', 'not in', ['draft', 'cancel'])
# ])
# if invoices:
# raise ValidationError(
- # _("Tidak bisa memilih Return Type 'Revisi SO' karena dokumen %s sudah dibuat invoice.") % record.origin
+ # _("Tidak bisa memilih Return Type 'Retur SO' karena dokumen %s sudah dibuat invoice.") % record.origin
# )
@@ -414,7 +414,7 @@ class TukarGuling(models.Model):
self.ensure_one()
if self.operations.picking_type_id.id not in [29, 30]:
raise UserError("❌ Picking type harus BU/OUT atau BU/PICK")
- # self._check_invoice_on_revisi_so()
+ # self._check_invoice_on_retur_so()
operasi = self.operations.picking_type_id.id
tipe = self.return_type
pp = vals.get('return_type', tipe)
@@ -492,14 +492,14 @@ class TukarGuling(models.Model):
self.ensure_one()
self._check_not_allow_tukar_guling_on_bu_pick()
- # existing_tukar_guling = self.env['tukar.guling'].search([
- # ('operations', '=', self.operations.id),
- # ('id', '!=', self.id),
- # ('state', '!=', 'cancel'),
- # ], limit=1)
- #
- # if existing_tukar_guling:
- # raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name)
+ existing_tukar_guling = self.env['tukar.guling'].search([
+ ('operations', '=', self.operations.id),
+ ('id', '!=', self.id),
+ ('state', '!=', 'cancel'),
+ ], limit=1)
+
+ if existing_tukar_guling:
+ raise UserError("BU ini sudah pernah diretur oleh dokumen %s." % existing_tukar_guling.name)
picking = self.operations
if picking.picking_type_id.id == 30 and self.return_type == 'tukar_guling':
raise UserError("❌ BU/PICK tidak boleh di retur tukar guling")
@@ -530,7 +530,7 @@ class TukarGuling(models.Model):
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._check_invoice_on_retur_so()
self._validate_product_lines()
if self.state != 'draft':
@@ -553,7 +553,7 @@ class TukarGuling(models.Model):
self.state = 'done'
# OUT revisi SO
- elif self.operations.picking_type_id.id == 29 and self.return_type == 'revisi_so':
+ elif self.operations.picking_type_id.id == 29 and self.return_type == 'retur_so':
total_ort = self.env['stock.picking'].search_count([
('tukar_guling_id', '=', self.id),
('picking_type_id', '=', 74),
@@ -567,7 +567,7 @@ class TukarGuling(models.Model):
self.state = 'done'
# PICK revisi SO
- elif self.operations.picking_type_id.id == 30 and self.return_type == 'revisi_so':
+ elif self.operations.picking_type_id.id == 30 and self.return_type == 'retur_so':
done_ort = self.env['stock.picking'].search([
('tukar_guling_id', '=', self.id),
('picking_type_id', '=', 74),
@@ -581,7 +581,7 @@ class TukarGuling(models.Model):
def action_approve(self):
self.ensure_one()
self._validate_product_lines()
- # self._check_invoice_on_revisi_so()
+ # self._check_invoice_on_retur_so()
self._check_not_allow_tukar_guling_on_bu_pick()
operasi = self.operations.picking_type_id.id
@@ -631,7 +631,7 @@ class TukarGuling(models.Model):
elif rec.state == 'approval_finance':
if not rec.env.user.has_group('indoteknik_custom.group_role_fat'):
raise UserError("Hanya Finance Manager yang boleh approve tahap ini.")
- # rec._check_invoice_on_revisi_so()
+ # rec._check_invoice_on_retur_so()
rec.set_opt()
rec.state = 'approval_logistic'
rec.date_finance = now
diff --git a/indoteknik_custom/models/tukar_guling_po.py b/indoteknik_custom/models/tukar_guling_po.py
index f2f37606..f61b3828 100644
--- a/indoteknik_custom/models/tukar_guling_po.py
+++ b/indoteknik_custom/models/tukar_guling_po.py
@@ -38,9 +38,9 @@ class TukarGulingPO(models.Model):
)
ba_num = fields.Char('Nomor BA', tracking=3)
return_type = fields.Selection([
- ('revisi_po', 'Revisi PO'),
+ ('retur_po', 'Retur PO'),
('tukar_guling', 'Tukar Guling'),
- ], string='Return Type', required=True, tracking=3)
+ ], string='Return Type', required=True, tracking=3, help='Retur PO (VRT-PRT),\n Tukar Guling (VRT-PRT-INPUT-PUT')
notes = fields.Text('Notes', tracking=3)
tukar_guling_po_id = fields.Many2one('tukar.guling.po', string='Tukar Guling PO', ondelete='cascade')
line_ids = fields.One2many('tukar.guling.line.po', 'tukar_guling_po_id', string='Product Lines', tracking=3)
@@ -143,9 +143,9 @@ class TukarGulingPO(models.Model):
return res
- # def _check_bill_on_revisi_po(self):
+ # def _check_bill_on_retur_po(self):
# for record in self:
- # if record.return_type == 'revisi_po' and record.origin:
+ # if record.return_type == 'retur_po' and record.origin:
# bills = self.env['account.move'].search([
# ('invoice_origin', 'ilike', record.origin),
# ('move_type', '=', 'in_invoice'), # hanya vendor bill
@@ -153,7 +153,7 @@ class TukarGulingPO(models.Model):
# ])
# if bills:
# raise ValidationError(
- # _("Tidak bisa memilih Return Type 'Revisi PO' karena PO %s sudah dibuat vendor bill. Harus Cancel Jika ingin melanjutkan") % record.origin
+ # _("Tidak bisa memilih Return Type 'Retur PO' karena PO %s sudah dibuat vendor bill. Harus Cancel Jika ingin melanjutkan") % record.origin
# )
@api.onchange('operations')
@@ -284,7 +284,7 @@ class TukarGulingPO(models.Model):
@api.constrains('return_type', 'operations')
def _check_required_bu_fields(self):
for record in self:
- if record.return_type in ['revisi_po', 'tukar_guling'] and not record.operations:
+ if record.return_type in ['retur_po', 'tukar_guling'] and not record.operations:
raise ValidationError("Operations harus diisi")
@api.constrains('line_ids', 'state')
@@ -350,7 +350,7 @@ class TukarGulingPO(models.Model):
def write(self, vals):
if self.operations.picking_type_id.id not in [75, 28]:
raise UserError("❌ Tidak bisa retur bukan BU/INPUT atau BU/PUT!")
- # self._check_bill_on_revisi_po()
+ # self._check_bill_on_retur_po()
tipe = vals.get('return_type', self.return_type)
if self.operations and self.operations.picking_type_id.id == 28 and tipe == 'tukar_guling':
@@ -418,7 +418,7 @@ class TukarGulingPO(models.Model):
def action_submit(self):
self.ensure_one()
- # self._check_bill_on_revisi_po()
+ # self._check_bill_on_retur_po()
self._validate_product_lines()
self._check_not_allow_tukar_guling_on_bu_input()
@@ -463,7 +463,7 @@ class TukarGulingPO(models.Model):
def action_approve(self):
self.ensure_one()
self._validate_product_lines()
- # self._check_bill_on_revisi_po()
+ # self._check_bill_on_retur_po()
self._check_not_allow_tukar_guling_on_bu_input()
if not self.operations:
@@ -485,7 +485,7 @@ class TukarGulingPO(models.Model):
elif rec.state == 'approval_finance':
if not rec.env.user.has_group('indoteknik_custom.group_role_fat'):
raise UserError("Hanya Finance yang boleh approve tahap ini.")
- # rec._check_bill_on_revisi_po()
+ # rec._check_bill_on_retur_po()
rec.set_opt()
rec.state = 'approval_logistic'
rec.date_finance = now
@@ -501,7 +501,7 @@ class TukarGulingPO(models.Model):
def update_doc_state(self):
# bu input rev po
- if self.operations.picking_type_id.id == 28 and self.return_type == 'revisi_po':
+ if self.operations.picking_type_id.id == 28 and self.return_type == 'retur_po':
prt = self.env['stock.picking'].search([
('tukar_guling_po_id', '=', self.id),
('state', '=', 'done'),
@@ -510,7 +510,7 @@ class TukarGulingPO(models.Model):
if self.state == 'approved' and prt:
self.state = 'done'
# bu put rev po
- elif self.operations.picking_type_id.id == 75 and self.return_type == 'revisi_po':
+ elif self.operations.picking_type_id.id == 75 and self.return_type == 'retur_po':
total_prt = self.env['stock.picking'].search_count([
('tukar_guling_po_id', '=', self.id),
('picking_type_id.id', '=', 76)
diff --git a/indoteknik_custom/report/purchase_report.xml b/indoteknik_custom/report/purchase_report.xml
index 6666235a..dd19340d 100644
--- a/indoteknik_custom/report/purchase_report.xml
+++ b/indoteknik_custom/report/purchase_report.xml
@@ -60,10 +60,11 @@
<tr>
<td style="width:50%; border:1px solid #ccc; border-radius:8px; padding:10px; background:#fcfcfc; vertical-align:top;">
<strong style="color:#d32f2f;">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
+ PT. Indoteknik Dotcom Gemilang<br/>
+ Jl. Bandengan Utara Komp A 8 B
+ RT. Penjaringan, Kec. Penjaringan, Jakarta
+ (BELAKANG INDOMARET)<br/>
+ Daerah Khusus Ibukota Jakarta 14440
</td>
<td style="width:50%; border:1px solid #ccc; border-radius:8px; padding:10px; background:#fcfcfc; vertical-align:top;">
<strong style="color:#d32f2f;">Nama Vendor</strong><br/>
@@ -93,12 +94,6 @@
<!-- NO & DESCRIPTION + IMAGE -->
<td style="border:1px solid #ccc; padding: 6px; display:flex; align-items:center; gap:10px;">
- <t t-if="not line.image_small">
- <div style="width:40px; height:40px; background:#f5f5f5; border:1px solid #ddd; border-radius:6px; display:flex; align-items:center; justify-content:center; color:#999; font-size:10px;">
- N/A
- </div>
- </t>
-
<!-- TEKS -->
<div style="display:flex; flex-direction:column; flex:1;">
<span style="font-weight:bold; margin-bottom:2px;">
@@ -107,12 +102,12 @@
</div>
</td>
- <t t-if="line.image_small">
<td style="border:1px solid #ccc; padding:6px; text-align:center;">
- <img t-att-src="image_data_uri(line.image_small)"
- style="width:100px; height:100px; object-fit:contain; border:1px solid #ddd; border-radius:6px; background:#fff;"/>
- </td>
+ <t t-if="line.image_small">
+ <img t-att-src="image_data_uri(line.image_small)"
+ style="width:100px; height:100px; object-fit:contain; border:1px solid #ddd; border-radius:6px; background:#fff;"/>
</t>
+ </td>
<!-- QTY -->
<td style="border:1px solid #ccc; padding:6px; text-align:center;">
<span t-field="line.product_qty"/> <span t-field="line.product_uom"/>
@@ -135,9 +130,9 @@
</tr>
<!-- WEBSITE DESCRIPTION -->
- <t t-if="line.product_id.website_description">
+ <t t-if="line.show_description">
<tr t-attf-style="background-color: #{ '#fef5f5' if line_index % 2 == 0 else '#fffafa' }; ">
- <td colspan="6" style="padding: 10px 14px; font-size:12px; line-height:1.3; font-style:italic; color:#555; border-left:1px solid #ccc; border-right:1px solid #ccc; border-bottom:1px solid #ccc;">
+ <td colspan="6" style="padding: 10px 14px; font-size:10px; line-height:1.3; font-style:italic; color:#555; border-left:1px solid #ccc; border-right:1px solid #ccc; border-bottom:1px solid #ccc;">
<div t-raw="line.product_id.website_description"/>
</td>
</tr>
diff --git a/indoteknik_custom/security/ir.model.access.csv b/indoteknik_custom/security/ir.model.access.csv
index 25273893..ea6670eb 100755
--- a/indoteknik_custom/security/ir.model.access.csv
+++ b/indoteknik_custom/security/ir.model.access.csv
@@ -183,8 +183,11 @@ 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_purchase_order_update_date_wizard,access.purchase.order.update.date.wizard,model_purchase_order_update_date_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_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
@@ -192,10 +195,10 @@ access_tukar_guling_line_all_users,tukar.guling.line.all.users,model_tukar_gulin
access_tukar_guling_po_all_users,tukar.guling.po.all.users,model_tukar_guling_po,base.group_user,1,1,1,1
access_tukar_guling_line_po_all_users,tukar.guling.line.po.all.users,model_tukar_guling_line_po,base.group_user,1,1,1,1
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
access_change_date_planned_wizard,access.change.date.planned.wizard,model_change_date_planned_wizard,,1,1,1,1
access_unpaid_invoice_view,access.unpaid.invoice.view,model_unpaid_invoice_view,base.group_user,1,1,1,1
access_surat_piutang_user,surat.piutang user,model_surat_piutang,base.group_user,1,1,1,1
-access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,base.group_user,1,1,1,1 \ No newline at end of file
+access_surat_piutang_line_user,surat.piutang.line user,model_surat_piutang_line,base.group_user,1,1,1,1
+access_sj_tele,access.sj.tele,model_sj_tele,base.group_system,1,1,1,1
diff --git a/indoteknik_custom/views/purchase_order.xml b/indoteknik_custom/views/purchase_order.xml
index 821f3295..7feec934 100755
--- a/indoteknik_custom/views/purchase_order.xml
+++ b/indoteknik_custom/views/purchase_order.xml
@@ -99,6 +99,7 @@
<field name="total_delivery_amt" attrs="{'required': [('partner_id', 'in', [9688, 29712])]}"/>
<field name="product_bom_id" attrs="{'invisible': [('product_bom_id', '=', None)]}"/>
<field name="manufacturing_id" attrs="{'invisible': [('product_bom_id', '=', None)]}"/>
+ <field name="show_description" attrs="{'invisible': [('partner_id', 'not in', [5571, 35475, 38198, 88261,37905, 9688, 20625, 1371, 37902, 12119])]}" widget="boolean_toggle"/>
<!-- <field name="move_id" domain="[('move_type','=','entry')]" context="{'form_view_ref': 'account.view_move_form'}" options="{'no_create': True}"/> -->
</field>
<field name="amount_total" position="after">
@@ -112,6 +113,7 @@
</field>
<field name="product_id" position="before">
<field name="line_no" attrs="{'readonly': 1}" optional="hide"/>
+ <field name="show_description" optional="hide" widget="boolean_toggle"/>
</field>
<field name="product_id" position="attributes">
<attribute name="options">{'no_create': True}</attribute>
@@ -181,19 +183,20 @@
</field>
<field name="order_line" position="attributes">
- <attribute name="attrs">{'readonly': ['|', ('state', 'in', ['purchase', 'done', 'cancel']), ('has_active_invoice', '=', True)]}</attribute>
+ <!-- <attribute name="attrs">{'readonly': ['|', ('state', 'in', ['purchase', 'done', 'cancel']), ('has_active_invoice', '=', True)]}</attribute> -->
+ <attribute name="attrs">{'readonly': [('has_active_invoice', '=', True)]}</attribute>
</field>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='price_unit']" position="attributes">
- <attribute name="attrs">{'readonly': [], 'required': True}</attribute>
+ <attribute name="attrs">{'readonly': [('state', 'in', ['purchase', 'done', 'cancel'])], 'required': True}</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='taxes_id']" position="attributes">
- <attribute name="attrs">{'readonly': []}</attribute>
+ <attribute name="attrs">{'readonly': [('state', 'in', ['purchase', 'done', 'cancel'])]}</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page/field[@name='order_line']/tree/field[@name='product_qty']" position="attributes">
- <attribute name="attrs">{'required': True}</attribute>
+ <attribute name="attrs">{'readonly': [('state', 'in', ['purchase', 'done', 'cancel'])], 'required': True}</attribute>
</xpath>
<xpath expr="//form/sheet/notebook/page[@name='purchase_delivery_invoice']" position="before">
diff --git a/indoteknik_custom/views/refund_sale_order.xml b/indoteknik_custom/views/refund_sale_order.xml
index 0c6cd371..51f3deab 100644
--- a/indoteknik_custom/views/refund_sale_order.xml
+++ b/indoteknik_custom/views/refund_sale_order.xml
@@ -92,7 +92,7 @@
</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'])]}">
+ attrs="{'invisible': ['|', ('show_approval_alert', '=', False), '|', ('status', 'in', ['reject', 'refund']), ('refund_type', 'not in', ['retur_full', 'retur_sebagian'])]}">
⚠️ SO sudah melakukan retur barang. Silakan lanjutkan refund.
</div>
</xpath>
diff --git a/indoteknik_custom/views/report_logbook_sj.xml b/indoteknik_custom/views/report_logbook_sj.xml
index 94f6c2ab..46260cd5 100644
--- a/indoteknik_custom/views/report_logbook_sj.xml
+++ b/indoteknik_custom/views/report_logbook_sj.xml
@@ -12,15 +12,18 @@
<field name="date_approve"/>
<field name="approve_by_finance"/>
<field name="state"/>
+
</tree>
</field>
- </record>
+ </record>
<record id="report_logbook_sj_line_tree" model="ir.ui.view">
<field name="name">report.logbook.sj.line.tree</field>
<field name="model">report.logbook.sj.line</field>
<field name="arch" type="xml">
<tree editable="bottom">
+<!-- <field name="sequence" widget="handle"/>-->
+ <field name="line_num" string="No" readonly="1"/>
<field name="name"/>
<field name="driver_id"/>
<field name="departure_date"/>
@@ -42,50 +45,47 @@
<field name="arch" type="xml">
<form>
<header>
- <button name="approve"
- string="Validate"
- type="object"
- />
+ <button name="approve" string="Validate" type="object" />
</header>
<sheet string="Report logbook SJ">
- <div class="oe_button_box" name="button_box"/>
+ <div class="oe_button_box" name="button_box"/>
+ <group>
<group>
- <group>
- <field name="name" readonly="1"/>
- <field name="date" readonly="1"/>
- <field name="date_approve" readonly="1"/>
- </group>
- <group>
- <field name="approve_by_finance" readonly="1"/>
- <field name="state" readonly="1"/>
- <field name="created_by" readonly="1"/>
- <field name="approve_by" readonly="1"/>
- <field name="count_line" readonly="1"/>
- </group>
+ <field name="name" readonly="1"/>
+ <field name="date" readonly="1"/>
+ <field name="date_approve" readonly="1"/>
</group>
- <notebook>
- <page string="Line">
- <field name="report_logbook_sj_line"/>
- </page>
- </notebook>
- </sheet>
- <div class="oe_chatter">
- <field name="message_follower_ids" widget="mail_followers"/>
- <field name="message_ids" widget="mail_thread"/>
- </div>
+ <group>
+ <field name="approve_by_finance" readonly="1"/>
+ <field name="state" readonly="1"/>
+ <field name="created_by" readonly="1"/>
+ <field name="approve_by" readonly="1"/>
+ <field name="count_line" readonly="1"/>
+ </group>
+ </group>
+ <notebook>
+ <page string="Line">
+ <field name="report_logbook_sj_line"/>
+ </page>
+ </notebook>
+ </sheet>
+ <div class="oe_chatter">
+ <field name="message_follower_ids" widget="mail_followers"/>
+ <field name="message_ids" widget="mail_thread"/>
+ </div>
</form>
</field>
</record>
<record id="report_logbook_sj_view_search" model="ir.ui.view">
- <field name="name">report.logbook.sj.search.view</field> <!-- Made the name more descriptive -->
- <field name="model">report.logbook.sj</field>
- <field name="arch" type="xml">
- <search string="Search Report">
- <field name="sj_number"/>
- </search>
- </field>
- </record>
+ <field name="name">report.logbook.sj.search.view</field> <!-- Made the name more descriptive -->
+ <field name="model">report.logbook.sj</field>
+ <field name="arch" type="xml">
+ <search string="Search Report">
+ <field name="sj_number"/>
+ </search>
+ </field>
+ </record>
<record id="report_logbook_sj_action" model="ir.actions.act_window">
<field name="name">Report Logbook SJ</field>
@@ -94,9 +94,19 @@
<field name="view_mode">tree,form</field>
</record>
- <menuitem id="menu_report_logbook_sj"
- name="Report Logbook SJ"
- action="report_logbook_sj_action"
- parent="account.menu_finance_reports"
- sequence="200"/>
+ <menuitem id="menu_report_logbook_sj" name="Report Logbook SJ" action="report_logbook_sj_action" parent="account.menu_finance_reports" sequence="200"/>
+
+ <data noupdate="1">
+ <record id="cron_daily_logbook_gap_to_telegram" model="ir.cron">
+ <field name="name">Daily Logbook SJ Gap → Telegram</field>
+ <field name="model_id" ref="model_report_logbook_sj"/>
+ <field name="state">code</field>
+ <field name="code">model.cron_daily_logbook_gap_to_telegram()</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field name="user_id" ref="base.user_root"/>
+ <field name="active">False</field>
+ </record>
+ </data>
</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index 44da3e13..8d56bbbd 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -297,9 +297,9 @@
<field name="note" optional="hide"/>
<field name="note_procurement" optional="hide"/>
<field name="vendor_subtotal" optional="hide"/>
- <field name="unreserved_percent" widget="percentpie" string="Unreserved"/>
- <field name="reserved_percent" widget="percentpie" string="Reserved"/>
- <field name="delivered_percent" widget="percentpie" string="Delivered"/>
+ <field name="unreserved_percent" widget="percentpie" string="Unreserved" optional="hide"/>
+ <field name="reserved_percent" widget="percentpie" string="Reserved" optional="hide"/>
+ <field name="delivered_percent" widget="percentpie" string="Delivered" optional="hide"/>
<field name="weight" optional="hide"/>
<field name="is_has_disc" string="Flash Sale Item?" readonly="1" optional="hide"/>
<field name="amount_voucher_disc" string="Voucher" readonly="1" optional="hide"/>
diff --git a/indoteknik_custom/views/sj_tele.xml b/indoteknik_custom/views/sj_tele.xml
new file mode 100644
index 00000000..cefcc968
--- /dev/null
+++ b/indoteknik_custom/views/sj_tele.xml
@@ -0,0 +1,15 @@
+<odoo>
+ <data noupdate="1">
+ <record id="woi" model="ir.cron">
+ <field name="name">SJ TELE</field>
+ <field name="model_id" ref="model_sj_tele"/>
+ <field name="state">code</field>
+ <field name="code">model.woi()</field>
+ <field name="interval_number">1</field>
+ <field name="interval_type">days</field>
+ <field name="numbercall">-1</field>
+ <field name="user_id" ref="base.user_root"/>
+ <field name="active">False</field>
+ </record>
+ </data>
+</odoo> \ No newline at end of file
diff --git a/indoteknik_custom/views/tukar_guling_po.xml b/indoteknik_custom/views/tukar_guling_po.xml
index 548a209f..4a9ab25d 100644
--- a/indoteknik_custom/views/tukar_guling_po.xml
+++ b/indoteknik_custom/views/tukar_guling_po.xml
@@ -87,7 +87,7 @@
<!-- <field name="srt_num" readonly="1"/>-->
<field name="operations" string="Operations"
attrs="{
- 'required': [('return_type', 'in', ['revisi_po', 'tukar_guling'])]
+ 'required': [('return_type', 'in', ['retur_po', 'tukar_guling'])]
}"/>
<!-- <field name="origin" readonly="1"/>-->
<field name="origin_po" readonly="1"/>