summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <it@fixcomart.co.id>2025-08-21 20:03:48 +0700
committerIndoteknik . <it@fixcomart.co.id>2025-08-21 20:03:48 +0700
commit0aa596341f2832ca17e713450f64d7f7f9b1ba4c (patch)
tree57e19bbb32007fe355515fa56d22104a1185dc96
parentc8ca82a47aca049cddf6a741477b7b06d20f8dea (diff)
parent4b549234856b810bd99f8b1e18e01da90ccdc1e1 (diff)
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into reminder-tempo-v2
-rwxr-xr-xindoteknik_custom/__manifest__.py4
-rw-r--r--indoteknik_custom/models/account_move.py4
-rw-r--r--indoteknik_custom/models/approval_payment_term.py8
-rwxr-xr-xindoteknik_custom/models/sale_order.py63
-rw-r--r--indoteknik_custom/models/sale_order_line.py23
-rw-r--r--indoteknik_custom/models/stock_picking.py39
-rw-r--r--indoteknik_custom/models/tukar_guling.py3
-rw-r--r--indoteknik_custom/static/src/js/check_product_barcode.js97
-rw-r--r--indoteknik_custom/views/approval_payment_term.xml11
-rwxr-xr-xindoteknik_custom/views/sale_order.xml11
10 files changed, 147 insertions, 116 deletions
diff --git a/indoteknik_custom/__manifest__.py b/indoteknik_custom/__manifest__.py
index 85603a33..360303b8 100755
--- a/indoteknik_custom/__manifest__.py
+++ b/indoteknik_custom/__manifest__.py
@@ -8,10 +8,10 @@
'author': 'Rafi Zadanly',
'website': '',
'images': ['assets/favicon.ico'],
- 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur', 'barcodes'],
+ 'depends': ['base', 'coupon', 'delivery', 'sale', 'sale_management', 'vit_kelurahan', 'vit_efaktur'],
'data': [
- 'security/ir.model.access.csv',
'views/assets.xml',
+ 'security/ir.model.access.csv',
'views/group_partner.xml',
'views/blog_post.xml',
'views/coupon_program.xml',
diff --git a/indoteknik_custom/models/account_move.py b/indoteknik_custom/models/account_move.py
index 6713e87c..e09f9c5c 100644
--- a/indoteknik_custom/models/account_move.py
+++ b/indoteknik_custom/models/account_move.py
@@ -323,8 +323,9 @@ class AccountMove(models.Model):
'reply_to': 'finance@indoteknik.co.id',
}
- _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}")
template.send_mail(invs[0].id, force_send=True, email_values=values)
+ _logger.info(f"Mengirim email ke: {values['email_to']} > email CC: {values['email_cc']}")
+ _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice (dtd = {dtd})")
# flag
invs.write({'reminder_sent_date': today})
# Post ke chatter
@@ -338,7 +339,6 @@ class AccountMove(models.Model):
author_id=system_id,
)
- _logger.info(f"Reminder terkirim ke {partner.name} ({values['email_to']}) → {len(invs)} invoice (dtd = {dtd})")
@api.onchange('invoice_date')
diff --git a/indoteknik_custom/models/approval_payment_term.py b/indoteknik_custom/models/approval_payment_term.py
index 4cf9a4c8..8618856a 100644
--- a/indoteknik_custom/models/approval_payment_term.py
+++ b/indoteknik_custom/models/approval_payment_term.py
@@ -72,20 +72,20 @@ class ApprovalPaymentTerm(models.Model):
if self.env.user.id != 688:
return
+ tracked_fields = {"blocking_stage", "warning_stage", "property_payment_term_id"}
+
for rec in self:
changes = []
old_values = old_values_dict.get(rec.id, {})
for field_name, new_value in vals.items():
- if field_name not in rec._fields or field_name == 'change_log_688':
+ if field_name not in tracked_fields:
continue
field = rec._fields[field_name]
old_value = old_values.get(field_name)
+ field_label = field.string # label user-friendly
- field_label = field.string # Ambil label user-friendly
-
- # Relational field
if field.type == 'many2one':
old_id = old_value[0] if old_value else False
is_different = old_id != new_value
diff --git a/indoteknik_custom/models/sale_order.py b/indoteknik_custom/models/sale_order.py
index 53be999f..ffb53dce 100755
--- a/indoteknik_custom/models/sale_order.py
+++ b/indoteknik_custom/models/sale_order.py
@@ -391,9 +391,9 @@ class SaleOrder(models.Model):
payment_state_custom = fields.Selection([
('unpaid', 'Unpaid'),
('partial', 'Partially Paid'),
- ('paid', 'Paid'),
+ ('paid', 'Full Paid'),
('no_invoice', 'No Invoice'),
- ], string="Payment Status", compute="_compute_payment_state_custom", store=False)
+ ], string="Payment Status Invoice", compute="_compute_payment_state_custom", store=False)
@api.depends('invoice_ids.payment_state', 'invoice_ids.amount_total', 'invoice_ids.amount_residual')
def _compute_payment_state_custom(self):
@@ -416,44 +416,43 @@ class SaleOrder(models.Model):
else:
order.payment_state_custom = 'partial'
- @api.depends('order_line.move_ids.state',
- 'order_line.move_ids.reserved_availability',
- 'order_line.move_ids.quantity_done')
+ @api.depends(
+ 'order_line.move_ids.state',
+ 'order_line.move_ids.reserved_availability',
+ 'order_line.move_ids.quantity_done',
+ 'order_line.move_ids.picking_type_id'
+ )
def _compute_reserved_delivered_pie(self):
for order in self:
- total_qty = sum(order.order_line.mapped('product_uom_qty'))
+ order_qty = sum(order.order_line.mapped('product_uom_qty')) or 0.0
reserved_qty = delivered_qty = 0.0
- if total_qty > 0:
- for line in order.order_line:
- order_qty = line.product_uom_qty or 0.0
- if not order_qty:
+ if order_qty > 0:
+ # Kumpulin semua moves dari order
+ all_moves = order.order_line.mapped('move_ids')
+
+ for move in all_moves:
+ # --- CASE 1: Move belum selesai ---
+ if move.state not in ('done', 'cancel'):
+ # Reserved qty hanya dari move yang belum selesai
+ reserved_qty += move.reserved_availability or 0.0
continue
- for move in line.move_ids:
- if move.state != 'done':
- # reserve qty (draft/assigned)
- if move.picking_type_id.code == 'internal':
- reserved_qty += move.reserved_availability or 0.0
- continue
-
- # sudah done → cek alur lokasi
- if move.location_dest_id.usage == 'customer':
- # barang keluar → delivered
- delivered_qty += move.quantity_done
- elif move.location_id.usage == 'customer':
- # barang balik (return) → kurangi delivered
- delivered_qty -= move.quantity_done
-
- # clamp biar ga minus
- delivered_qty = max(delivered_qty, 0)
+ # --- 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
- order.reserved_percent = (reserved_qty / total_qty) * 100
- order.delivered_percent = (delivered_qty / total_qty) * 100
- order.unreserved_percent = 100 - order.reserved_percent - order.delivered_percent
- else:
- order.reserved_percent = order.delivered_percent = order.unreserved_percent = 0
+ # Clamp supaya delivered gak minus
+ delivered_qty = max(delivered_qty, 0)
+ # Hitung persen
+ order.reserved_percent = min((reserved_qty / order_qty) * 100, 100) if order_qty else 0
+ order.delivered_percent = min((delivered_qty / order_qty) * 100, 100) if order_qty else 0
+ order.unreserved_percent = max(100 - order.reserved_percent - order.delivered_percent, 0)
def _has_ccm(self):
if self.id:
diff --git a/indoteknik_custom/models/sale_order_line.py b/indoteknik_custom/models/sale_order_line.py
index 2406995d..a20f93ef 100644
--- a/indoteknik_custom/models/sale_order_line.py
+++ b/indoteknik_custom/models/sale_order_line.py
@@ -66,21 +66,29 @@ class SaleOrderLine(models.Model):
if order_qty > 0:
for move in line.move_ids:
+ # --- CASE 1: Picking belum done ---
if move.state != 'done':
- # Reserve qty (hanya dari picking internal yang belum selesai)
if move.picking_type_id.code == 'internal':
reserved_qty += move.reserved_availability or 0.0
continue
- # Kalau sudah done → cek lokasi
- if move.location_dest_id.usage == 'customer':
- # Barang keluar → tambah delivered
+ # --- CASE 2: Picking sudah done ---
+ if move.picking_type_id.code == 'internal':
+ # PICK done → qty_done tetap dianggap reserved (masih nunggu OUT)
+ reserved_qty += move.quantity_done or 0.0
+
+ elif move.location_dest_id.usage == 'customer':
+ # OUT done (barang nyampe customer)
delivered_qty += move.quantity_done
+ reserved_qty -= move.quantity_done
+
elif move.location_id.usage == 'customer':
- # Barang balik dari customer (retur) → kurangi delivered
+ # Retur dari customer
delivered_qty -= move.quantity_done
+ reserved_qty += move.quantity_done
- # Jangan sampai delivered minus
+ # clamp supaya gak minus
+ reserved_qty = max(reserved_qty, 0)
delivered_qty = max(delivered_qty, 0)
# Hitung persentase
@@ -88,9 +96,6 @@ class SaleOrderLine(models.Model):
line.delivered_percent = (delivered_qty / order_qty) * 100 if order_qty else 0
line.unreserved_percent = 100 - line.reserved_percent - line.delivered_percent
-
-
-
def _get_outgoing_incoming_moves(self):
outgoing_moves = self.env['stock.move']
incoming_moves = self.env['stock.move']
diff --git a/indoteknik_custom/models/stock_picking.py b/indoteknik_custom/models/stock_picking.py
index 3d04a416..a48e0ed1 100644
--- a/indoteknik_custom/models/stock_picking.py
+++ b/indoteknik_custom/models/stock_picking.py
@@ -1330,18 +1330,20 @@ class StockPicking(models.Model):
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
self.date_reserved = current_time
+
# Validate Qty Demand Can't higher than Qty Product
- for move_line in self.move_line_ids_without_package:
- purchase_line = move_line.move_id.purchase_line_id
- if purchase_line:
- if purchase_line.product_uom_qty < move_line.product_uom_qty:
- raise UserError(
- _("Quantity demand (%s) tidak bisa lebih besar dari qty product (%s) untuk produk %s") % (
- move_line.product_uom_qty,
- purchase_line.product_uom_qty,
- move_line.product_id.display_name
+ if self.location_dest_id.id == 58 and 'BU/INPUT/' in self.name:
+ for move in self.move_ids_without_package:
+ purchase_line = move.purchase_line_id
+ if purchase_line:
+ if purchase_line.product_qty < move.quantity_done:
+ raise UserError(
+ _("Quantity demand (%s) tidak bisa lebih besar dari qty product (%s) untuk produk %s") % (
+ move.quantity_done,
+ purchase_line.product_qty,
+ move.product_id.display_name
+ )
)
- )
self.validation_minus_onhand_quantity()
self.responsible = self.env.user.id
@@ -2070,7 +2072,7 @@ class CheckProduct(models.Model):
_name = 'check.product'
_description = 'Check Product'
_order = 'picking_id, id'
- _inherit = ['barcodes.barcode_events_mixin'] # ⬅️ aktifkan barcode handler
+ _inherit = ['barcodes.barcode_events_mixin']
picking_id = fields.Many2one(
@@ -2086,21 +2088,6 @@ class CheckProduct(models.Model):
status = fields.Char(string='Status', compute='_compute_status')
code_product = fields.Char(string='Code Product')
- def write(self, vals):
- if 'code_product' in vals and not self.env.context.get('from_barcode_scan'):
- raise UserError("Field Code Product hanya bisa diisi melalui barcode scan.")
- res = super().write(vals)
- # konsolidasi dll milik Anda tetap jalan
- if not self.env.context.get('skip_consolidate'):
- self.with_context(skip_consolidate=True)._consolidate_duplicate_lines()
- return res
-
- # Scanner handler
- def on_barcode_scanned(self, barcode):
- self.ensure_one()
- self.with_context(from_barcode_scan=True).write({'code_product': barcode})
- self._onchange_code_product()
-
@api.onchange('code_product')
def _onchange_code_product(self):
if not self.code_product:
diff --git a/indoteknik_custom/models/tukar_guling.py b/indoteknik_custom/models/tukar_guling.py
index 4b03d4b0..23b9f085 100644
--- a/indoteknik_custom/models/tukar_guling.py
+++ b/indoteknik_custom/models/tukar_guling.py
@@ -109,9 +109,6 @@ class TukarGuling(models.Model):
('invoice_line_ids.product_id', 'in', product_ids),
]
- if rec.partner_id:
- domain.append(('partner_id', '=', rec.partner_id.id))
-
extra = []
if rec.origin:
extra.append(('invoice_origin', 'ilike', rec.origin))
diff --git a/indoteknik_custom/static/src/js/check_product_barcode.js b/indoteknik_custom/static/src/js/check_product_barcode.js
index f7a1bb75..2fddc616 100644
--- a/indoteknik_custom/static/src/js/check_product_barcode.js
+++ b/indoteknik_custom/static/src/js/check_product_barcode.js
@@ -1,41 +1,76 @@
-odoo.define('indoteknik_custom.prevent_manual_typing', function (require) {
- "use strict";
+odoo.define('indoteknik_custom.buffered_scanner', function (require) {
+ 'use strict';
+ console.log('✅ Indoteknik_Custom JS Loaded');
- console.log("✅ Custom JS from indoteknik_custom loaded!");
+ var GAP_MS = 120;
+ var MIN_LEN = 3;
+ var COMMIT_TIMEOUT = 180;
+ var buffer = '';
+ var last = 0;
+ var timer = null;
- const THRESHOLD_MS = 40; // jeda antar karakter dianggap scanner kalau <40ms
- let lastTime = 0;
- let burstCount = 0;
+ function reset() {
+ buffer = '';
+ last = 0;
+ if (timer) { clearTimeout(timer); timer = null; }
+ }
- function isScannerLike(now) {
- const gap = now - lastTime;
- lastTime = now;
- if (gap < THRESHOLD_MS) {
- burstCount += 1;
- } else {
- burstCount = 1;
- }
- return burstCount >= 3; // setelah 3 char cepat, dianggap scanner
+ function isCodeProduct(el) {
+ return el && el instanceof HTMLInputElement && el.name === 'code_product';
+ }
+
+ function commit() {
+ var el = document.activeElement;
+ if (!isCodeProduct(el)) { reset(); return; }
+ if (buffer.length >= MIN_LEN) {
+ el.value = buffer;
+ el.dispatchEvent(new Event('input', { bubbles: true }));
+ el.dispatchEvent(new Event('change', { bubbles: true }));
+ }
+ reset();
+ }
+
+ document.addEventListener('keydown', function (e) {
+ var el = document.activeElement;
+ if (!isCodeProduct(el)) return;
+
+ var key = e.key;
+
+ // ENTER mengakhiri scan
+ if (key === 'Enter') {
+ e.preventDefault();
+ commit();
+ return;
}
- document.addEventListener('keydown', function (e) {
- const t = e.target;
- if (!(t instanceof HTMLInputElement)) return;
+ // abaikan tombol kontrol (Shift, Tab, Arrow, Backspace, dll.)
+ if (key.length !== 1) return;
- // pastikan hanya field code_product
- if (t.name !== 'code_product') return;
+ var now = performance.now();
+ var gap = now - (last || now);
+ last = now;
- const now = performance.now();
- const scanner = isScannerLike(now);
+ if (!buffer || gap <= GAP_MS) {
+ // bagian dari "scan cepat" → tangani sendiri (hindari karakter hilang)
+ e.preventDefault();
+ buffer += key;
- // enter tetap boleh (scanner biasanya akhiri Enter)
- if (e.key === "Enter") return;
+ if (timer) clearTimeout(timer);
+ timer = setTimeout(function () {
+ // auto-commit jika scanner tidak mengirim Enter
+ commit();
+ }, COMMIT_TIMEOUT);
+ } else {
+ // jeda besar → kemungkinan manual. Kalau mau benar-benar melarang,
+ // buka komentar 2 baris di bawah.
+ // e.preventDefault();
+ // e.stopPropagation();
+ reset(); // keluar dari mode buffer agar manual normal
+ }
+ }, true);
- // kalau bukan scanner → blok manual ketikan
- if (!scanner) {
- e.preventDefault();
- e.stopPropagation();
- }
- }, true);
-});
+ document.addEventListener('focusin', function (e) {
+ if (isCodeProduct(e.target)) reset();
+ }, true);
+}); \ No newline at end of file
diff --git a/indoteknik_custom/views/approval_payment_term.xml b/indoteknik_custom/views/approval_payment_term.xml
index f7c24737..5c130f3f 100644
--- a/indoteknik_custom/views/approval_payment_term.xml
+++ b/indoteknik_custom/views/approval_payment_term.xml
@@ -86,6 +86,17 @@
<field name="view_mode">tree,form</field>
</record>
+ <record id="approval_payment_term_search" model="ir.ui.view">
+ <field name="name">approval.payment.term.search</field>
+ <field name="model">approval.payment.term</field>
+ <field name="arch" type="xml">
+ <search>
+ <field name="number" string="Document Number"/>
+ <field name="partner_id" string="Partner"/>
+ </search>
+ </field>
+ </record>
+
<menuitem id="menu_approval_payment_term" name="Approval Payment Term"
parent="account.menu_finance_receivables"
action="approval_payment_term_action"
diff --git a/indoteknik_custom/views/sale_order.xml b/indoteknik_custom/views/sale_order.xml
index be31456b..a1a5e0cd 100755
--- a/indoteknik_custom/views/sale_order.xml
+++ b/indoteknik_custom/views/sale_order.xml
@@ -464,9 +464,6 @@
<field name="pareto_status" optional="hide"/>
<field name="shipping_method_picking" optional="hide"/>
<field name="hold_outgoing" 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>
</field>
</record>
@@ -486,10 +483,10 @@
<field name="payment_state_custom" widget="badge"
decoration-danger="payment_state_custom == 'unpaid'"
decoration-success="payment_state_custom == 'paid'"
- decoration-warning="payment_state_custom == 'partial'"/>
- <field name="unreserved_percent" widget="percentpie" string="Unreserved"/>
- <field name="reserved_percent" widget="percentpie" string="Reserved"/>
- <field name="delivered_percent" widget="percentpie" string="Delivered"/>
+ decoration-warning="payment_state_custom == 'partial'" optional="hide"/>
+ <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="payment_type" optional="hide"/>
<field name="payment_status" optional="hide"/>
<field name="pareto_status" optional="hide"/>