diff options
| author | Indoteknik . <it@fixcomart.co.id> | 2025-08-21 20:03:48 +0700 |
|---|---|---|
| committer | Indoteknik . <it@fixcomart.co.id> | 2025-08-21 20:03:48 +0700 |
| commit | 0aa596341f2832ca17e713450f64d7f7f9b1ba4c (patch) | |
| tree | 57e19bbb32007fe355515fa56d22104a1185dc96 | |
| parent | c8ca82a47aca049cddf6a741477b7b06d20f8dea (diff) | |
| parent | 4b549234856b810bd99f8b1e18e01da90ccdc1e1 (diff) | |
Merge branch 'odoo-backup' of https://bitbucket.org/altafixco/indoteknik-addons into reminder-tempo-v2
| -rwxr-xr-x | indoteknik_custom/__manifest__.py | 4 | ||||
| -rw-r--r-- | indoteknik_custom/models/account_move.py | 4 | ||||
| -rw-r--r-- | indoteknik_custom/models/approval_payment_term.py | 8 | ||||
| -rwxr-xr-x | indoteknik_custom/models/sale_order.py | 63 | ||||
| -rw-r--r-- | indoteknik_custom/models/sale_order_line.py | 23 | ||||
| -rw-r--r-- | indoteknik_custom/models/stock_picking.py | 39 | ||||
| -rw-r--r-- | indoteknik_custom/models/tukar_guling.py | 3 | ||||
| -rw-r--r-- | indoteknik_custom/static/src/js/check_product_barcode.js | 97 | ||||
| -rw-r--r-- | indoteknik_custom/views/approval_payment_term.xml | 11 | ||||
| -rwxr-xr-x | indoteknik_custom/views/sale_order.xml | 11 |
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"/> |
